Admin panel (fw admin)
fw admin [--port N] [--no-open] [--code-dir DIR]
lib/admin.py (Python standard library only) runs on the laptop. It starts a small web page on 127.0.0.1 for
bug reports, blog comments and what is deployed where, prints a one-time login URL and opens it in the browser.
- The pages are server-rendered HTML with plain
POSTforms and no JavaScript. - Every button runs the matching
~/bin/fw ...command on the server over ssh, exactly what you would type (FW_HOST, defaultdh). - The panel keeps no state and never shows a password.
- It is local-only on purpose: it is never hosted on DreamHost.
FW_CODE_DIR (or --code-dir, default ~/code) is the folder holding the apps' working copies.
The server-side fw must know issues list --json, issues show --json, issues record, comments list --json
and deploys --json, so deploy the server scripts first (npm run deploy).
Security model
| Threat | Defence |
|---|---|
| Other machines | binds to 127.0.0.1 only |
| Strangers on this machine | the login URL carries a random key that works once and sets a session cookie fwadmin-<port> (HttpOnly, SameSite=Strict); any other request without the cookie gets 403 |
| DNS rebinding | the Host header must be exactly 127.0.0.1:<port>, else 421 |
| Cross-site requests | every POST needs a random per-launch token in its form (a separate secret from the cookie), an Origin (if sent) equal to the page's own, and Sec-Fetch-Site (if sent) same-origin or none; otherwise 403 and no command runs |
| Script injection | strict Content-Security-Policy (no scripts, forms only to itself, no framing), nosniff, no referrer, no caching |
| Command injection | mail text never reaches a command line: only a validated repo name, issue number, comment id and a fixed set of action words, each checked again before ssh is run |
| Mistakes | hide, reject, untrust, remove and every mode change ask for confirmation first |
| Leaks | query strings are never logged |
Pages
Reports (/reports)
One table across repos: repo, number, title, state, public, mode, record, messages shown/total, age. Filters: Waiting (the default when anything waits), Has replay and All.
A report (/reports/<repo>/<n>)
- The details table and every message in full, including reports that are not published, with who sent it (Reporter A, B, ...), whether it passed authentication and whether it is shown.
- Buttons: Approve, Hide, Close / Reopen.
- Radio buttons for the repo's mode (public, reviewed, private) with Set.
- Previous / next links, and a link to the public page when there is one.
Watch (on a report with a run record)
- A link to the deployed replay viewer, when the record is public.
- Always "Download record" (
issues record). - When
<code dir>/<repo>/package.jsonhas areport-replayscript, "Prepare dev replay": 1. it saves the record to a fresh temporary folder, 2. runsnpm run --silent report-replay -- <record.json>in<code dir>/<repo>(with--rules <commit>when the report's details have a commit), 60 s at most, 3. shows the output, with thehttp://localhost:5173/?replay=...address as a link.
It works only while npm run dev runs in that repo. The record folder is kept, because the dev server needs it:
one fw-admin-<repo>-<n>-... folder per replay under the system temp dir, mode 700. Delete them when you like.
Comments (/comments)
Pending comments in full, with the post link, author, date and source. Buttons: Approve, Approve + trust (email comments only), Reject, and Untrust (trusted senders only). Below, "Recently approved" (the 20 newest) with a Remove button each.
Deploy (/deploy, read-only for now)
Uses fw deploys --json. There is no button yet: deploy with npm run deploy.
- Tools: per repo that declares deploy targets (in
package.jsonunderfuzzyworld.deploy), each target's state (up to date, pending, never deployed), the commit and time it was deployed from, and the commits on the default branch since that touch its folders; per laptop forperMachinetargets. - Apps: every deployed app against its repo: state, deployed commit, time, and the commits since.
- Commits that only touch Markdown, CI config, the licence,
.gitignore,docs/orscreenshots/are ignored, so a repo-wide CI-file rename does not mark every app pending. - The state is "unknown" when the repo or the commit is not on the server.
Behaviour
The server handles one request at a time (an idle connection cannot stall it). After each action the page reloads with the command's output (or its error) at the top. These messages are server-wide, not per browser tab.