server (Go)
Single binary. HTTP API, recorder, EventSub manager, scheduler, SSE bus.
Three deployable pieces.
server (Go)
Single binary. HTTP API, recorder, EventSub manager, scheduler, SSE bus.
dashboard (React)
Single-page app. Vite build; the recorder serves the bundle in production.
relay (Cloudflare Worker)
Optional public webhook ingress. Stateless fan-out backed by a Durable Object buffer.
The server is one Go process. Postgres and SQLite are both supported behind one repository interface. ffmpeg and ffprobe are required at runtime.
cmd/server/main.go wires everything in this order:
config.toml and .env. Set up the slog logger.database/sql) and run embedded migrations.Repository adapter.Resume() to pick up jobs left in RUNNING by a previous process.SERVER_MODE=relay, dial the Connect relay and start the replay agent.SERVER_MODE=poll.Postgres and SQLite share one Repository interface in internal/repository/. sqlc turns the SQL files in queries/{postgres,sqlite}/ into typed Go in pgadapter/pggen/ and sqliteadapter/sqlitegen/.
Migrations live in migrations/{postgres,sqlite}/ as numbered NNN_name.up.sql / NNN_name.down.sql pairs and are embedded into the binary. They run automatically on start; the schema_migrations table tracks applied versions.
| Component | Where | What it does |
|---|---|---|
| Downloader | internal/downloader/ | HLS pull → segment fetch with retry → ffmpeg remux → ffprobe → thumbnail → upload. State in DB so crashes resume cleanly. |
| EventSub manager | internal/service/eventsub/ | Boot reconcile (delete orphans, create missing) and per-call quota snapshots. |
| Scheduler | internal/scheduler/ | One ticker, 15 s cadence. Each task has its own row with next_run_at, is_enabled, last_error. |
| Standard tasks | internal/scheduler/tasks.go | EventSub reconcile, quota snapshot, category-art sync, retention sweeps for fetch/event logs and webhook payloads, session and app-token cleanup. |
React 19 SPA, Vite-built. TanStack Router for file-based routes, TanStack Query plus a tRPC v11 client (HTTP batching + SSE on the same client). UI is Base UI v1 scaffolded with shadcn v4; styling is Tailwind v4. i18n is i18next with English and French; a parity test prevents missing keys.
In dev, Vite proxies /api/* and /trpc/* to the recorder on port 8080. In production, the recorder serves the prebuilt dist/ directory directly so the SPA and API share an origin.
Types and Zod schemas come from the Go side via task trpcgen and are committed under dashboard/src/api/generated/.
Cloudflare Worker plus a Durable Object. Two endpoints:
POST /u/{token} — accepts arbitrary bytes, fans out to subscribers, replies 202 (or echoes a synchronous response for EventSub challenges).GET /u/{token}/subscribe — WebSocket. Replays the 5-minute buffer, then streams new events.Stateless except for the Durable Object’s per-token buffer. No Twitch credentials, no payload inspection. See Relay protocol for the wire format.
| Integration | Where | Notes |
|---|---|---|
| Twitch Helix | internal/twitch/client.go | OAuth, streams, games, users, follows, GQL playback tokens. |
| Twitch EventSub | internal/service/eventsub/ | REST CRUD over generated bindings. |
| Generated types | tools/twitch-api-gen/ | Parses Twitch HTML docs. Snapshots committed; CI checks drift. |
| Connect relay | internal/relayclient/ | Outbound WebSocket; replays signed EventSub frames to a local handler. |
| tRPC | befabri/trpcgo | Server side. task trpcgen emits the dashboard’s TypeScript client + Zod schemas. |
| ffmpeg / ffprobe | internal/downloader/{remux,probe}/ | Required at runtime. |