Files
hermes a6fb61d0ea feat: provision Alpine LXC 136 running Atheos, wired to Gitea
Provisions a LAN-only browser code editor (Atheos) in an Alpine 3.23 LXC on the
scotty PVE node, with armistace/blog cloned and ready to edit. Built via the
AI-DLC workflow; the full process trail is in aidlc-docs/.

Deliverables
- scripts/provision.sh          rebuild the guest from scratch (re-runnable)
- scripts/update-atheos.sh      move Atheos to latest upstream and RE-PIN
- scripts/build-and-test.sh     captures evidence for every acceptance criterion
- files/atheos.conf             nginx vhost (socket path byte-matches the FPM pool)
- files/ssh-config              ~atheos/.ssh/config deploy-key alias
- RUNBOOK.md                    operational guide (START AT STEP 1)
- docs/security-notes.md        credential model + wider-web hardening TODOs
- docs/resiliency-notes.md      what a non-PoC deployment would need

Design decisions
- D1 no git identity is configured by the agent; the operator sets it. Git will
  refuse the first commit until then, which is deliberate (requirement FR-11).
- D2 Atheos pinned to commit f90cd18 (build 611) with a verified tarball sha256,
  plus update-atheos.sh as the deliberate escape hatch.
- D3 nginx :80, web root /var/www/atheos, php-fpm over a unix socket.
- D6 git auth is a per-repository DEPLOY KEY scoped to armistace/blog, not a
  personal access token (a PAT's write:repository scope reaches every repo the
  account can write).

Verified on the live guest (40 checks, 0 failures)
- AC-2  Atheos served over HTTP 200 by nginx+php-fpm; socket paths byte-match
- AC-3  20 posts + themes/cleanblog present; owned by the php-fpm user
- AC-4  all five writable paths writable as the service user
- AC-5  remote targets armistace/blog; deploy key authenticates over SSH
- AC-6  survival proven with a real container restart, marker byte-identical
- AC-8  no python/pelican/pip/make and no DB engine - scope boundary enforced
- FR-11 git identity is empty; deploy key mode 600 owned by the service user

Traps documented in the RUNBOOK (each cost real debugging time)
- The Gitea SSH user is `gitea`, NOT `git` (app.ini SSH_USER=gitea). With `git`,
  sshd answers "Invalid user git" and rejects before reading authorized_keys,
  which is indistinguishable from a bad key.
- The Gitea SSH endpoint is the LAN address (:22 is not forwarded publicly).
- `uptime` and `free` report the PVE HOST inside an LXC without lxcfs, so a
  container that just restarted can claim days of uptime.
2026-09-29 15:50:34 +10:00

2.2 KiB

Unit of Work Dependency — atheos-blog-lxc

Stage: INCEPTION — Units Generation (Part 2)

Dependency matrix

Rows depend on columns.

U1 Guest U2 Web stack U3 Atheos U4 Git+clone U5 Docs
U1 Guest — — — — —
U2 Web stack ✔ — — — —
U3 Atheos ✔ ✔ — — —
U4 Git+clone ✔ (git) — ✔ (workspace) — —
U5 Docs ✔ ✔ ✔ ✔ —

Build order

U1  ──►  U2  ──►  U3  ──►  U4  ──►  U5
 │                 ▲        │
 └─────────────────┴────────┘
      (U4 needs git from U1)

Critical path: U1 → U2 → U3 → U4 → U5. Strictly sequential — no parallelism worth the coordination cost at this size. Each unit already includes a verification step, so a failure is caught at unit granularity rather than at the end.

Why these boundaries

Boundary Reason
U1 before everything nothing can be installed without the guest
U2 (nginx+php) is one unit, not two the socket contract (I-1) spans both — splitting them would create a boundary through a single interface, which is exactly where integration bugs live
U3 after U2 Atheos has nothing to run on until PHP exists
U4 separate from U3 different failure class: U3 is PHP-runtime risk (C-1); U4 is VCS/permissions risk. Separating them keeps a U3 failure from disguising itself as "git is broken"
U4 before U5 the RUNBOOK must document the real remote command, which only exists after the clone
U5 last docs describe what was actually built

Cross-unit risks

Risk Units Note
FPM user identity U2 → U3 → U4 U2 resolves the run user; U3 sets ownership; U4 owns the key. All three must agree on the same user — a mismatch yields "dubious ownership" (I-3)
FPM socket path U2 (internal) must match across nginx + pool
Pinned Atheos SHA U3 → U5 U5's update script must re-pin the same mechanism
Remote URL U4 → U5 U5 documents it; U4 must not change it to SSH