Skip to main content

max / makenotwork

sando/deploy: document units + testnot mirror refresh/redeploy Add a README for the deploy units: what each does, plus the testnot.work staging-mirror refresh (daily reload from the prod backup) and the manual binary redeploy procedure. Host secrets/certs/IPs stay in the private layer. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-07 21:47 UTC
Commit: a0b0f1d4be961e28ec104ad6bb9a1b3cb761432e
Parent: 5f28184
1 file changed, +55 insertions, -0 deletions
@@ -0,0 +1,55 @@
1 + # Sando deploy units
2 +
3 + systemd units and scripts that run on the Sando host (fw13) and on deploy
4 + targets. Host-specific secrets, certs, and tailnet IPs are **not** here — they
5 + live in the Syncthing private layer (`_private/infra/`, `_private/deploy`).
6 +
7 + ## Files
8 +
9 + | File | Where it runs | Purpose |
10 + |------|---------------|---------|
11 + | `sandod.service` | Sando host | The Sando daemon (`sandod`). |
12 + | `bootstrap-sandod-host.sh` | Sando host | One-time host setup for the daemon. |
13 + | `bootstrap-node.sh` | a deploy target | One-time node setup (release dirs, deploy user, service). |
14 + | `sando-daemon.toml.example` | Sando host | Template for the daemon config (`sando.toml`). |
15 + | `post-receive` | git remote | Push-to-deploy hook. |
16 + | `sandod-backup-fetch.{service,timer}` | Sando host | Daily pull of the prod backup to `/srv/sando/backups/latest.sql.gz` (04:00 UTC). |
17 + | `mnw-testnot-refresh.{sh,service,timer}` | Sando host | Daily refresh of the testnot.work staging mirror (05:00 UTC). |
18 +
19 + ## testnot.work staging mirror
20 +
21 + testnot is a read-only mirror of production, gated app-side to Fan+/creator
22 + accounts (`ACCESS_GATE=fan_plus_or_creator`). It exists so creators and Fan+
23 + members can preview upcoming features, and to back the pre-cutover migration
24 + dry-run.
25 +
26 + **Daily refresh** (`mnw-testnot-refresh.timer` → `.service` → `.sh`): reloads
27 + testnot's database from the backup `sandod-backup-fetch` already pulls, so the
28 + mirror tracks live. The script stops the app, resets the schema (recreating
29 + `public` owned by the app role — PG15+ otherwise blocks the app role's boot
30 + migrations), restores the dump as the postgres superuser over Tailscale SSH,
31 + and restarts the app, which applies any newer migrations on boot. Writes
32 + between refreshes are non-durable by design.
33 +
34 + Install on the Sando host:
35 +
36 + ```sh
37 + sudo install -m 0755 mnw-testnot-refresh.sh /usr/local/bin/mnw-testnot-refresh.sh
38 + sudo install -m 0644 mnw-testnot-refresh.service /etc/systemd/system/
39 + sudo install -m 0644 mnw-testnot-refresh.timer /etc/systemd/system/
40 + sudo systemctl daemon-reload
41 + sudo systemctl enable --now mnw-testnot-refresh.timer
42 + ```
43 +
44 + Run a refresh manually: `sudo /usr/local/bin/mnw-testnot-refresh.sh`
45 +
46 + **Redeploy the binary** (no Sando integration yet — manual): build the release
47 + on the Sando host, then over Tailscale SSH as root on the target, copy the
48 + current release dir to a new one, stream the new `makenotwork` binary in, stream
49 + a tar of `static/` + `docs/` (`docs/` = `server/site-docs/{public,examples}` +
50 + `server/docs/business/assumptions.toml`, mirroring `server/deploy/deploy.sh`),
51 + flip the `current` symlink, and `systemctl restart makenotwork.service` (which
52 + boot-migrates). Old release dirs are kept for rollback.
53 +
54 + Cert, the makenotwork Postgres password, the Caddyfile, and the operator
55 + runbook for the root-level node setup are in `_private/infra/testnot/`.