Files
atheos-blog-lxc/aidlc-docs/requirements/requirements.md
T
hermes c2897ed660 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:44:11 +10:00

130 lines
8.9 KiB
Markdown

# 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 136` on 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* `blog` is
present as a project whose files I can open and edit.
- **AC-4** — *Given* the `blog` project 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 exact `git remote set-url` command 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/blog` cloned 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.