hermes c4a4de1da7 fix: rDelete fataled on musl (GLOB_BRACE) - broke ALL deletion and plugin removal
Reported as "deleting terminal just cycles in the ui". Root cause is much larger than the
plugin: it was a platform bug that broke deletion everywhere.

CAUSE
  traits/helpers.php::rDelete() did:
      glob($target . "{*,.[!.]*,..?*}", GLOB_BRACE|GLOB_MARK);
  GLOB_BRACE is a GLIBC extension. Alpine is musl, where the constant is undefined,
  and referencing an undefined constant is a FATAL in PHP 8:
      PHP Fatal error: Uncaught Error: Undefined constant "GLOB_BRACE"
      in traits/helpers.php:138
      #0 components/market/class.market.php(178): Common::rDelete()
      #1 components/market/controller.php(46): Market->remove()
  The fatal kills the request before Common::send(200), so the front end never gets a
  settle response and the UI spins forever. A hang, not an error - which is why it
  presented as "cycles in the ui".

BLAST RADIUS (measured, two call sites)
  components/market/class.market.php:178   removing a plugin from the market
  components/filetree/class.filetree.php:59  DELETING ANY FILE OR FOLDER IN THE EDITOR
  The second is core functionality, so this was never a plugin bug - installing the
  Terminal plugin is just what made it visible.

FIX
  patches/helpers-rDelete-GLOB_BRACE.diff - expand the brace pattern by hand when
  GLOB_BRACE is undefined, keeping upstream behaviour where it is defined. Verified:
  nested dirs + dotfiles + single files all delete correctly now.
  Upstream has NOT fixed this (checked main) so it cannot be resolved by updating.

PLUGIN REMOVED
  Once deletion worked, removal via the market succeeded and the plugin is now gone:
    {"text":"Plugin successfully removed.","status":200}
  That is the right end state - a browser shell was the largest attack surface, and
  macros already provide command execution. Removed through the market's own code path
  so the plugin list, addons cache and bundle stayed consistent.

TESTS (now 42 passed, 1 failed - the failure is branch protection, a genuine open item)
  + a GLOB_BRACE regression guard that fails loudly if the patch is lost to an update
  + the identity guard is INVERTED: FR-11 required an EMPTY identity so the agent could
    not impersonate the operator, but the operator has since asked for it to be set, so
    the test asserting emptiness was asserting the opposite of the current design. It
    now requires the identity to be present and to NOT look agent-generated.

docs/known-issues.md: new section 1 documents the GLOB_BRACE fault in full, including the
honest note that this is the real cost of choosing Alpine over a glibc base image.
2026-09-29 16:28:44 +10:00

atheos-blog-lxc

Provisioning for a browser-based code editor (Atheos) in an Alpine LXC, wired to Gitea, with the armistace/blog repository cloned and ready to edit.

Built with the AI-DLC workflow — the full process trail (requirements, design, units, decisions, audit log) lives in aidlc-docs/.


What this is

A small, single-purpose Proxmox container that puts an editor in your browser so you can write blog posts and commit them to Gitea — without VS Code, without a cloud IDE, and without a build toolchain.

Guest Proxmox LXC 136 on node scotty
OS Alpine 3.23.2 (unprivileged, OpenRC)
URL http://192.168.178.57/ — LAN only
Stack nginx → php-fpm 8.3 (unix socket) → Atheos IDE
Sizes 2 vCPU · 1 GB RAM · 20 GB rootfs · onboot=1
Editor Atheos — PHP, no database, real vim keybindings
Project armistace/blog cloned to /var/www/atheos/workspace/blog
Git auth per-repository deploy key (write-enabled)

What it deliberately is NOT

  • Not VS Code / code-server / Coder. Atheos is an independent, lightweight PHP editor.
  • Not a build environment. The blog is a Pelican site, but building/publishing happens in your existing pipeline. There is intentionally no Python, no pelican, no make here — the presence of Python would be a defect (verified by acceptance criterion AC-8).
  • Not internet-facing. No reverse proxy host, no DNS record, no port-forward is created.

Quick start

