Initial commit

This commit is contained in:
Ian Keane 2026-09-14 13:45:57 -04:00
commit 6aa9bf85f3
10 changed files with 660 additions and 0 deletions

60
README.md Normal file
View file

@ -0,0 +1,60 @@
# zoitestream
Landing page + lightweight API for `stream.dumpnet.chat`, backed by
[MediaMTX](https://github.com/bluenviron/mediamtx).
This is intentionally tiny: a single Flask process with a SQLite database
(on a persistent volume, not container-local disk) tracking the currently
active stream and stream history. MediaMTX calls this service's `/hooks/*`
endpoints on stream start/stop (via its `runOnReady`/`runOnNotReady` path
hooks), and the landing page polls `/api/active` and `/api/history` to
render status. An IRC bot can call `/api/creds` to fetch the current
publish URL to hand out to whoever wants to stream.
State persists across pod restarts/redeploys (SQLite file lives on the
shared `appdata` hostPath PV, mounted at `STATE_DB_PATH`).
## Design notes
- One shared publish stream/password for everyone (a deliberate feature,
not a limitation) — credentials are retrievable via `/api/creds` (meant
for the IRC bot to call), never shown on the public landing page. The
IRC channel is a high-trust environment, so this endpoint is only
lightly protected (shared token), not treated as a hard secret boundary.
- `/hooks/ready` and `/hooks/not-ready` are protected by a separate shared
token (`HOOK_TOKEN`) so MediaMTX is the only thing that can flip stream
status — rotatable independently of the creds token.
- Publish password itself lives in MediaMTX's own internal auth config
(`authInternalUsers`), not validated by zoitestream — zoitestream just
knows the current value (via `PUBLISH_PASSWORD` env var, sourced from
the same secret) so it can hand it back out through `/api/creds`.
- No scheduling API yet — planned as a future addition, likely driven by
an IRC bot, and will live in the same SQLite database.
## Endpoints
- `GET /` — landing page
- `GET /api/active` — JSON list of currently active streams
- `GET /api/history` — JSON list of recent streams (capped, most-recent-first)
- `GET /api/creds` — publish URL/user/password (token-protected via `CREDS_TOKEN`)
- `GET /health` — health check
- `POST /hooks/ready` — called by MediaMTX `runOnReady` (form/query: `path`, `source_type`)
- `POST /hooks/not-ready` — called by MediaMTX `runOnNotReady` (form/query: `path`)
## Env vars
- `HOOK_TOKEN` — shared secret required (as `?token=` or `X-Hook-Token` header) on `/hooks/*`
- `CREDS_TOKEN` — shared secret required (as `?token=` or `X-Creds-Token` header) on `/api/creds`
- `PUBLISH_USER` — publish username to report via `/api/creds` (default `streamer`)
- `PUBLISH_PASSWORD` — publish password to report via `/api/creds` — must match MediaMTX's own config
- `DOMAIN` — display domain shown on the landing page and in `/api/creds` (default `stream.dumpnet.chat`)
- `RTMP_PORT` — RTMP port used in the URL built by `/api/creds` (default `1935`)
- `STATE_DB_PATH` — path to the SQLite file (default `/data/zoitestream.db`)
- `HISTORY_LIMIT` — number of history entries to retain (default `50`)
## Local dev
```
uv sync
STATE_DB_PATH=./dev.db uv run flask --app wsgi:app run --debug
```