Prepend a guard that runs `autoload -Uz compinit && compinit` when compdef isn't defined, so the generated zsh completion works without oh-my-zsh or if sourced before compinit.
145 lines
5.3 KiB
Bash
Executable File
145 lines
5.3 KiB
Bash
Executable File
#!/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 <<EOF
|
|
${MARK_BEGIN}
|
|
# Run the CI orchestrator as the ${RUN_USER} service user (production venv).
|
|
ci() { sudo -u ${RUN_USER} ${PROD_PY} -m ci_orchestrator "\$@"; }
|
|
# Same, but as the current user (no sudo) — handy for read-only commands.
|
|
ci-me() { ${PROD_PY} -m ci_orchestrator "\$@"; }
|
|
EOF
|
|
[ -n "$cmpl" ] && printf '[ -f "%s" ] && source "%s"\n' "$cmpl" "$cmpl"
|
|
printf '%s\n' "${MARK_END}"
|
|
}
|
|
|
|
# Generate a click shell-completion file for `ci`. Click's generated script
|
|
# calls the command (`ci`) to query completions, but `ci` is a sudo shell
|
|
# function — env vars don't survive sudo and `env VAR=x ci` can't call a
|
|
# function. Rewrite the query to call the venv python directly (read-only, no
|
|
# sudo) and drop zsh's `$+commands[ci]` guard (ci is a function, not on PATH).
|
|
gen_completion() {
|
|
local out="$1" shell="$2"
|
|
case "$shell" in
|
|
zsh)
|
|
# Self-load compinit if compdef isn't defined yet (so the file works even
|
|
# without oh-my-zsh, or if sourced before compinit).
|
|
printf '%s\n' 'if ! whence compdef >/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 "$@"
|