anvilsign in

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
4646
4747 ## 3. Build the image on your Mac, ship it to hagrid
4848
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+
4955 **Do not build on the VPS** — a release build needs ~2–4 GB peak and OOMs a
5056 cheap, swap-less droplet. Instead, cross-compile a static binary on your Mac
5157 (native speed, no QEMU) and copy it into a thin image.
⋯ 137 unchanged lines
189195
190196 ## Operations
191197
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
194202 future version won't auto-apply to an existing database yet (Toasty migration
195203 support is pending) — the git repos on disk are unaffected, but repo metadata
196204 in SQLite may need recreating until migrations land.
⋯ 7 unchanged lines
modifieddeploy/build.sh+3 −1
⋯ 32 unchanged lines
3333 docker build --platform linux/amd64 -t "$IMAGE" .
3434
3535 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'
3739
3840 rm -f deploy/anvild
3941 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"