diff --git a/infra/deploy/forgejo/README.md b/infra/deploy/forgejo/README.md index fbd5b76..461f1ad 100644 --- a/infra/deploy/forgejo/README.md +++ b/infra/deploy/forgejo/README.md @@ -177,14 +177,48 @@ parallel, and even capacity 3 (an earlier, undocumented hand-tune) leaves `build-backend`/`build-frontend`/`validate-observability` queued behind those three before they get a slot. -**Adding more runner capacity should mean raising this number, or adding a -second runner alongside it — not putting a runner on prod or beta (vps2).** -`container.docker_host: automount` gives job containers the *host's* Docker -socket; on vps2 that would mean any CI job has root-equivalent access to both -the prod and beta stacks running there. An earlier revision of this stack -actually ran the runner as a Swarm-hosted container on the box Forgejo was -pinned to and was deliberately reverted for this same class of reason — see -the note at the top of `docker-stack.yml`. +**This paragraph used to say a runner must never go on vps2. That was reversed +deliberately on 2026-08-01, and the reasoning is worth keeping rather than +quietly deleting.** + +The original objection stands on its merits: `container.docker_host: automount` +gives job containers the *host's* Docker socket, so a runner on vps2 means any +CI job has root-equivalent access to both the prod and beta stacks running +there. An earlier revision of this stack ran the runner as a Swarm-hosted +container on the Forgejo node and was reverted for the same class of reason. + +What changed is that the alternative turned out to be worse. The desktop was +the estate's **only** registered runner, and when it went offline on +2026-07-31 nothing merged, nothing deployed, and the nightly backup did not +fire for 21 hours — on a repo where every branch is protected and every change +is a PR, a single absent runner freezes the whole estate. Availability of CI +is not a convenience here; it is what the backup and the deploy path both +hang off. + +So the runner on vps2 (`runner-vps2/`) is a considered trade, not an +oversight, and it is bounded rather than assumed benign: + +- `capacity: 1` — one job at a time, so a CI burst cannot contend with + `thermograph_web` for the box's six cores. +- `--cpus=2 --memory=4g` on job containers, set in `container.options`. Job + containers are siblings of the runner rather than children, so the runner's + own compose limits do not reach them; that option is the only lever that + does. +- `valid_volumes: []` — a job cannot bind-mount a host path, which is the + difference between "a job can read `/etc/thermograph.env`" and "a job + cannot", on the box where that file is prod's. +- The compose project joins no `thermograph` network. + +Be honest about what that buys: it is defence against **accident**, not +against a hostile workflow author. Forgejo's own database on vps1 already +stores `VPS2_SSH_KEY`, which is root on vps2, so the material was already +reachable — what changed is that it is now reachable by a *job* rather than +only at rest. On a two-person estate where both people can already SSH to that +box as root, that is the honest boundary. + +**If you are adding capacity rather than redundancy, raise `capacity` or add a +runner on a box that hosts nothing.** The vps2 runner exists so that CI has a +second home, not because prod is a good place to run CI. ## Custom CI job image (`ci-runner/`)