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.
9.9 KiB
RUNBOOK — atheos-blog-lxc
Atheos browser editor for armistace/blog, running in an Alpine LXC on Proxmox.
| Guest | LXC 136 on node scotty (192.168.178.69) |
| IP | http://192.168.178.57/ |
| OS | Alpine 3.23.2 · unprivileged · 2 vCPU · 1 GB RAM · 20 GB root · onboot=1 |
| App | Atheos IDE, pinned to commit f90cd1806dbe0e25673a77e27390e8b1a240c3f6 (build 611) |
| Repo | git.aridgwayweb.com/hermes/atheos-blog-lxc (this provisioning repo) |
| Workspace | /var/www/atheos/workspace/blog → clone of armistace/blog |
| Access | LAN only. No NPM proxy host, no DNS record, no port-forward. |
⚠️ STEP 1 — DO THIS FIRST: set your git identity
The agent deliberately did not configure a git identity (design decision D1, requirement FR-11). Git will refuse to commit until you set one. This is your first action after SSH-ing in.
ssh root@192.168.178.57
# become the user that runs PHP and owns the workspace
su -s /bin/sh atheos
# set YOUR identity (these values are placeholders - use your real ones)
git config --global user.name "Andrew Ridgway"
git config --global user.email "ar17787@gmail.com"
# verify
git config --global --get user.name
git config --global --get user.email
Why you and not the agent: you own the
armistaceidentity. Configuring it under your name programmatically would impersonate you and blur commit attribution.
The same identity must be set for the deploy key's user, which is the same account (atheos),
so the single su above covers both the editor's commits and anything you do by hand.
STEP 2 — install the deploy key (Phase 2 prerequisite)
The guest already has a per-repo deploy keypair. Only the public half is shared:
# print it again if you need it
cat /home/atheos/.ssh/id_ed25519_blog.pub
Add it in Gitea:
- Open
armistace/blog→ Settings → Deploy Keys → Add Deploy Key - Title:
atheos-blog-lxc - Paste the public key
- ✅ TICK "Allow write access" — required, otherwise push is rejected
Public key (as generated 2026-09-29):
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINj03Tep9pULTvVDCC3q1ZtocKTZSmDhPsB61Zic1SPk atheos-blog-lxc deploy key (armistace/blog)
Fingerprint to confirm you pasted the right one:
SHA256:qHvsjjSX79aPZRoNTRI4j+HH3hArAuINIRu9ewxx7t8 (ED25519)
STEP 3 — switch the remote to SSH (Phase 2)
Once the key is installed, tell the agent (or run it yourself):
su -s /bin/sh atheos -c \
'git -C /var/www/atheos/workspace/blog remote set-url origin gitea-blog:armistace/blog.git'
# verify the transport actually authenticates
su -s /bin/sh atheos -c 'ssh -T gitea-blog' # expect a Gitea greeting, not "Permission denied"
su -s /bin/sh atheos -c 'git -C /var/www/atheos/workspace/blog fetch --dry-run'
Why the remote uses gitea-blog and not a hostname
gitea-blog is an SSH config alias defined in /home/atheos/.ssh/config:
Host gitea-blog
HostName 192.168.178.155 # LAN address
User gitea # <-- NOT `git`! see the trap below
IdentityFile ~/.ssh/id_ed25519_blog
IdentitiesOnly yes
Port 22
⚠️ TRAP: the SSH user is
gitea, notgitEvery GitHub/Gitea tutorial uses
git@host. This instance does not. Itsapp.inisetsSSH_USER = gitea.If you use
User git, sshd repliesInvalid user gitand rejects the connection before it ever readsauthorized_keys— so the error looks exactly like a rejected key and sends you hunting for a problem that isn't there. Confirmed in the sshd log on LXC 109:Invalid user git from 192.168.178.57 port 39780 Connection closed by invalid user git 192.168.178.57 [preauth]Correct user →
Hi there! You've successfully authenticated with the deploy key named atheos-blog-lxc.
Two reasons for the alias:
-
IdentitiesOnly yesmeans no other key is ever offered — a straygit pushcannot authenticate with a different (possibly broader) identity. -
It's the LAN address because that's the only path that works. Verified 2026-09-29:
Endpoint Result git.aridgwayweb.com:22unreachable — the public path does not forward :22 (only :443 via NPM) 192.168.178.155:22OPEN — Gitea's app.inisetsSSH_DOMAIN = 192.168.178.155,SSH_PORT = 22,START_SSH_SERVER = falseSo Gitea serves SSH from the LXC's own
sshdand advertises the LAN address. Since this guest is LAN-only, LAN transport is also the correct choice — no internet round-trip.
STEP 4 — complete the Atheos installer
Open http://192.168.178.57/ in a browser. The Atheos first-run installer appears and creates the admin user — this is yours to own; the agent seeded no accounts.
Verify afterwards:
- you can open the Workspace and see a
blogproject - opening
src/content/*.mdshows your posts - saving persists (the files are at
/var/www/atheos/workspace/blog)
Atheos git settings
Open Atheos → (user menu) → Settings to confirm the git integration. The codegit component
handles commit/push. If you see "detected dubious ownership", the safe.directory entries are
missing — restore them with:
su -s /bin/sh atheos -c 'git config --global --add safe.directory /var/www/atheos/workspace'
su -s /bin/sh atheos -c 'git config --global --add safe.directory /var/www/atheos/workspace/blog'
Day-to-day operations
Edit and commit
Do everything in the browser at http://192.168.178.57/. Commit via Atheos's git panel, or by hand:
ssh root@192.168.178.57
su -s /bin/sh atheos
cd /var/www/atheos/workspace/blog
git status
git add -A && git commit -m "post: <what changed>"
git push
The build is NOT here
This box only writes markdown. Building/publishing the Pelican site happens in the existing
pipeline elsewhere. There is deliberately no Python, no pelican, no make on this guest.
Services (OpenRC — not systemd)
rc-service nginx status
rc-service php-fpm83 status
rc-update show | grep -E 'nginx|php-fpm83' # both must be in 'default'
Logs
tail -f /var/log/nginx/atheos.error.log # PHP fatals surface here
tail -f /var/log/nginx/atheos.access.log
Reboot / recovery
pct reboot 136 # from scotty, or
pct stop 136 && pct start 136
Services are enabled at the OpenRC default runlevel, so they return automatically
(verified — see aidlc-docs acceptance criteria AC-6).
Rebuild from scratch
The whole guest is reproducible:
# on scotty (192.168.178.69), as root
pct destroy 136
sh scripts/provision.sh # from this repo, on the node
Nothing unique lives on the guest: the blog's source of truth is Gitea. Only uncommitted edits would be lost — commit often.
Update Atheos (and re-pin)
Atheos has no releases, so this install is pinned to a commit. To move to the latest upstream commit, install it, and re-pin:
sh scripts/update-atheos.sh # on the guest; preserves workspace/, backs up, verifies, re-pins
Security model (summary — full detail in docs/security-notes.md)
- Git auth = a per-repository deploy key, scoped to
armistace/blog, write-enabled. Not a PAT (whosewrite:repositoryscope reaches every repo the account can write). - Private key:
/home/atheos/.ssh/id_ed25519_blog, mode600, owned by the php-fpm user. - The web app and the key share one unprivileged user, so a compromised Atheos can write
blog. That is the accepted floor — one repo, and (with branch protection) one branch. - LAN-only. If this is ever exposed: keep branch protection on
blog'smaster, terminate TLS at NPM, and put authentication in front of Atheos. Seedocs/security-notes.md§2 (S-1…S-14).
Reaching the guest when SSH from your laptop fails
The guest has root SSH enabled with your fleet keys installed:
ssh-rsa ... aridgway@strategenics-25ssh-ed25519 ... oct2025
If those keys aren't what you're using, add/push a key via the PVE node (which always works):
ssh root@192.168.178.69 "pct exec 136 -- sh -c 'echo \"<your-pubkey>\" >> /root/.ssh/authorized_keys'"
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| 502 Bad Gateway | nginx fastcgi_pass and the FPM pool listen disagree |
grep -E '^listen' /etc/php83/php-fpm.d/www.conf and compare with the fastcgi_pass line in /etc/nginx/http.d/atheos.conf. Both must read /run/php-fpm83/php-fpm.sock. Then restart both services. |
| "detected dubious ownership" | safe.directory missing or the repo is owned by a different user |
Re-add the two safe.directory entries (STEP 4) |
| git commit refuses: "please tell me who you are" | identity not set — expected on a fresh build | STEP 1 |
| git push: Permission denied (publickey) | deploy key not installed, or the SSH user is wrong, or "Allow write access" not ticked | Check all three. Especially: grep User /home/atheos/.ssh/config must say gitea, not git (see STEP 3 trap). On the Gitea host, confirm with journalctl -u ssh | tail — Invalid user git means the username is wrong, not the key. |
| push times out | remote still pointing at git.aridgwayweb.com (:22 not forwarded) |
STEP 3 — use the gitea-blog alias |
| Atheos shows a blank page / 500 | PHP fatal | tail -50 /var/log/nginx/atheos.error.log |
| Atheos installer says a path isn't writable | ownership broken | chown -R atheos:atheos /var/www/atheos |
| PHP session errors | rootfs full | df -h / (the rootfs is 20 GB and the app is tiny, so this indicates something else) |
Change log
| Date | Change |
|---|---|
| 2026-09-29 | Initial build: LXC 136 on scotty, Alpine 3.23.2, nginx + php83-fpm (socket), Atheos pinned at f90cd18 (build 611), armistace/blog cloned over HTTPS, per-repo deploy key generated. LAN-only. |