Skip to main content

max / makenotwork

11.8 KB · 247 lines History Blame Raw
1 # Sando topology config.
2 #
3 # Tiers run in declaration order. Each tier lists the gates that must pass to
4 # unlock promotion *to* the next tier, the nodes it ships to, and the canary
5 # policy for shipping within the tier.
6 #
7 # Day-one wiring: host (fw13, local) -> A (testnot.work) -> B (prod-1). C is
8 # declared but not provisioned; adding the second prod node later is a config
9 # edit (set provisioned = true, fill in [[tier.node]]).
10 #
11 # The first tier is "host" — it refers to whatever machine sandod runs on
12 # (currently fw13). Renamed from the legacy "mm" name in Session 1 of
13 # the sando bundle redesign.
14
15 [repo]
16 bare_path = "/srv/sando/mnw.git"
17 branch = "main"
18 # Canonical remote sandod fetches the deploy branch from before worktree-ing a
19 # sha (pull-based /rebuild). Authorized via the `sando-deploy` read-only
20 # service account on the makenot.work git server (fetch-only collaborator).
21 # Leave unset for push-based hosts (commits arrive via the bare-repo hook).
22 upstream = "git@ssh.makenot.work:max/makenotwork.git"
23
24 # ---- auxiliary repos ----
25 # Extra repos fetched and checked out beside the per-sha worktree so a cross-repo
26 # path dependency resolves at build time. The mnw-cli companion (built from the
27 # MNW worktree) carries `synckit-client = { path = "../../synckit/synckit-client" }`
28 # after synckit moved to its own repo; from <workdir>/<sha>/mnw-cli that resolves
29 # to <workdir>/synckit, so synckit must be checked out there. checkout_dir is a
30 # path under the workdir, nesting allowed; the checkout is shared across shas and
31 # refreshed to `branch` HEAD each build. See the maintainer wiki, sando-overview.
32 [[aux_repo]]
33 name = "synckit"
34 bare_path = "/srv/sando/synckit.git"
35 upstream = "git@ssh.makenot.work:max/synckit.git"
36 branch = "main"
37 checkout_dir = "synckit"
38
39 # The server and multithreaded both carry
40 # `docengine = { path = "../../Libraries/docengine" }` after docengine left
41 # MNW/shared on 2026-07-30. From <workdir>/<sha>/server that resolves to
42 # <workdir>/Libraries/docengine, hence the nested checkout_dir.
43 [[aux_repo]]
44 name = "docengine"
45 bare_path = "/srv/sando/docengine.git"
46 upstream = "git@ssh.makenot.work:max/docengine.git"
47 branch = "main"
48 checkout_dir = "Libraries/docengine"
49
50 # Prod-backup clones used by migration_dry_run on the Sando host, one per
51 # database that has a [[migration_check]] in the daemon config. For localhost dev
52 # a source can be a file:// path to a fixture dump. In prod we pull from
53 # alpha-west-1 via a scoped `backup-puller` rrsync user, whose forced command is
54 # `rrsync -ro /var/lib/mnw/backups` — so every path here is relative to that
55 # directory, and the per-DB subdirs are what `server/deploy/backup-db.sh` writes.
56 [[backup]]
57 name = "server"
58 source = "ssh://backup-puller@alpha-west-1:2200/makenotwork/latest.sql.gz"
59 local_path = "/srv/sando/backups/latest.sql.gz"
60
61 # multithreaded has its own database and applies its own migrations at boot
62 # (multithreaded/src/main.rs, `sqlx::migrate!()`), so it needs its own dump to
63 # dry-run against. Restoring the server's would prove nothing about it.
64 [[backup]]
65 name = "multithreaded"
66 source = "ssh://backup-puller@alpha-west-1:2200/multithreaded/latest.sql.gz"
67 local_path = "/srv/sando/backups/multithreaded-latest.sql.gz"
68
69 # ---- host: fw13 local pre-staging gate ----
70 [[tier]]
71 name = "host"
72 provisioned = true
73 canary = "sequential"
74 # code_smoke runs FIRST: it boots the freshly-built binary against a throwaway
75 # empty DB it migrates from scratch + seeds (the example catalog), then probes
76 # /health. Fast + infra-light (no prod-dump restore), so a green here proves the
77 # code is sound and isolates a later cargo_test / migration_dry_run red as an
78 # environment problem rather than a code one.
79 #
80 # hardening_test is cargo_test's blind spot: cargo_test builds with
81 # --features fast-tests, which relaxes the auth/sandbox rate limits and argon2
82 # cost AND #[ignore]s the whole rate-limiting suite, so the gate never touched
83 # the auth hardening it exists to protect. hardening_test re-runs that suite
84 # with no features, single-threaded, against production constants. It pays for
85 # a second compile of the server's test binary; that is the cost of the
86 # coverage.
87 #
88 # The lint + supply-chain gates. `-D warnings` used to be enforced in exactly
89 # one place (server/deploy/run-ci.sh, which died with the astra pipeline) and
90 # `cargo fmt --check` nowhere at all. fmt runs first: it needs no compilation,
91 # so a formatting red comes back in seconds instead of after a full build.
92 # cargo_audit and cargo_deny only run in crates carrying a triaged
93 # .cargo/audit.toml / deny.toml -- four crates in this repo fail cargo audit
94 # purely for lack of a reviewed advisory posture, and a permanently red gate
95 # teaches everyone to ignore it.
96 gates = [
97 { kind = "fmt" },
98 { kind = "code_smoke" },
99 { kind = "cargo_test" },
100 { kind = "hardening_test" },
101 { kind = "clippy" },
102 { kind = "cargo_audit" },
103 { kind = "cargo_deny" },
104 { kind = "migration_dry_run" },
105 { kind = "boot_smoke" },
106 ]
107 # Host is the daemon's own machine (fw13); no remote node row.
108
109 # ---- A: testnot.work staging ----
110 [[tier]]
111 name = "a"
112 provisioned = true
113 canary = "sequential"
114 # node_health (post-deploy) probes the deployed node over its executor — the
115 # gate boot_smoke used to stand in for, but boot_smoke runs on the build host and
116 # proves nothing about testnot-1. boot_smoke stays a host build-time gate only.
117 #
118 # A tier's gates guard promotion *out* of it, so THIS list is what stands between
119 # testnot and production. manual_confirm lives here for that reason. It used to
120 # sit only on tier b, where it guards b -> c — and c is not provisioned, so it
121 # gated nothing: a prod ship was cleared by node_health + burn_in alone, and
122 # hotfix: true skips burn_in, leaving one gate on a production deploy.
123 #
124 # hotfix does NOT skip manual_confirm (only burn_in), so every ship to prod-1
125 # now needs an explicit POST /confirm/a naming the version. That is the point:
126 # there was no human sign-off anywhere on the path to production.
127 gates = [
128 { kind = "node_health" },
129 { kind = "burn_in", hours = 48 },
130 { kind = "manual_confirm" },
131 ]
132 [[tier.node]]
133 name = "testnot-1"
134 ssh_target = "deploy@testnot" # tailnet name; never the public IP / testnot.work hostname
135 release_root = "/opt/mnw"
136 service_name = "makenotwork.service"
137 # What this box IS, checked against it before anything is pushed (2026-08-25).
138 # Note it is NOT the same base as prod-1 below: staging is two Ubuntu releases
139 # and four glibc point versions ahead, so tier A does not rehearse tier B on the
140 # one axis this field exists for. That is a real gap, tracked separately; the
141 # declaration states it rather than hiding it.
142 base_image = "ubuntu/26.04"
143 libc = "2.43"
144 # Pre-swap config-drift guard: source this node's env and run the freshly-rsynced
145 # binary in MNW_CHECK_CONFIG=1 mode BEFORE the symlink swap. A required var
146 # missing here (how prod crash-looped on CDN_BASE_URL, postmortem 2026-07-09 #2)
147 # fails the promote with the running service intact. The node runs 0.10.14, which
148 # supports the mode; validated end-to-end 2026-07-10.
149 config_check_env_file = "/etc/mnw/makenotwork.env"
150 # Readiness probe on top of `systemctl is-active`. Without this, node_health
151 # proves only that systemd thinks the unit is running — which a crash-looping
152 # binary can satisfy between restarts. The node binds HOST=127.0.0.1 PORT=8080
153 # (/etc/mnw/makenotwork.env); verified serving 200 on 2026-07-21.
154 health_url = "http://127.0.0.1:8080/health"
155 # Companion: multithreaded. Re-enabled 2026-08-06, after the read-only demo was
156 # provisioned on this box (GoingsOn multithreaded 34dc3ff6). It was removed in
157 # 2fccc2d0 because testnot-1 had no mt at all, and a companion block for a node
158 # that cannot install it fails the promote AFTER the symlink swap — the expensive
159 # half of the failure. That is no longer the case; what is on the box now:
160 # - postgres role + database `multithreaded`, peer auth over the unix socket
161 # (DATABASE_URL=postgres:///multithreaded, matching prod)
162 # - system user `multithreaded`, /opt/multithreaded, .env, and the unit from
163 # multithreaded/deploy/multithreaded.service, enabled and running on
164 # 127.0.0.1:3400, seeded via `--seed`
165 # - /usr/local/lib/mnw/install-companion.sh + /etc/sudoers.d/mnw-companion
166 #
167 # The install path was exercised end to end before this line was uncommented, as
168 # `deploy`, through the wrapper's own guards — a staged binary under
169 # */releases/*/companions/* installed to /opt/multithreaded and restarted the
170 # unit, exit 0. It is not inferred from the grant being present.
171 #
172 # mt is on testnot FIRST, unlike mnw-cli, because this is the tier where the 48h
173 # burn-in runs: a forum that fails to boot on the new binary should fail here,
174 # not on prod. See multithreaded/deploy/README.md.
175 [[tier.node.companion]]
176 name = "multithreaded"
177 install_path = "/opt/multithreaded/multithreaded"
178 service_name = "multithreaded.service"
179
180 # ---- B: prod-1 ----
181 [[tier]]
182 name = "b"
183 provisioned = true
184 canary = "sequential"
185 # Guards b -> c. Kept for the day prod-2 is provisioned; it gates nothing today
186 # (c has provisioned = false). The sign-off that matters for shipping to THIS
187 # tier lives on tier a.
188 gates = [
189 { kind = "node_health" },
190 { kind = "manual_confirm" },
191 ]
192 [[tier.node]]
193 name = "prod-1"
194 # Tailnet name; port 2200 supplied via /srv/sando/.ssh/config Host block.
195 # Service user is "makenotwork" (pre-existing on prod), not "deploy" — chose
196 # not to chown 885M of backups + redo postgres peer auth for a cosmetic rename.
197 ssh_target = "makenotwork@alpha-west-1"
198 release_root = "/opt/mnw"
199 service_name = "makenotwork.service"
200 # Measured 2026-08-25. The margin here is zero: makenotwork, mnw-cli and pom all
201 # carry a GLIBC_2.39 floor and this box has exactly 2.39, so one point release of
202 # drift on the build host breaks it. Declaring the number is what lets that be
203 # compared before a build rather than by the pre-swap `ldd` guard afterwards.
204 base_image = "ubuntu/24.04"
205 libc = "2.39"
206 # Pre-swap config-drift guard (see testnot-1 above). This node is the one the
207 # 0.10.14 deploy crash-looped on a missing CDN_BASE_URL — the exact miss this
208 # gate closes. First real exercise is the next prod promote.
209 config_check_env_file = "/etc/mnw/makenotwork.env"
210 # Readiness probe (see testnot-1). The unit binds HOST=0.0.0.0 PORT=3000, so the
211 # probe dials it on loopback; verified serving 200 on 2026-07-21. This is the
212 # node whose crash-loop `systemctl is-active` alone would have missed.
213 health_url = "http://127.0.0.1:3000/health"
214 # Companion: install the mnw-cli built in this same promote (see [[companion]] in
215 # sando-daemon.toml) after the server is up, and restart its unit. Closes the
216 # drift that broke git hosting during the 0.10.14 deploy — mnw-cli now ships from
217 # the same sha as the server. Needs the node-side wrapper + sudoers grant
218 # (deploy/install-companion.sh, deploy/mnw-companion.sudoers).
219 [[tier.node.companion]]
220 name = "mnw-cli"
221 install_path = "/opt/mnw-cli/mnw-cli"
222 service_name = "mnw-cli.service"
223 # Companion: multithreaded, same sha as the server, same host. This is what
224 # retires multithreaded/deploy/deploy-hetzner.sh (cross-compiled on macOS, scp'd
225 # to root@, 41 commits behind when found). mt now rides the whole ladder — native
226 # build on fw13, the migration_dry_run gate against its own dump, the test and
227 # lint gates, and the server's burn-in on testnot — instead of a laptop and an
228 # scp. Design: wiki sando-mt-pom-pipelines.
229 #
230 # prod-1 is the only node that installs it, because it is the only node that runs
231 # mt (see the note on testnot-1 above). It already has the wrapper and the
232 # sudoers grant, from mnw-cli.
233 [[tier.node.companion]]
234 name = "multithreaded"
235 install_path = "/opt/multithreaded/multithreaded"
236 service_name = "multithreaded.service"
237
238 # ---- C: prod-2 (declared, not yet provisioned) ----
239 [[tier]]
240 name = "c"
241 provisioned = false
242 canary = "sequential"
243 gates = [
244 { kind = "node_health" },
245 ]
246 # [[tier.node]] entries to be added when the second prod node ships.
247