From f4a5ba7c3100bfe61cfa443cf5eb4deadfdd6c80 Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Mon, 20 Jul 2026 10:17:40 -0700 Subject: [PATCH] =?UTF-8?q?feat(booth):=20add=20The=20Booth=20=E2=80=94=20?= =?UTF-8?q?ephemeral=20media=20drop=20board=20for=20CC=20sessions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A standing user-level web server (nh3-dev :8090) that renders drop-folders under ~/booth-data as ephemeral media "booths" so Claude Code sessions can surface A/B renders and smoke results to the operator, then let them self-wipe. - Scan-and-serve model, no database, no upload API — a booth is just a folder. A folder's own index.html is served verbatim; otherwise an auto-gallery of images / webm+mp4 video / audio is rendered, with .txt caption sidecars folded in (labels A/B pairs). - 24h TTL from newest mtime in the tree; background sweeper wipes stale booths. - Path-traversal + symlink-escape guarded; delete via UI button or DELETE API. - FastAPI + Jinja2, runs from the checkout under systemctl --user (booth.service), alongside the other nh3-dev fleet sidecars. 15 tests, all green. - Homepage tile added (Apps -> The Booth, siteMonitor /healthz). - Harden the homepage rsync doc: exclude *.bak* and logs/ so --delete can't wipe the host's dated services.yaml backups (footgun found deploying this). --- configs/homepage/README.md | 9 + configs/homepage/services.yaml | 9 + services/booth/.gitignore | 6 + services/booth/README.md | 104 +++++++ services/booth/booth.service | 21 ++ services/booth/booth/__init__.py | 3 + services/booth/booth/app.py | 326 ++++++++++++++++++++++ services/booth/booth/templates/base.html | 103 +++++++ services/booth/booth/templates/booth.html | 34 +++ services/booth/booth/templates/index.html | 37 +++ services/booth/pyproject.toml | 26 ++ services/booth/scripts/booth | 47 ++++ services/booth/tests/test_booth.py | 184 ++++++++++++ 13 files changed, 909 insertions(+) create mode 100644 services/booth/.gitignore create mode 100644 services/booth/README.md create mode 100644 services/booth/booth.service create mode 100644 services/booth/booth/__init__.py create mode 100644 services/booth/booth/app.py create mode 100644 services/booth/booth/templates/base.html create mode 100644 services/booth/booth/templates/booth.html create mode 100644 services/booth/booth/templates/index.html create mode 100644 services/booth/pyproject.toml create mode 100755 services/booth/scripts/booth create mode 100644 services/booth/tests/test_booth.py diff --git a/configs/homepage/README.md b/configs/homepage/README.md index 2296faa..f09a984 100644 --- a/configs/homepage/README.md +++ b/configs/homepage/README.md @@ -85,12 +85,21 @@ Current workflow — push this directory onto the host: ```bash rsync -av --delete \ --exclude='.env' --exclude='.env.*' \ + --exclude='*.bak*' --exclude='logs/' \ configs/homepage/ esh-docker-vm:/opt/docker/conf/homepage/ ``` The real `.env` lives on `esh-docker-vm` next to the compose file and must not be overwritten (holds Plex/Jellyfin keys). +> **`--delete` footgun (learned 2026-07-20):** the host keeps dated +> `services.yaml.bak-*` safety copies and a live `logs/` dir that are *not* +> in this repo. A bare `--delete` rsync wipes both. The `--exclude='*.bak*'` +> and `--exclude='logs/'` above protect them. For a one-file tweak, skip +> `--delete` entirely and push the single file: +> `rsync -av configs/homepage/services.yaml esh-docker-vm:/opt/docker/conf/homepage/services.yaml` +> (back up the host copy first: `ssh esh-docker-vm 'cp -a …/services.yaml …/services.yaml.bak--'`). + The homepage container reloads most files on-change; if a new group in `settings.yaml` doesn't show up, `docker compose restart` on the host. diff --git a/configs/homepage/services.yaml b/configs/homepage/services.yaml index 3cccf8f..8aff8c7 100644 --- a/configs/homepage/services.yaml +++ b/configs/homepage/services.yaml @@ -17,6 +17,15 @@ siteMonitor: http://10.0.50.45:3001 description: Uptime monitor (esh-docker-vm) +- Apps: + # Manual entry — the Booth is a user-level systemd service on nh3-dev + # (not a Docker-labeled stack), so it can't auto-discover; list it here. + - The Booth: + href: http://10.100.10.50:8090/ + icon: mdi-filmstrip + siteMonitor: http://10.100.10.50:8090/healthz + description: Ephemeral media drop — CC-session A/B & smoke results (nh3-dev, 24h TTL) + # The AI tab is fully Docker-auto-discovered. Each inference service carries # a homepage.group=AI - label on its compose file (AI - Inference, # AI - Eval & Retrieval, AI - Gateways & Chat, AI - Speech (TTS), diff --git a/services/booth/.gitignore b/services/booth/.gitignore new file mode 100644 index 0000000..c845f32 --- /dev/null +++ b/services/booth/.gitignore @@ -0,0 +1,6 @@ +.venv/ +__pycache__/ +*.pyc +*.egg-info/ +.pytest_cache/ +booth-data/ diff --git a/services/booth/README.md b/services/booth/README.md new file mode 100644 index 0000000..24928b2 --- /dev/null +++ b/services/booth/README.md @@ -0,0 +1,104 @@ +# The Booth + +A dead-simple standing web server for surfacing **ephemeral media** to the +operator — A/B renders, smoke-test screenshots, audio/video samples. A Claude +Code session drops a folder of files somewhere on disk; the Booth renders it as +a browsable "booth" and **wipes it 24h after the last activity**. No database, +no upload API — 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/`. + +## 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`) → `` + - video (`webm mp4 ogv m4v mov`) → `