603d0ad555
Clicking a gallery image now opens a dedicated viewer instead of dumping you on the raw file. - GET /b/<name>/view?f=<img> — full-viewport viewer (registered before the file catch-all so /view wins; non-image f 307-redirects to the raw file, traversal and missing f 404). - Fit (downscale-only) / 1:1 (natural pixels, scroll-to-pan) toggle that only appears when the image is larger than the viewport — when it already fits, Fit ≡ 1:1 so the toggle is hidden. Re-evaluates on resize. - Download button + ✕/Esc back to the gallery. Australis-themed, progressive JS (degrades to fit-only, no-JS still shows the image + download + back). - 5 new tests (34 total, all green); verified Fit/1:1/hidden-toggle states in a real browser.
136 lines
5.7 KiB
Markdown
136 lines
5.7 KiB
Markdown
# The Booth
|
|
|
|
A dead-simple standing web server for shuttling **ephemeral files** between the
|
|
operator and CC sessions — A/B renders, smoke-test screenshots, audio/video
|
|
samples, or anything you want to hand off. It works both directions:
|
|
|
|
- **Session → operator:** a session drops a folder of files on disk; the Booth
|
|
renders it as a browsable "booth".
|
|
- **Operator/anyone → pickup:** upload files through the browser (or `curl -F`)
|
|
and get a **human-readable pickup id** like `4-wombat` or `star-84`.
|
|
|
|
Either way it **wipes 24h after the last activity**. No database — the
|
|
filesystem *is* the state.
|
|
|
|
- **Live:** http://10.100.10.50:8090/ (nh3-dev) · linked from Homepage → *Apps → The Booth*
|
|
- **Data dir:** `~/booth-data/` on nh3-dev (one subfolder per booth)
|
|
- **TTL:** 24h, measured from the newest mtime in a booth's tree (it lives while
|
|
you're touching it, self-destructs 24h after you stop)
|
|
|
|
## How a session posts
|
|
|
|
A booth is **just a folder** under the data dir. Three ways, cheapest first:
|
|
|
|
```bash
|
|
# 1. On nh3-dev — the helper (services/booth/scripts/booth):
|
|
booth add my-run out/a.png out/b.png # creates booth + copies, prints URL
|
|
booth new my-run # empty booth, then cp/mv into ~/booth-data/my-run/
|
|
booth url my-run # just print the URL
|
|
booth ls # list booths
|
|
booth rm my-run # wipe now (TTL would anyway)
|
|
|
|
# 2. On nh3-dev — raw, no helper:
|
|
mkdir -p ~/booth-data/my-run && cp out/*.png ~/booth-data/my-run/
|
|
# -> http://10.100.10.50:8090/b/my-run/
|
|
|
|
# 3. From another host — rsync into the data dir:
|
|
rsync -a ./out/ nh3-dev:booth-data/my-run/
|
|
```
|
|
|
|
Then hand the operator `http://10.100.10.50:8090/b/my-run/`.
|
|
|
|
## Upload for pickup
|
|
|
|
The reverse direction — put files in through the web, pick them up by id:
|
|
|
|
- **Browser:** the index page has an *Upload files for pickup* panel
|
|
(drag-drop or click). Submit → you land on a booth with a **human-readable
|
|
id** (`4-wombat`, `star-84`) whose files each have a ⬇ download link.
|
|
- **curl (a remote session with no ssh to nh3-dev can use this too):**
|
|
```bash
|
|
curl -sS -i -F 'files=@out/a.png' -F 'files=@out/b.png' \
|
|
http://10.100.10.50:8090/upload | grep -i location
|
|
# Location: /b/star-84/ <- the pickup id
|
|
```
|
|
- **Pick up** at `http://10.100.10.50:8090/b/<id>/` (download links), or on
|
|
nh3-dev straight off disk at `~/booth-data/<id>/`.
|
|
|
|
Uploads are stamped as pickup booths (a `⬆ pickup` badge in the UI) and expire
|
|
on the same 24h TTL. Limits: `BOOTH_MAX_FILES` files (default 50) and
|
|
`BOOTH_MAX_UPLOAD_MB` total per submission (default 1024); filenames are reduced
|
|
to a safe basename (no path traversal).
|
|
|
|
## What a booth renders
|
|
|
|
- **Has its own `index.html`?** → served **verbatim** (its relative assets —
|
|
`chart.png`, `report.css` — resolve out of the same folder). Build whatever
|
|
page you want.
|
|
- **No `index.html`?** → **auto-gallery** of the folder's media:
|
|
- images (`png jpg jpeg gif webp avif svg bmp`) → `<img>` (click → full-screen
|
|
viewer with **Fit** / **1:1** — the toggle only appears when the image is
|
|
larger than the viewport — plus download and ✕/Esc back to the gallery)
|
|
- video (`webm mp4 ogv m4v mov`) → `<video controls>`
|
|
- audio (`mp3 wav ogg flac m4a opus aac`) → `<audio controls>`
|
|
- anything else → a download link
|
|
- **Captions:** a `<file>.txt` or same-stem `<stem>.txt` sidecar is folded in as
|
|
that item's caption — the natural way to label an A/B pair:
|
|
```
|
|
a.png b.png
|
|
a.txt "baseline" b.png.txt "cudaMallocAsync (winner)"
|
|
```
|
|
|
|
## Routes
|
|
|
|
| Route | Purpose |
|
|
|---|---|
|
|
| `GET /` | Index — one card per booth (newest first), with expiry countdown |
|
|
| `GET /b/<name>/` | A booth (its `index.html`, else auto-gallery) |
|
|
| `GET /b/<name>/<file>` | Serve a file out of the booth |
|
|
| `POST /upload` | Upload files → new pickup booth; 303-redirects to `/b/<id>/` (id in `Location`) |
|
|
| `POST /b/<name>/delete` | Wipe a booth (the UI's "Wipe now" button) |
|
|
| `DELETE /b/<name>` | Wipe a booth (curl/API) |
|
|
| `GET /healthz` | `{ok, ttl_hours, booths}` — Homepage siteMonitor target |
|
|
|
|
## Ops
|
|
|
|
Runs as a **user-level** systemd service on nh3-dev (no root, no Docker),
|
|
alongside the other fleet sidecars (herald, zellij-web, ttyd).
|
|
|
|
```bash
|
|
systemctl --user status booth.service
|
|
systemctl --user restart booth.service
|
|
journalctl --user -u booth.service -f # sweeper logs "[booth] swept …"
|
|
```
|
|
|
|
Config is env in the unit (`booth.service`):
|
|
`BOOTH_DATA_DIR`, `BOOTH_TTL_HOURS`, `BOOTH_HOST_LABEL`, `BOOTH_SWEEP_INTERVAL_MIN`,
|
|
`BOOTH_MAX_UPLOAD_MB` (default 1024), `BOOTH_MAX_FILES` (default 50).
|
|
|
|
### Install / update
|
|
|
|
```bash
|
|
cd services/booth
|
|
uv venv && uv pip install fastapi "uvicorn[standard]" jinja2 python-multipart # runtime deps
|
|
cp booth.service ~/.config/systemd/user/booth.service
|
|
systemctl --user daemon-reload && systemctl --user enable --now booth.service
|
|
```
|
|
|
|
Code runs straight from this checkout (the unit's `WorkingDirectory` /
|
|
`ExecStart` point here), so "deploy an update" = edit + `systemctl --user
|
|
restart booth.service`.
|
|
|
|
### Tests
|
|
|
|
```bash
|
|
cd services/booth && uv pip install pytest httpx && .venv/bin/python -m pytest -q
|
|
```
|
|
|
|
## Notes / non-goals
|
|
|
|
- **No auth.** LAN/WG-internal only, ephemeral content — don't drop secrets in a
|
|
booth, and note anyone on the LAN can upload (bounded by the size/file limits).
|
|
Uploaded files are served back with their own content-type, so an uploaded
|
|
`index.html` renders as a page (a feature for custom reports; keep it in mind).
|
|
- Booth names with `/`, `..`, or a leading `.` are rejected; file serving and
|
|
uploaded filenames are guarded against path traversal and symlink escape.
|