fuzzyworld

fuzzyworld-tools / files / docs / admin.md

admin.md

Blame: the commit that last changed each line · file · history

bb7e1f57 2026-11-06 Pavel Safronov1# Admin panel (`fw admin`)
2
3```bash
4fw admin [--port N] [--no-open] [--code-dir DIR]
5```
6
7`lib/admin.py` (Python standard library only) runs **on the laptop**. It starts a small web page on `127.0.0.1` for
8bug reports, blog comments and what is deployed where, prints a one-time login URL and opens it in the browser.
9
10- The pages are server-rendered HTML with plain `POST` forms and **no JavaScript**.
11- Every button runs the matching `~/bin/fw ...` command on the server over ssh, exactly what you would type (`FW_HOST`, default `dh`).
12- The panel keeps no state and never shows a password.
13- It is local-only on purpose: it is never hosted on DreamHost.
14
15`FW_CODE_DIR` (or `--code-dir`, default `~/code`) is the folder holding the apps' working copies.
16
17The server-side `fw` must know `issues list --json`, `issues show --json`, `issues record`, `comments list --json`
18and `deploys --json`, so deploy the server scripts first (`npm run deploy`).
19
20## Security model
21
22| Threat | Defence |
23|---|---|
24| Other machines | binds to `127.0.0.1` only |
25| 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 |
26| DNS rebinding | the `Host` header must be exactly `127.0.0.1:<port>`, else 421 |
27| 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 |
28| Script injection | strict `Content-Security-Policy` (no scripts, forms only to itself, no framing), `nosniff`, no referrer, no caching |
29| 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 |
30| Mistakes | hide, reject, untrust, remove and every mode change ask for confirmation first |
31| Leaks | query strings are never logged |
32
33## Pages
34
35### Reports (`/reports`)
36
37One table across repos: repo, number, title, state, public, mode, record, messages shown/total, age. Filters:
38**Waiting** (the default when anything waits), **Has replay** and **All**.
39
40### A report (`/reports/<repo>/<n>`)
41
42- 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.
43- Buttons: Approve, Hide, Close / Reopen.
44- Radio buttons for the repo's mode (public, reviewed, private) with Set.
45- Previous / next links, and a link to the public page when there is one.
46
47### Watch (on a report with a run record)
48
49- A link to the deployed replay viewer, when the record is public.
50- Always "Download record" (`issues record`).
51- When `<code dir>/<repo>/package.json` has a `report-replay` script, "Prepare dev replay":
52 1. it saves the record to a fresh temporary folder,
53 2. runs `npm 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,
54 3. shows the output, with the `http://localhost:5173/?replay=...` address as a link.
55
56 It works only while `npm run dev` runs in that repo. The record folder is kept, because the dev server needs it:
57 one `fw-admin-<repo>-<n>-...` folder per replay under the system temp dir, mode 700. Delete them when you like.
58
59### Comments (`/comments`)
60
61Pending comments in full, with the post link, author, date and source. Buttons: Approve, Approve + trust (email
62comments only), Reject, and Untrust (trusted senders only). Below, "Recently approved" (the 20 newest) with a Remove
63button each.
64
65### Deploy (`/deploy`, read-only for now)
66
67Uses `fw deploys --json`. There is no button yet: deploy with `npm run deploy`.
68
69- **Tools:** per repo that declares deploy targets (in `package.json` under `fuzzyworld.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 for `perMachine` targets.
70- **Apps:** every deployed app against its repo: state, deployed commit, time, and the commits since.
71 - Commits that only touch Markdown, CI config, the licence, `.gitignore`, `docs/` or `screenshots/` are ignored, so a repo-wide CI-file rename does not mark every app pending.
72 - The state is "unknown" when the repo or the commit is not on the server.
73
74## Behaviour
75
76The server handles one request at a time (an idle connection cannot stall it). After each action the page reloads
77with the command's output (or its error) at the top. These messages are server-wide, not per browser tab.