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.
8.9 KiB
Requirements — atheos-blog-lxc
Project: Alpine LXC running Atheos, wired to Gitea, with armistace/blog cloned and ready to edit.
Stage: INCEPTION — Requirements Analysis
Date: 2026-09-29
Deploy Target: Proxmox LXC on node scotty (.69). Not the K3s cluster.
1. Intent Analysis Summary
| User request | An Alpine LXC that (1) installs Atheos with nginx+PHP, (2) sets up git integration, (3) has git.aridgwayweb.com/armistace/blog cloned and ready to use, (4) leaves SSH key setup to the operator, (5) is backed by a 20 GB root. |
| Request type | New Project (greenfield repo; provisioning a new guest) |
| Scope estimate | Multiple components — guest creation, web stack, web app, VCS wiring, docs |
| Complexity estimate | Moderate — no application code to write; the risk is in correct provisioning and in not fighting Atheos's install model |
The one finding that shaped this
armistace/blog is a Pelican static site, not plain markdown: it has pelicanconf.py,
Makefile, publishconf.py and themes/cleanblog. Building/publishing needs Python + pelican,
which Atheos (PHP) cannot do. Andrew confirmed (Q1=A) that the existing pipeline already handles
the build and that "just writing markdown is more than enough" — so the build is explicitly out
of scope. This box is an editor + git client, nothing more.
2. Functional Requirements
| ID | Requirement | Source |
|---|---|---|
| FR-1 | An Alpine 3.23 LXC is created on PVE node scotty (.69) with a 20 GB rootfs, 2 vCPU and 1 GB RAM, onboot=1, unprivileged. |
user point 5 + Q3 |
| FR-2 | nginx + PHP-FPM are installed and configured to serve Atheos; PHP includes the extensions Atheos wants for its plugin marketplace (ZIP, OPENSSL, MBSTRING) and allow_url_fopen=On. |
user point 1 |
| FR-3 | Atheos (github.com/Atheos/Atheos) is installed into the web root, pinned to a specific commit, with config.php, data/, workspace/, plugins/ and themes/ writable by the PHP-FPM user. No database is required. |
user point 1 |
| FR-4 | Atheos's git integration is configured and usable: git installed, a safe.directory set for the workspace, and Atheos's own git settings present (committer identity, default branch). | user point 2 |
| FR-5 | armistace/blog is cloned into workspace/ on its default branch (master) so it appears as an Atheos project, ready to open and edit. |
user points 3, 6 |
| FR-6 | The clone is read-from-Gitea ready, and the repo is left with instructions to switch the remote to SSH once the operator adds their key. The agent must not switch the remote nor install credentials itself. | Q2 + Q6 |
| FR-7 | A RUNBOOK.md documents: the container's IP, how to log in over SSH, where the workspace/project lives, exactly how to move to an SSH remote (git remote set-url) after adding the key, plus credential/identity setup and common ops. |
Q2 + Q6 + standing preference |
| FR-8 | A README.md documents what this is, the install method, and the one-time provisioning steps. | standing preference |
| FR-9 | The project repo (hermes/atheos-blog-lxc) holds a repeatable provisioning script so the whole LXC can be re-created from scratch. |
AI-DLC / standing preference |
| FR-10 | Reachability is LAN-only: Atheos served on the LXC's own IP on port 80. No NPM proxy host, no DNS record, no router port-forward is created. | Q4 |
| FR-11 | The agent does not create or configure armistace credentials or git identity. The operator performs that over SSH, under their own identity. |
Q2 (explicit restriction) |
3. Non-Functional Requirements
| ID | Requirement |
|---|---|
| NFR-1 | Rootfs is 20 GB and must stay comfortably within it (Atheos + nginx + PHP + git + the blog repo is well under 1 GB). |
| NFR-2 | The container must survive a reboot and return serving Atheos with the clone still present and functional (verify, don't assume). |
| NFR-3 | Provisioning must be repeatable and idempotent-friendly: a single documented script plus manual steps. |
| NFR-4 | The guest must not depend on the K3s cluster; it is a standalone LXC. |
| NFR-5 | Timezone should match the operator (Australia/Brisbane) so Atheos/git timestamps are sane. |
4. Explicitly Out of Scope (confirmed by the operator)
- Building/publishing the Pelican site. No Python, pelican,
make html, or preview server on this box (Q1=A). The existing pipeline handles the build. - SSH key creation/installation for
armistace. Operator does this (Q2, user point 4). - Any NPM / public exposure (Q4=LAN only).
- Atheos user accounts beyond the initial install. The operator will create their own login during Atheos's first-run installer.
5. Constraints & Risks
| ID | Constraint / risk | Mitigation |
|---|---|---|
| C-1 | 🔴 Atheos on Alpine is off the beaten path. Upstream only documents Ubuntu/CentOS/Apache/nginx. Alpine's PHP packaging and paths differ. | Verify the package set on Alpine 3.23 before installing (php83 + php83-fpm + php83-session + php83-zip + php83-openssl + php83-mbstring). Confirm the FPM socket path and nginx fastcgi_pass match. |
| C-2 | 🔴 Alpine is OpenRC, not systemd. Service management is rc-service/rc-update. |
Provision with OpenRC idioms; do not emit systemd units. |
| C-3 | 🔴 The Alpine template is not yet downloaded on scotty. | pveam update + pveam download local alpine-3.23-default_... before pct create. |
| C-4 | 🟠 Atheos first-run installer needs the web root writable and will create a config + admin user. | Set ownership/permissions correctly; document that the operator completes the installer in the browser. |
| C-5 | 🟠 git clone of an internet-facing Gitea from a new guest — the clone is public (repo is public), so no credential is needed for the clone. |
Confirm the repo stays public; clone over the public URL. If it ever goes private, the RUNBOOK covers adding a credential. |
| C-6 | 🟡 Agent must respect the no-armistace-credentials restriction. | FR-11; state it plainly in the RUNBOOK so a future agent does not "helpfully" set it. |
6. Extension Configuration
| Extension | Enabled | Decided At |
|---|---|---|
| security-baseline | ❌ No | Requirements Analysis (Q5=A) |
| resiliency-baseline | ❌ No | Requirements Analysis (Q5=A) |
| property-based-testing | ❌ No | Requirements Analysis (Q5=A) |
Per the standing preference, the opted-out concerns are not dropped — they are written up as
"what to do if this ever moves external" in docs/security-notes.md and docs/resiliency-notes.md
(see FR-12 below).
| ID | Requirement | Source |
|---|---|---|
| FR-12 | Even with both extensions opted out, produce docs/security-notes.md and docs/resiliency-notes.md listing the concrete hardening TODOs and their code touch-points, and cross-reference them from the README. |
standing preference |
7. Acceptance Criteria (Given/When/Then)
- AC-1 — Given the LXC is created, when I run
pct config 136on scotty, then it shows Alpine 3.23, rootfs 20 GB, and it is onboot-enabled. - AC-2 — Given provisioning is complete, when I open
http://<lxc-ip>/from the LAN, then Atheos's first-run installer (or login) is served by nginx/PHP — not a 502/404. - AC-3 — Given I complete the Atheos installer, when I open the workspace, then
blogis present as a project whose files I can open and edit. - AC-4 — Given the
blogproject is open, when I edit a markdown file and save, then the change persists to disk inside the container. - AC-5 — Given the clone, when I run
git -C workspace/blog remote -v, then it points at Gitea (initially HTTP), and the RUNBOOK gives the exactgit remote set-urlcommand to move to SSH. - AC-6 — Given the container is rebooted, when it comes back, then Atheos serves again and the clone and any edits are still present.
- AC-7 — Given the RUNBOOK, when a different agent picks the repo up, then they can re-provision or hand off without prior context.
- AC-8 — Given the build is out of scope, when I search the container, then no Python/pelican build tooling is installed (proving scope was respected).
8. Key Requirements Summary
- One small Alpine LXC (136) on scotty, 20 GB root, serving Atheos over nginx+PHP on the LAN.
- Atheos is an editor + git client here — deliberately not a build environment.
armistace/blogcloned into Atheos's workspace, ready to write in.- Operator-owned credentials: the agent sets up the clone and the instructions; the operator adds the SSH key and, later, switches the remote.
- RUNBOOK + README + re-provision script, because a later agent or a re-build must not need me.