festifun/deploy/DEPLOY.md
m3ultra 931cf81a6c Deploy-ready: path-prefix hosting (digalot.fyi/festifun) + deploy artifacts
Frontend: API_BASE env-driven (VITE_API_BASE, default localhost:8000 so dev is
unchanged); vite.config.js base from FESTIVAL4D_BASE. Prod build
'FESTIVAL4D_BASE=/festifun/ VITE_API_BASE=/festifun' serves same-origin under
the prefix. Validated end-to-end via a local proxy mirroring the nginx config:
app boots, media Range 206, 3D + overlays + timeline render under /festifun.

deploy/: nginx location blocks (prefix strip + Range), systemd unit
(uvicorn 127.0.0.1:8000, no keys on the public box), DEPLOY.md. Labels baked
into the shipped DB locally so detect can't spend credits from an anonymous
public endpoint. Backend suite 103 passed; default build unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 13:20:36 +10:00

4.5 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.