festifun/deploy/DEPLOY.md
m3ultra 36bacf67f0 Live capture: USB webcams, laptop cams, and phones -> data/raw
Records rather than streams: each device records a clip WITH AUDIO and uploads
it; the normal offline pipeline takes over. Fits the design almost for free —
MediaRecorder's webm/mp4 are already ingestible, and audio-based sync means
devices need no clock sync and needn't start together.

- capture.py: POST /api/capture/upload (streamed, sanitized name, size cap,
  partial cleanup), heartbeat/devices registry, GET /capture page.
- static/capture.html: standalone page (no build step) — device picker, live
  preview, record->upload, 1fps snapshot heartbeat, 'who's shooting what'
  monitor, and secure-context detection that tells you to use
  (getUserMedia needs HTTPS — the #1 gotcha for phones).
- OPT-IN via FESTIVAL4D_CAPTURE=1; manifest reports has_capture and the viewer
  only then shows a Capture link. Public deploy leaves it off (open upload
  endpoint would be unsafe) — noted in DEPLOY.md.
- Adds python-multipart dep. Suite 103 -> 119.

Also fixes test_manifest_shape, which was already RED on main: f12b6e0 added
has_splat to the manifest without updating the exact-set lock. Updated to the
true set (now also has_capture); the lock is what caught both drifts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 15:43:51 +10:00

4.9 KiB

Deploying Festival 4D to digalot.fyi/festifun

Target: dealgod@100.94.195.115 (tailscale), served under https://digalot.fyi/festifun by the existing web server that already terminates TLS for the domain.

The app runs as two pieces behind that web server:

  • Static SPA — the built frontend (frontend/dist), served under /festifun/.
  • API backend — uvicorn on 127.0.0.1:8000 (localhost-only), reached via /festifun/api and /festifun/media.

This topology was validated locally end-to-end against a proxy that mirrors the nginx config in this directory (static + prefix-stripping API/media proxy with Range) — the app loads, plays video, and renders the 3D scene correctly under the /festifun prefix.


Deploy mode: fully open

Per the owner's decision, all features are public and unauthenticated — playback, 3D, and the mutating endpoints (annotate, correct event type, add/delete anchors). Be aware this means anyone who finds the URL can edit or delete the project's annotations/anchors/events. If that becomes a problem, add HTTP basic-auth on the /festifun/ location, or run the backend read-only.

AI classification & API keys — deliberately OFF on the public box

POST /api/events/detect runs ffmpeg and, if a classifier key is present, a paid AI call. To avoid an unauthenticated public endpoint that spends real credits, do not put GEMINI_API_KEY or the OpenRouter creds on the server. Instead bake the moment labels in before deploy:

# locally, with keys loaded (set -a; . ./.env; set +a):
python -m festival4d events        # writes AI labels into data/project.db

Then ship that DB. On the server, detect still works but degrades to candidate-only (no spend), exactly as designed. If you want live classification on the public site, set the key in the systemd unit — but add rate-limiting first (nginx limit_req), or it's a bill-run-up vector.


One-time server setup

# on dealgod@100.94.195.115
sudo mkdir -p /var/www/festifun
sudo chown dealgod:dealgod /var/www/festifun
# Python venv for the backend
python3 -m venv /var/www/festifun/.venv

Build + push (run locally)

# 1. Build the frontend for the /festifun prefix + same-origin API
cd frontend
FESTIVAL4D_BASE=/festifun/ VITE_API_BASE=/festifun npm run build

# 2. Generate the project to ship (synthetic demo, or your real footage pipeline first),
#    with labels baked in if you loaded keys:
cd ..
python -m festival4d synthetic        # or: ingest -> sync -> reconstruct -> events

# 3. Push build + backend + data to the server
rsync -az --delete frontend/dist/        dealgod@100.94.195.115:/var/www/festifun/dist/
rsync -az --delete backend/              dealgod@100.94.195.115:/var/www/festifun/backend/
rsync -az            pyproject.toml       dealgod@100.94.195.115:/var/www/festifun/
rsync -az --delete data/                 dealgod@100.94.195.115:/var/www/festifun/data/

Install backend deps + service (on the server)

cd /var/www/festifun
.venv/bin/pip install -e ".[dev]"        # or a runtime-only extra if defined
sudo cp backend/../deploy/festifun-api.service /etc/systemd/system/   # adjust path to the repo copy
sudo systemctl daemon-reload
sudo systemctl enable --now festifun-api
curl -s localhost:8000/api/health        # -> {"status":"ok"}

Wire up the web server

Append the blocks from deploy/festifun.nginx.conf into the existing server { } for digalot.fyi, adjusting alias paths to /var/www/festifun/dist/. Then:

sudo nginx -t && sudo systemctl reload nginx

Visit https://digalot.fyi/festifun/.

Using Caddy instead of nginx? The equivalent is a handle_path /festifun/* block: file_server for the SPA, reverse_proxy 127.0.0.1:8000 for /festifun/api/* and /festifun/media/* (Caddy strips the matched prefix with handle_path). Ask and I'll write the Caddyfile once I can see which server is actually running there.


Updating later

Re-run the build + rsync steps, then sudo systemctl restart festifun-api (only needed if the backend or data changed; a frontend-only change just needs the dist rsync).

Notes / caveats

  • CORS is irrelevant in this topology — everything is same-origin under /festifun. The dev-only wide CORS in config.py stays as-is; it doesn't affect the hosted site.
  • Single project. The app assumes one project at a time (SQLite at data/project.db).
  • The backend binds 127.0.0.1 only; nginx is the sole public entry point.

Live capture on the public box: leave it OFF

FESTIVAL4D_CAPTURE is not set in deploy/festifun-api.service, so /capture and /api/capture/* are never mounted publicly — an open upload endpoint on a public host would let anyone write files to the server. Capture is for your local/tailnet machine (see the README's Capture section: FESTIVAL4D_CAPTURE=1 + tailscale serve). Record locally, run the pipeline, then deploy the resulting project.