Просмотр исходного кода

Add install.sh for install steps 1-3

Runs the deploy user, build/install and secret/service steps from the
README in order. Safe to re-run: keeps the SSH key, webhook secret and
deploy log, refreshes binaries and config, restarts deployd. Checks the
sudoers file with visudo before it goes live and stops if it fails.
--dry-run prints every command without running anything or needing sudo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude 23 часов назад
Родитель
Сommit
48741b8610
2 измененных файлов с 130 добавлено и 1 удалено
  1. 14 1
      README.md
  2. 116 0
      install.sh

+ 14 - 1
README.md

@@ -28,6 +28,7 @@ The main piece is the **deploy pipeline**: when a pull request is merged into
 | `deployd/` | `/usr/local/bin/deployd` | Webhook receiver (Go, standard library only) |
 | `release/release` | `/usr/local/bin/release` | Builds, deploys, tags and logs one commit |
 | `release/test.sh` | not installed | Runs `release` end to end in a sandbox |
+| `install.sh` | not installed | Runs install steps 1–3 below; safe to re-run |
 | `sites.json` | `/etc/deployd/sites.json` | Which repo deploys to which site |
 | `systemd/deployd.service` | `/etc/systemd/system/` | Runs `deployd` as the `deploy` user |
 | `sudoers/60-deploy` | `/etc/sudoers.d/60-deploy` | Lets `deploy` restart `goapp@<name>` only |
@@ -56,7 +57,19 @@ Repos not listed are refused.
 
 ## Install (once, as the admin)
 
-Steps 1–4 match Phase 7 of the server plan.
+Steps 1–4 match Phase 7 of the server plan. **Steps 1–3 are one script:**
+
+```bash
+git clone https://git.monoaf.com/bajdev/devops.git ~/devops && cd ~/devops
+./install.sh --dry-run    # optional: print every command, change nothing
+./install.sh
+```
+
+It's safe to re-run, for example to upgrade after merging changes here. It
+keeps the deploy user's SSH key, the webhook secret and the deploy log,
+reinstalls the binaries and config, and restarts `deployd`. At the end it
+prints the deploy user's public key for Gogs. The steps it runs are below,
+for reference or to do by hand; then do step 4.
 
 **1. The `deploy` user**, its log, and its sudo rule:
 

+ 116 - 0
install.sh

@@ -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