collin/anvil · c48ae612
feat: one-shot deploy script over a multiplexed SSH connection
Collin Richards · 2026-06-09 22:48 UTC · c48ae6125270d901a8b1e3b8758569e9bcf46981 · parent d894a14f · browse files
modifiedDEPLOY.md+10 −2
| ⋯ 45 unchanged lines | |||
| 46 | 46 | ||
| 47 | 47 | ## 3. Build the image on your Mac, ship it to hagrid | |
| 48 | 48 | ||
| 49 | + | **One-shot:** `./deploy/deploy.sh` does this whole section *and* section 4 in | |
| 50 | + | one go — it opens a single multiplexed SSH connection to hagrid (so the key | |
| 51 | + | passphrase is asked at most once), runs `build.sh` over it, then pipes | |
| 52 | + | `run.sh` to the host to restart the container. The rest of this section | |
| 53 | + | describes the individual steps it composes. | |
| 54 | + | ||
| 49 | 55 | **Do not build on the VPS** — a release build needs ~2–4 GB peak and OOMs a | |
| 50 | 56 | cheap, swap-less droplet. Instead, cross-compile a static binary on your Mac | |
| 51 | 57 | (native speed, no QEMU) and copy it into a thin image. | |
| ⋯ 137 unchanged lines | |||
| 189 | 195 | ||
| 190 | 196 | ## Operations | |
| 191 | 197 | ||
| 192 | - | - **Update**: re-run `./deploy/run.sh` (rebuilds the image, recreates the | |
| 193 | - | container; the `anvil-data` volume persists). NOTE: adding new DB tables in a | |
| 198 | + | - **Update**: from the Mac, `./deploy/deploy.sh` (build + ship + restart in | |
| 199 | + | one command, one passphrase prompt); or on hagrid, re-run `./deploy/run.sh` | |
| 200 | + | to recreate the container from the already-loaded image (the `anvil-data` | |
| 201 | + | volume persists). NOTE: adding new DB tables in a | |
| 194 | 202 | future version won't auto-apply to an existing database yet (Toasty migration | |
| 195 | 203 | support is pending) — the git repos on disk are unaffected, but repo metadata | |
| 196 | 204 | in SQLite may need recreating until migrations land. | |
| ⋯ 7 unchanged lines | |||
modifieddeploy/build.sh+3 −1
| ⋯ 32 unchanged lines | |||
| 33 | 33 | docker build --platform linux/amd64 -t "$IMAGE" . | |
| 34 | 34 | ||
| 35 | 35 | echo "==> shipping $IMAGE to $REMOTE" | |
| 36 | - | docker save "$IMAGE" | gzip | ssh "$REMOTE" 'docker load' | |
| 36 | + | # ANVIL_SSH_OPTS lets deploy.sh point us at its multiplexed master connection. | |
| 37 | + | # shellcheck disable=SC2086 | |
| 38 | + | docker save "$IMAGE" | gzip | ssh ${ANVIL_SSH_OPTS:-} "$REMOTE" 'docker load' | |
| 37 | 39 | ||
| 38 | 40 | rm -f deploy/anvild | |
| 39 | 41 | echo "==> done. On $REMOTE, run ./deploy/run.sh to (re)start the container." | |
addeddeploy/deploy.sh+26 −0
| 1 | + | #!/usr/bin/env bash | |
| 2 | + | # One-shot build + deploy from the Mac: cross-compile, ship the image to | |
| 3 | + | # hagrid, and (re)start the container there — over a single multiplexed SSH | |
| 4 | + | # connection, so the key passphrase is asked at most once. | |
| 5 | + | # | |
| 6 | + | # Usage: ./deploy/deploy.sh (env overrides: ANVIL_REMOTE, ANVIL_IMAGE, ...) | |
| 7 | + | set -euo pipefail | |
| 8 | + | ||
| 9 | + | REMOTE="${ANVIL_REMOTE:-hagrid}" | |
| 10 | + | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" | |
| 11 | + | ||
| 12 | + | # Master connection socket. Everything below reuses it via `ssh -S`. | |
| 13 | + | CTL="${TMPDIR:-/tmp}/anvil-deploy-$$.sock" | |
| 14 | + | cleanup() { ssh -S "$CTL" -O exit "$REMOTE" 2>/dev/null || true; } | |
| 15 | + | trap cleanup EXIT | |
| 16 | + | ||
| 17 | + | echo "==> opening SSH master connection to $REMOTE (passphrase asked once)" | |
| 18 | + | ssh -MNf -S "$CTL" "$REMOTE" | |
| 19 | + | ||
| 20 | + | # build.sh ships the image with `ssh $ANVIL_SSH_OPTS`, riding the master. | |
| 21 | + | export ANVIL_SSH_OPTS="-S $CTL" | |
| 22 | + | "$SCRIPT_DIR/build.sh" | |
| 23 | + | ||
| 24 | + | echo "==> (re)starting anvil on $REMOTE" | |
| 25 | + | # run.sh is standalone (docker only), so pipe it over — no checkout needed. | |
| 26 | + | ssh -S "$CTL" "$REMOTE" 'bash -s' < "$SCRIPT_DIR/run.sh" |