If you just want to use it:

  1. Open http://192.168.178.57/ and complete the Atheos first-run installer.
  2. ssh root@192.168.178.57, then set your git identity — see RUNBOOK.md STEP 1. Git will not let you commit until you do, and that is deliberate: the agent does not create your identity.
  3. Add the deploy key to armistace/blog and switch the remote to SSH — RUNBOOK.md STEP 2 & 3.

👉 The full operational guide is RUNBOOK.md. Start at STEP 1.


Repo layout

.
├── README.md                     this file
├── RUNBOOK.md                    ← operational guide; START AT STEP 1
├── docs/
│   ├── security-notes.md         credential model + hardening TODOs if ever exposed
│   ├── resiliency-notes.md       what a non-PoC deployment would need
│   └── writer-branch-workflow.md how writing/publishing works from the editor
├── files/
│   ├── atheos.conf               nginx vhost (install to /etc/nginx/http.d/atheos.conf)
│   ├── ssh-config                ~atheos/.ssh/config (the gitea-blog alias)
│   └── macros.db.php             installed copy of the Atheos writer macros
├── scripts/
│   ├── provision.sh              rebuild the whole guest from scratch
│   └── update-atheos.sh          update Atheos to latest upstream + re-pin
└── aidlc-docs/                   AI-DLC process trail (requirements → design → units → audit)

Rebuilding from scratch

The guest holds nothing unique — the blog's source of truth is Gitea — so a rebuild is cheap:

# on scotty (192.168.178.69), as root
pct destroy 136
sh scripts/provision.sh

scripts/provision.sh is written to be re-runnable and reproduces the documented build exactly, including the pinned Atheos commit and its verified tarball checksum.


Design decisions worth knowing

# Decision Why
D1 No git identity is configured by the agent. You set it (RUNBOOK STEP 1). You own the armistace identity; setting it programmatically would impersonate you and blur attribution (requirement FR-11).
D2 Atheos is pinned to a commit SHA, with update-atheos.sh to deliberately move it. Atheos has no releases; a moving branch is not reproducible.
D3 nginx :80, web root /var/www/atheos, php-fpm over a unix socket. Smallest network surface — nothing extra listens on the LAN.
D6 Per-repository deploy key, not a personal access token. A Gitea PAT's scopes are route groups matched against your whole account, so a stolen token reaches every repo you can write. A deploy key reaches exactly one.

Full rationale, including the alternatives that were rejected and the verification behind them, is in aidlc-docs/design-artifacts/application-design.md and docs/security-notes.md.


Security at a glance

  • Writing happens on a branch, never on master. The editor commits to a long-lived atheos-writer branch and you merge via PR in Gitea — see docs/writer-branch-workflow.md. ⚠️ This only holds if branch protection is enabled on armistace/blog's master — without it the deploy key could force-push master directly. Confirm with the branch_protections API call in that document.
  • Git auth: deploy key scoped to armistace/blog, private key mode 600, owned by the php-fpm user.
  • The SSH alias uses IdentitiesOnly yes, so no other key is ever offered.
  • The web app runs as a dedicated unprivileged user (atheos), which also owns the workspace.
  • ⚠️ Accepted risk: Atheos runs as a web app with shell-exec reach, so a compromise can use the deploy key to write blog. That is the intended floor (one repo, one branch) rather than the ceiling. Keep branch protection on blog's master.
  • If this is ever exposed to the wider web, work through docs/security-notes.md §2 first.

Provenance

Built 2026-09-29
AI-DLC stages executed Workspace Detection, Requirements, Workflow Planning, Application Design, Units Generation, Infrastructure Design, Code Generation, Build & Test
Stages skipped (with rationale) Reverse Engineering, User Stories, Functional Design, NFR Requirements, NFR Design — see aidlc-docs/plans/execution-plan.md
Atheos pin f90cd1806dbe0e25673a77e27390e8b1a240c3f6 (build 611), tarball sha256 03ebcb1479b59f1898b5fa50808b181750b42211698f7f7b4042cf2514d78b20
S
Description
Alpine LXC running Atheos (browser editor) wired to Gitea + armistace/blog. LAN-only. Built with AI-DLC.
Readme
237 KiB
Languages
Shell 92.7%
PHP 7.3%