#!/usr/bin/env bash # install-ci-alias.sh — install a `ci` shell helper that runs the CI # orchestrator as the ci-runner service user against the production venv. # # After install: ci validate host == sudo -u ci-runner \ # /opt/ci/venv/bin/python -m ci_orchestrator validate host # # Usage: # ./scripts/install-ci-alias.sh # install into the current user's # # ~/.bashrc and ~/.zshrc (if present) # ./scripts/install-ci-alias.sh --system # install system-wide via # # /etc/profile.d/ci-alias.sh (needs sudo) # ./scripts/install-ci-alias.sh --uninstall # remove the installed block # # Idempotent: re-running updates the block in place instead of duplicating it. set -euo pipefail PROD_PY="${CI_PROD_PYTHON:-/opt/ci/venv/bin/python}" RUN_USER="${CI_RUN_USER:-ci-runner}" MARK_BEGIN="# >>> ci-orchestrator alias >>>" MARK_END="# <<< ci-orchestrator alias <<<" log() { printf '[install-ci-alias] %s\n' "$*"; } die() { printf '[install-ci-alias] ERROR: %s\n' "$*" >&2; exit 1; } block() { # $1 (optional): path to a shell-completion file to source from the block. local cmpl="${1:-}" cat </dev/null 2>&1; then autoload -Uz compinit && compinit -u; fi' > "$out" _CI_COMPLETE=zsh_source "$PROD_PY" -m ci_orchestrator 2>/dev/null \ | sed -e "s#_CI_COMPLETE=zsh_complete ci)#_CI_COMPLETE=zsh_complete ${PROD_PY} -m ci_orchestrator)#" \ -e '/\$+commands\[ci\]/d' >> "$out" ;; bash) _CI_COMPLETE=bash_source "$PROD_PY" -m ci_orchestrator 2>/dev/null \ | sed -e "s#_CI_COMPLETE=bash_complete \$1#_CI_COMPLETE=bash_complete ${PROD_PY} -m ci_orchestrator#" > "$out" ;; *) return 1 ;; esac [ -s "$out" ] || { rm -f "$out"; return 1; } } strip_block() { # Remove an existing marked block from $1 (in place). No-op if absent. local file="$1" [ -f "$file" ] || return 0 if grep -qF "$MARK_BEGIN" "$file"; then # Delete everything between the markers, inclusive. sed -i "/$(printf '%s' "$MARK_BEGIN" | sed 's/[].[*^$/]/\\&/g')/,/$(printf '%s' "$MARK_END" | sed 's/[].[*^$/]/\\&/g')/d" "$file" # Drop a possible trailing blank line left behind. sed -i -e :a -e '/^\n*$/{$d;N;ba}' "$file" 2>/dev/null || true fi } install_into() { local file="$1" shell="" cmpl="" case "$file" in *.zshrc) shell="zsh" ;; *.bashrc) shell="bash" ;; esac if [ -n "$shell" ] && [ -x "$PROD_PY" ]; then cmpl="$HOME/.ci-completion.$shell" if gen_completion "$cmpl" "$shell"; then log "wrote $shell completion: $cmpl" else cmpl="" log "WARNING: could not generate $shell completion — skipped (alias still installed)." fi fi strip_block "$file" { printf '\n'; block "$cmpl"; } >> "$file" log "updated $file" } uninstall_from() { local file="$1" if [ -f "$file" ] && grep -qF "$MARK_BEGIN" "$file"; then strip_block "$file" log "removed block from $file" fi } main() { local mode="user" action="install" case "${1:-}" in --system) mode="system" ;; --uninstall) action="uninstall" ;; "" ) ;; * ) die "unknown argument: $1 (use --system or --uninstall)" ;; esac # Sanity: warn (don't fail) if the production venv is missing. [ -x "$PROD_PY" ] || log "WARNING: $PROD_PY not found — alias installed anyway; fix the venv before use." if [ "$mode" = "system" ]; then local target="/etc/profile.d/ci-alias.sh" if [ "$action" = "uninstall" ]; then sudo rm -f "$target" && log "removed $target" else block | sudo tee "$target" >/dev/null sudo chmod 0644 "$target" log "installed $target (applies to all login shells)" fi else local files=() [ -f "$HOME/.bashrc" ] && files+=("$HOME/.bashrc") [ -f "$HOME/.zshrc" ] && files+=("$HOME/.zshrc") # If neither exists, create ~/.bashrc so the alias lands somewhere. [ ${#files[@]} -eq 0 ] && files+=("$HOME/.bashrc") for f in "${files[@]}"; do if [ "$action" = "uninstall" ]; then uninstall_from "$f"; else install_into "$f"; fi done fi if [ "$action" = "install" ]; then log "done. Open a new shell, or run: source ~/.bashrc" log "try: ci validate host" else log "done. Open a new shell to drop the alias." fi } main "$@"