|
@@ -0,0 +1,116 @@
|
|
|
|
|
+#!/usr/bin/env bash
|
|
|
|
|
+# install.sh [--dry-run]
|
|
|
|
|
+#
|
|
|
|
|
+# Installs the deploy pipeline from this checkout: steps 1-3 of "Install
|
|
|
|
|
+# (once, as the admin)" in README.md. Run it as the admin (a user with
|
|
|
|
|
+# sudo), from anywhere; it finds the repo from its own path.
|
|
|
|
|
+#
|
|
|
|
|
+# Safe to re-run, e.g. to upgrade after merging changes here: it never
|
|
|
|
|
+# replaces the deploy user's SSH key, the webhook secret or the deploy log,
|
|
|
|
|
+# and restarts deployd so a new binary takes effect.
|
|
|
|
|
+#
|
|
|
|
|
+# --dry-run prints every command instead of running it.
|
|
|
|
|
+set -euo pipefail
|
|
|
|
|
+
|
|
|
|
|
+dry=false
|
|
|
|
|
+case ${1:-} in
|
|
|
|
|
+ --dry-run) dry=true ;;
|
|
|
|
|
+ "") ;;
|
|
|
|
|
+ *) echo "usage: install.sh [--dry-run]" >&2; exit 2 ;;
|
|
|
|
|
+esac
|
|
|
|
|
+
|
|
|
|
|
+repo=$(cd "$(dirname "$0")" && pwd)
|
|
|
|
|
+home=/var/lib/deploy
|
|
|
|
|
+cd "$repo"
|
|
|
|
|
+
|
|
|
|
|
+say() { printf '\n== %s\n' "$*"; }
|
|
|
|
|
+run() { # print and run a command (only print with --dry-run)
|
|
|
|
|
+ printf '+ %s\n' "$*"
|
|
|
|
|
+ $dry || "$@"
|
|
|
|
|
+}
|
|
|
|
|
+as_deploy() { # run a shell snippet as the deploy user
|
|
|
|
|
+ printf "+ sudo -iu deploy sh -c '%s'\n" "$1"
|
|
|
|
|
+ $dry || sudo -iu deploy sh -c "$1"
|
|
|
|
|
+}
|
|
|
|
|
+exists() { # does a root-only path exist? In a dry run, never prompt: assume not.
|
|
|
|
|
+ if $dry; then sudo -n test -e "$1" 2>/dev/null; else sudo test -e "$1"; fi
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+for f in deployd/main.go release/release sites.json systemd/deployd.service sudoers/60-deploy; do
|
|
|
|
|
+ [[ -f $f ]] || { echo "missing $f: run this from a checkout of bajdev/devops" >&2; exit 1; }
|
|
|
|
|
+done
|
|
|
|
|
+command -v go >/dev/null || { echo "go is not installed" >&2; exit 1; }
|
|
|
|
|
+$dry || sudo -v
|
|
|
|
|
+
|
|
|
|
|
+say "1. deploy user, log and SSH key"
|
|
|
|
|
+if getent passwd deploy >/dev/null; then
|
|
|
|
|
+ echo "user deploy exists"
|
|
|
|
|
+ run sudo usermod -aG webops deploy
|
|
|
|
|
+else
|
|
|
|
|
+ run sudo useradd -r -m -d "$home" -s /bin/bash -G webops deploy
|
|
|
|
|
+fi
|
|
|
|
|
+if exists /var/log/deploys.log; then
|
|
|
|
|
+ echo "/var/log/deploys.log exists, kept"
|
|
|
|
|
+else
|
|
|
|
|
+ run sudo install -m 664 -o deploy -g webops /dev/null /var/log/deploys.log
|
|
|
|
|
+fi
|
|
|
|
|
+if exists "$home/.ssh/id_ed25519"; then
|
|
|
|
|
+ echo "deploy SSH key exists, kept"
|
|
|
|
|
+else
|
|
|
|
|
+ as_deploy 'mkdir -p -m 700 ~/.ssh && ssh-keygen -q -t ed25519 -N "" -f ~/.ssh/id_ed25519 -C deploy@vps'
|
|
|
|
|
+fi
|
|
|
|
|
+as_deploy 'git config --global user.name Deploy && git config --global user.email deploy@monoaf.com'
|
|
|
|
|
+# Record Gogs' host key now. Logging in only works once the Gogs deploy
|
|
|
|
|
+# account has the key, so a refusal here is expected on a first install.
|
|
|
|
|
+as_deploy 'ssh -o StrictHostKeyChecking=accept-new -o BatchMode=yes git@git.monoaf.com true 2>/dev/null || true'
|
|
|
|
|
+
|
|
|
|
|
+say "2. build, test and install"
|
|
|
|
|
+build=$(mktemp -d); trap 'rm -rf "$build"' EXIT
|
|
|
|
|
+(cd deployd && run go test ./... && run env CGO_ENABLED=0 go build -trimpath -o "$build/deployd" .)
|
|
|
|
|
+run sudo install -o root -g root -m 755 "$build/deployd" /usr/local/bin/deployd
|
|
|
|
|
+run sudo install -o root -g root -m 755 release/release /usr/local/bin/release
|
|
|
|
|
+run sudo install -d -o root -g root -m 755 /etc/deployd
|
|
|
|
|
+run sudo install -o root -g root -m 644 sites.json /etc/deployd/sites.json
|
|
|
|
|
+run sudo install -o root -g root -m 644 systemd/deployd.service /etc/systemd/system/deployd.service
|
|
|
|
|
+# sudoers: install under a name sudo ignores (it contains a dot), check it,
|
|
|
|
|
+# then move it into place, so a bad file can never break sudo.
|
|
|
|
|
+run sudo install -o root -g root -m 440 sudoers/60-deploy /etc/sudoers.d/60-deploy.new
|
|
|
|
|
+if $dry; then
|
|
|
|
|
+ echo "+ sudo visudo -cf /etc/sudoers.d/60-deploy.new && sudo mv /etc/sudoers.d/60-deploy.new /etc/sudoers.d/60-deploy"
|
|
|
|
|
+elif sudo visudo -cf /etc/sudoers.d/60-deploy.new; then
|
|
|
|
|
+ sudo mv /etc/sudoers.d/60-deploy.new /etc/sudoers.d/60-deploy
|
|
|
|
|
+else
|
|
|
|
|
+ sudo rm -f /etc/sudoers.d/60-deploy.new
|
|
|
|
|
+ echo "sudoers/60-deploy failed visudo; nothing changed" >&2
|
|
|
|
|
+ exit 1
|
|
|
|
|
+fi
|
|
|
|
|
+
|
|
|
|
|
+say "3. webhook secret and service"
|
|
|
|
|
+if exists /etc/deployd/secret.env; then
|
|
|
|
|
+ echo "/etc/deployd/secret.env exists, kept"
|
|
|
|
|
+else
|
|
|
|
|
+ # umask 077: the file is created readable by root only, never briefly wider.
|
|
|
|
|
+ echo "+ openssl rand -hex 32 | sed 's/^/WEBHOOK_SECRET=/' | sudo sh -c 'umask 077 && cat > /etc/deployd/secret.env'"
|
|
|
|
|
+ $dry || openssl rand -hex 32 | sed 's/^/WEBHOOK_SECRET=/' | sudo sh -c 'umask 077 && cat > /etc/deployd/secret.env'
|
|
|
|
|
+fi
|
|
|
|
|
+run sudo systemctl daemon-reload
|
|
|
|
|
+run sudo systemctl enable deployd
|
|
|
|
|
+run sudo systemctl restart deployd
|
|
|
|
|
+if ! $dry; then
|
|
|
|
|
+ sleep 1
|
|
|
|
|
+ systemctl is-active --quiet deployd || { echo "deployd is not running; see: journalctl -u deployd" >&2; exit 1; }
|
|
|
|
|
+ echo "deployd is active"
|
|
|
|
|
+fi
|
|
|
|
|
+
|
|
|
|
|
+say "Done. Next, by hand"
|
|
|
|
|
+cat <<EOF
|
|
|
|
|
+1. Gogs: create a 'deploy' user, add this key to it, and make it a Write
|
|
|
|
|
+ collaborator on each repo the pipeline releases:
|
|
|
|
|
+EOF
|
|
|
|
|
+if $dry; then echo " (deploy's public key: $home/.ssh/id_ed25519.pub)"; else sudo cat "$home/.ssh/id_ed25519.pub" | sed 's/^/ /'; fi
|
|
|
|
|
+cat <<EOF
|
|
|
|
|
+2. Step 4 of README.md: allow Gogs to call 127.0.0.1 (app.ini), restart gogs.
|
|
|
|
|
+3. For each site, follow "Moving a site onto the pipeline" in README.md.
|
|
|
|
|
+ The webhook secret is in /etc/deployd/secret.env:
|
|
|
|
|
+ sudo cat /etc/deployd/secret.env
|
|
|
|
|
+EOF
|