Public: anyone can read this; the crew can change it.
A box of the forge's own runs a restored snapshot as a service, beside production, taking no traffic #9008
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/8945-a-box-of-the-forge-s-own-runs-a-restored → main 1 commit fork
5 files
changed +129
−0
docs/EXTRACT.md +13
| @@ -118,3 +118,16 @@ The edge sign-in becomes the forge's own sign-in and grants. Zero-downtime | ||
| 118 | 118 | deploys become the forge's swap. Backups become the snapshot. TLS becomes |
| 119 | 119 | the proxy's. The crew, the studio, the contracts, the audit, the following — |
| 120 | 120 | all of it already lives in the forge and moves with the data. |
| 121 | ||
| 122 | ## The box beside production | |
| 123 | ||
| 124 | Steps 1–3 as files: `scripts/box-install.sh` (user, `/var/lib/forge`, | |
| 125 | `/etc/forge/forge.env`, checkout at `/opt/forge`, `bun install`, the unit) and | |
| 126 | `infra/forge-box/forge.service` (`Restart=always`, enabled at boot, so a | |
| 127 | killed process or a reboot brings it back). Then the lines of | |
| 128 | [](RESTORE.md) with `<data-dir>` = `/var/lib/forge` as the `forge` | |
| 129 | user, and `systemctl enable --now forge`. Nothing points at the box; compare | |
| 130 | repository and app counts with production, kill the process and reboot to see | |
| 131 | it return. Restoring reads the bucket by the card's lines only — no platform | |
| 132 | service is involved. `test/forge-box-unit.test.ts` parses the unit. | |
| 133 | Done on a machine: the drill itself (restore, counts, reboot) is the owner's to run. |
docs/RESTORE.md +4
| @@ -33,3 +33,7 @@ writing anything; a missing one restores without the key and lists the | ||
| 33 | 33 | sealed values to set again. To pick an older snapshot, put its name |
| 34 | 34 | (`2026-09-22T08-00-00Z`) where line 5 says `latest`. Details: |
| 35 | 35 | [](SNAPSHOT.md). |
| 36 | ||
| 37 | On a box that stays: `scripts/box-install.sh`, then lines 3–6 as the `forge` | |
| 38 | user with `<data-dir>` `/var/lib/forge`, then `systemctl enable --now forge` | |
| 39 | in place of line 7 ([](EXTRACT.md), _The box beside production_). |
infra/forge-box/forge.service added +22
| @@ -0,0 +1,22 @@ | ||
| 1 | [Unit] | |
| 2 | Description=The forge, restored from a snapshot, beside production | |
| 3 | After=network-online.target | |
| 4 | Wants=network-online.target | |
| 5 | StartLimitIntervalSec=0 | |
| 6 | ||
| 7 | [Service] | |
| 8 | Type=simple | |
| 9 | User=forge | |
| 10 | Group=forge | |
| 11 | WorkingDirectory=/opt/forge | |
| 12 | EnvironmentFile=/etc/forge/forge.env | |
| 13 | Environment=FORGE_DATA=/var/lib/forge | |
| 14 | ExecStart=/usr/local/bin/bun run src/main.ts | |
| 15 | Restart=always | |
| 16 | RestartSec=2 | |
| 17 | NoNewPrivileges=true | |
| 18 | ProtectSystem=full | |
| 19 | ReadWritePaths=/var/lib/forge | |
| 20 | ||
| 21 | [Install] | |
| 22 | WantedBy=multi-user.target |
scripts/box-install.sh added +39
| @@ -0,0 +1,39 @@ | ||
| 1 | #!/usr/bin/env bash | |
| 2 | # Install the forge on a box as a systemd service (docs/EXTRACT.md steps 1-3). | |
| 3 | # Run as root from a checkout: scripts/box-install.sh [/opt/forge] | |
| 4 | # It does not restore and does not start the forge; docs/RESTORE.md lines 3-6 | |
| 5 | # fill /var/lib/forge, then `systemctl enable --now forge`. | |
| 6 | set -euo pipefail | |
| 7 | ||
| 8 | src="$(cd "/.." && pwd)" | |
| 9 | dest="" | |
| 10 | ||
| 11 | [ "$(id -u)" = 0 ] || { echo "run as root" >&2; exit 1; } | |
| 12 | for bin in bun git; do | |
| 13 | command -v "" >/dev/null || { echo "missing binary: " >&2; exit 1; } | |
| 14 | done | |
| 15 | [ -x /usr/local/bin/bun ] || ln -s "$(command -v bun)" /usr/local/bin/bun | |
| 16 | ||
| 17 | id forge >/dev/null 2>&1 || useradd --system --home /var/lib/forge --shell /usr/sbin/nologin forge | |
| 18 | install -d -o forge -g forge -m 700 /var/lib/forge | |
| 19 | install -d -m 755 /etc/forge | |
| 20 | if [ ! -e /etc/forge/forge.env ]; then | |
| 21 | umask 077 | |
| 22 | cat > /etc/forge/forge.env << | |
| 23 | # FORGE_URL=https://forge.example | |
| 24 | # FORGE_APP_DOMAIN=forge.example | |
| 25 | # FORGE_SNAPSHOT_PASSPHRASE= | |
| 26 | # S3_ENDPOINT= S3_BUCKET= S3_ACCESS_KEY= S3_SECRET_KEY= S3_REGION= | |
| 27 | ||
| 28 | fi | |
| 29 | ||
| 30 | if [ "" != "" ]; then | |
| 31 | mkdir -p "" | |
| 32 | cp -a "/." "/" | |
| 33 | fi | |
| 34 | chown -R forge:forge "" | |
| 35 | (cd "" && runuser -u forge -- bun install --frozen-lockfile) | |
| 36 | ||
| 37 | install -m 644 "/infra/forge-box/forge.service" /etc/systemd/system/forge.service | |
| 38 | systemctl daemon-reload | |
| 39 | echo "installed; restore into /var/lib/forge (docs/RESTORE.md), then: systemctl enable --now forge" |
test/forge-box-unit.test.ts added +51
| @@ -0,0 +1,51 @@ | ||
| 1 | /** The box's systemd unit (`infra/forge-box/forge.service`) says what docs/EXTRACT.md step 3 says. */ | |
| 2 | import { expect, test } from "bun:test"; | |
| 3 | import { readFileSync } from "node:fs"; | |
| 4 | import { join } from "node:path"; | |
| 5 | ||
| 6 | const root = join(import.meta.dir, ".."); | |
| 7 | ||
| 8 | function parse(text: string): Map<string, Map<string, string[]>> { | |
| 9 | const out = new Map<string, Map<string, string[]>>(); | |
| 10 | let sec: Map<string, string[]> | undefined; | |
| 11 | for (const raw of text.split("\n")) { | |
| 12 | const line = raw.trim(); | |
| 13 | if (!line || line.startsWith("#")) continue; | |
| 14 | const h = line.match(/^\[(\w+)\]$/); | |
| 15 | if (h) { | |
| 16 | sec = new Map(); | |
| 17 | out.set(h[1]!, sec); | |
| 18 | continue; | |
| 19 | } | |
| 20 | const kv = line.match(/^(\w+)=(.*)$/); | |
| 21 | if (!kv || !sec) throw new Error(`unparsable unit line: ${line}`); | |
| 22 | sec.set(kv[1]!, [...(sec.get(kv[1]!) ?? []), kv[2]!]); | |
| 23 | } | |
| 24 | return out; | |
| 25 | } | |
| 26 | ||
| 27 | const unit = parse(readFileSync(join(root, "infra/forge-box/forge.service"), "utf8")); | |
| 28 | const svc = unit.get("Service")!; | |
| 29 | ||
| 30 | test("the unit runs the forge from /opt/forge and always restarts", () => { | |
| 31 | expect(svc.get("ExecStart")).toEqual(["/usr/local/bin/bun run src/main.ts"]); | |
| 32 | expect(svc.get("WorkingDirectory")).toEqual(["/opt/forge"]); | |
| 33 | expect(svc.get("Restart")).toEqual(["always"]); | |
| 34 | expect(svc.get("Environment")).toContain("FORGE_DATA=/var/lib/forge"); | |
| 35 | expect(svc.get("User")).toEqual(["forge"]); | |
| 36 | }); | |
| 37 | ||
| 38 | test("the unit starts at boot and never gives up restarting", () => { | |
| 39 | expect(unit.get("Install")!.get("WantedBy")).toEqual(["multi-user.target"]); | |
| 40 | expect(unit.get("Unit")!.get("StartLimitIntervalSec")).toEqual(["0"]); | |
| 41 | }); | |
| 42 | ||
| 43 | test("the install script places the unit, keeps no secret, and is executable", () => { | |
| 44 | const path = join(root, "scripts/box-install.sh"); | |
| 45 | const sh = readFileSync(path, "utf8"); | |
| 46 | expect(sh).toContain("/etc/systemd/system/forge.service"); | |
| 47 | expect(sh).toContain("infra/forge-box/forge.service"); | |
| 48 | expect(sh).toContain("set -euo pipefail"); | |
| 49 | expect(sh).not.toMatch(/PASSPHRASE=\S/); | |
| 50 | expect(require("node:fs").statSync(path).mode & 0o111).toBeGreaterThan(0); | |
| 51 | }); |
new above
offline