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.
This commit is contained in:
2026-09-29 15:44:11 +10:00
commit c2897ed660
26 changed files with 2333 additions and 0 deletions
@@ -0,0 +1,76 @@
# Application Design (consolidated) — atheos-blog-lxc
**Stage**: INCEPTION — Application Design (minimal depth) · **Date**: 2026-09-29
**Deploy target**: Proxmox LXC `136` on node `scotty` (.69) — Alpine 3.23 · LAN-only
---
## 1. What this system is
A small, single-purpose guest whose only job is to put a **browser editor** in front of the
`armistace/blog` working copy. It is deliberately **not** a build environment.
```
┌───────────────────────── LXC 136 (scotty) ─────────────────────────┐
Browser (LAN) │ │
http://<ip>/ ────┼──► nginx :80 ──unix socket──► php-fpm (php83) ──► Atheos (PHP) │
│ │ │
│ ├──► workspace/blog/** (rw) │
│ └──► git CLI ──► local commit│
└────────────────────────────────────────────────────────────────────┘
│
(operator, later) SSH identity + SSH remote
▼
Gitea git.aridgwayweb.com
```
## 2. Components
| ID | Component | Role |
|---|---|---|
| C1 | LXC `136` on scotty, Alpine 3.23 | isolated runtime, OpenRC, 20 GB root, onboot |
| C2 | nginx :80 | HTTP front end; sends `*.php` to C3 |
| C3 | php-fpm `php83` | PHP runtime; ZIP/OPENSSL/MBSTRING; `allow_url_fopen=On` |
| C4 | Atheos (pinned SHA) | the editor; git via its `codegit` component |
| C5 | `/var/www/atheos/workspace` | project storage (writable) |
| C6 | git CLI | VCS; `safe.directory` set; **no identity** (operator-owned) |
| C7 | `blog` working copy | clone of `armistace/blog` on `master` |
| C8 | Gitea (external) | clone source; agent never pushes |
| C9 | Operator (external) | owns SSH key, git identity, Atheos admin, remote switch |
## 3. Key interfaces
1. **nginx → php-fpm**: `fastcgi_pass unix:<FPM_SOCKET>` must equal the pool's `listen =`. Mismatch ⇒ **502**.
2. **Atheos → filesystem**: writable `config.php`, `data/`, `workspace/`, `plugins/`, `themes/`. Failure ⇒ installer dependency error.
3. **Atheos → git**: needs `git` on PATH **and** `safe.directory` for the workspace (else *dubious ownership*). Identity intentionally unset.
4. **git → Gitea**: HTTPS clone (public); operator later switches to SSH.
5. **OpenRC**: `nginx` + `php-fpm83` enabled at `default` runlevel — otherwise first reboot breaks service (AC-6).
## 4. Design decisions (from the gate)
| # | Decision | Source |
|---|---|---|
| D1 | **No git identity configured by the agent.** `user.name`/`user.email` left unset; RUNBOOK instructs the operator to set them first. | Q1=B |
| D2 | **Atheos pinned to a resolved commit SHA**, with an additional **update script** that re-clones at latest and re-pins. | Q2 |
| D3 | Web root `/var/www/atheos`; workspace `/var/www/atheos/workspace`; nginx :80; php-fpm via unix socket. | Q3 |
| D4 | Atheos served at the **site root**. | assumed, accepted |
| D5 | Operator completes Atheos's first-run installer; **agent seeds no users**. | FR-11 / Q2 of requirements |
| **D6** | **Credential = a per-repository Gitea deploy key scoped to `armistace/blog`, write-enabled** (`read_only:false`), owned by the php-fpm user, aliased via `~/.ssh/config` with `IdentitiesOnly yes`. **Not** a PAT (account-wide scopes) and **not** a dedicated Gitea user (no blast-radius reduction for `blog`). Private key generated on the guest; public half handed to the operator to add in-repo. | Andrew, 2026-09-29 (security review) |
## 5. Scope guards (each is a testable negative)
- No database installed
- **No Python / pelican / `make`** — the presence of Python is a defect (AC-8)
- No NPM proxy host / DNS / port-forward (FR-10)
- No `armistace` credential, SSH key, or git identity created by the agent (FR-11)
## 6. Design-level risks handed to Infrastructure Design
- **C-1** Atheos on Alpine is under-documented → resolve the FPM socket and extension package names from the *installed* system, not from upstream docs.
- **C-2** OpenRC, not systemd → all service work uses `rc-service` / `rc-update`.
- **C-3** Alpine template must be downloaded on scotty first (`pveam update` + `download`).
- **C-4** The installer needs a writable web root before it will run.
## 7. Unit mapping
U1 guest (C1) · U2 web stack (C2,C3) · U3 Atheos (C4,C5) · U4 git + clone (C6,C7) · U5 docs + script (all)