A small, opinionated admin dashboard for Gas City (gc) orchestrations — built for a single operator running a single city.
The upstream gc dashboard (port 8080) is the generic multi-city surface. Citadel is the one you keep open in a tab and check every few minutes: agents, beads, mail, recent activity, system health, all on one page each.
Five views.
- Agents — every session's state at a glance. Click for a live
gc session peeksnapshot. - Beads — engineering work only (default filter hides system tracking noise). Claim, close, nudge inline.
- Mail — read any agent's mailbox via a view-as dropdown. Outgoing mail always goes from you; impersonation is read-only.
- Activity — recent commits, dev-deploy events, refinery merges. Real-time updates via Server-Sent Events from the gc supervisor.
- Health — supervisor process state, host memory pressure (RSS + Committed_AS), dolt-noms 24-hour trend, dev-deploy success/failure history.
- A running Gas City (
gc supervisorreachable over HTTP —:8372by default) - Node.js 20+
- Linux user with
systemd --userenabled (for the systemd deploy path; alternatives in deploy/README.md)
git clone https://github.com/Wldc4rd/citadel.git
cd citadel
npm install
npm run build:shared # types must build first
# Terminal 1 — backend on :8081
npm run dev:backend
# Terminal 2 — Vite dev server on :5174, proxies /api → :8081
npm run dev:frontendBrowse http://127.0.0.1:5174.
cd citadel
npm install
npm run build
node backend/dist/server.js # serves API + frontend on :8081For the systemd-managed install: deploy/README.md.
All knobs are environment variables. See backend/src/config.ts for the authoritative source.
| Variable | Default | Purpose |
|---|---|---|
PORT |
8081 |
TCP port the dashboard listens on |
HOST |
127.0.0.1 |
Bind interface. Set 0.0.0.0 for LAN access on a trusted network. |
ADMIN_EXTRA_ALLOWED_HOSTS |
(empty) | CSV of extra hostnames allowed in the Host: header (e.g. my-vm,192.168.1.58). The floor 127.0.0.1/localhost is always allowed. |
GC_SUPERVISOR_URL |
http://127.0.0.1:8372 |
gc supervisor API base URL |
GC_CITY_NAME |
thriva-dev |
Name of the city this dashboard manages (one dashboard per city) |
GC_CITY_OWNER_ALIAS |
human |
The dashboard operator's wire identity — used as bead --assignee on claim, mail-filter default, audit actor, and the frontend's default viewing-as. Set to e.g. charlie to preserve the historical Charlie-deploy assignee/routing. Wire identity for the mail-send path is structurally locked to 'human' regardless. |
ADMIN_AUDIT_LOG_PATH |
(gc's events.jsonl) |
Where state-changing actions append audit entries |
ADMIN_FRONTEND_DIST |
../frontend/dist |
Path to built frontend assets |
ADMIN_DOLT_NOMS_ROOT |
/home/charlie/thriva-dev/.beads/dolt |
Root of the bd-store Dolt tree the Health sparkline samples (10-min cadence). Set to "" to disable. |
THRIVA_ADMIN_GIT_REPO |
/home/charlie/thriva |
Repo for the Activity view's git log queries |
THRIVA_ADMIN_DASHBOARD_DISABLED |
0 |
Kill switch — set to 1 to refuse to start |
Admin tooling that assumes the operator is the only user and the dashboard is reachable only on a trusted network.
- Default bind is
127.0.0.1only — DNS-rebinding floor - Host-header allow-list always permits
127.0.0.1andlocalhost; LAN names opt in viaADMIN_EXTRA_ALLOWED_HOSTS - CSRF — state-changing endpoints require a token issued via cookie (double-submit pattern)
- Origin check — POST/PATCH/DELETE require an
Originmatching the allowed-host set - Content Security Policy —
script-src 'self', no inline scripts, noeval - Exec whitelist — every shell-out is enumerated explicitly in
backend/src/exec.ts. There is no general-purpose command execution path by design.
Full threat model: docs/SECURITY.md.
- Backend — Node 20 + Express + TypeScript. Single port serves API at
/api/*and the SPA from/. - Frontend — React 18 + Vite + TypeScript + Tailwind. Single-page app, statically served by the backend in production.
- Shared types —
citadel-sharedworkspace package. Wire-shape drift becomes a compile error on both sides. - Deploy — systemd user unit. Deliberately not managed by
gc [[services]]— see docs/ARCHITECTURE.md for why the dashboard must outlive supervisor outages.
citadel/
├── package.json # npm workspace root
├── shared/ # wire-shape types
├── backend/ # Express + TS
│ └── src/{server.ts,middleware,routes,gc-client.ts,exec.ts,audit.ts}
├── frontend/ # React + Vite + Tailwind
│ └── src/{components,routes,api}
├── deploy/ # systemd unit + install README
└── docs/ # ARCHITECTURE, SECURITY, EXTENDING
- docs/ARCHITECTURE.md — design decisions and tradeoffs
- docs/SECURITY.md — full threat model + posture
- docs/EXTENDING.md — how to add a view, route, or whitelisted exec command
- deploy/README.md — systemd install, update, kill-switch, diagnostics
This was extracted from a private project (tools/admin-dashboard/) once it proved more useful for one specific operator's daily workflow than the upstream generic gc dashboard. Its full commit history is preserved via git subtree split and lives at the root of this repo.
The name is from Mad Max: Fury Road — Furiosa's fortified stronghold from which the wasteland is overseen and the war rig is dispatched. Admin dashboard, fortified stronghold — same idea.
Built for a specific way of running a gc city. Parameterized via env vars so it could serve other operators with similar shapes, but no claim of broad generality is made. Bug reports welcome; feature requests considered against the "should be useful on a single screen kept open all day" sniff test.
MIT © 2026 Charlie Coutts