Skip to content

Architecture

ReplayVOD has three main components: a Go server, a React dashboard, and an optional Cloudflare Worker relay. The production server serves the dashboard’s static build.

The server runs the HTTP API, recorder, scheduler, and Twitch integrations in one Go process. PostgreSQL and SQLite implement the same repository interface. Recording requires ffmpeg and ffprobe at runtime.

  • Directoryserver/
    • Directorycmd/server/ entrypoint
      • …
    • Directoryinternal/
      • Directoryserver/
        • Directoryapi/ HTTP routes (Chi) + tRPC procedures, grouped by domain (auth, schedule, system, …)
          • …
      • Directoryinvite/ invitation links
        • …
      • Directorysession/ encrypted token store, cookie sessions, initial whitelist entries
        • …
      • Directoryconfig/ configuration loading
        • …
      • Directorydatabase/ database connections and migrations
        • …
      • Directoryrepository/
        • Directorycontracttest/ shared tests for both adapters
          • …
        • Directorypgadapter/ Postgres adapter (sqlc-generated)
          • …
        • Directorysqliteadapter/ SQLite adapter (sqlc-generated)
          • …
      • Directorytwitch/ Helix client + generated EventSub types
        • …
      • Directoryservice/
        • Directoryeventsub/ subscription management and quotas
          • …
        • Directorystreammeta/ title and category tracking
          • …
        • Directorycategoryart/ category artwork updates
          • …
        • Directoryschedule/ scheduled-recording rules and channel requests
          • …
      • Directorydownloader/
        • Directoryhls/ segment fetcher
          • …
        • Directoryremux/ ffmpeg wrapper
          • …
        • Directoryprobe/ ffprobe wrapper
          • …
        • Directorythumbnail/ JPEG generator
          • …
      • Directoryscheduler/ task runner with DB-persisted state
        • …
      • Directoryeventbus/ typed in-process publish/subscribe
        • …
      • Directoryrelayclient/ WebSocket agent for Connect
        • …
      • Directorystorage/ local and S3 backends
        • …
      • Directorylogger/ slog setup
        • …
    • Directorymigrations/ versioned .up/.down SQL pairs
      • …
    • Directoryqueries/ sqlc query files
      • …
    • Directorytests/upgrade/ Docker upgrade and rollback suite against published images
      • …
    • Directorytools/
      • Directorytwitch-api-gen/ generator for Helix + EventSub types
        • …
      • Directoryrepo-adapter-gen/ generator for the adapters’ mappers and method bodies
        • …
      • Directoryupgrade-baseline/ records a published release in the upgrade manifest
        • …
      • Directoryupgrade-changes/ decides whether CI needs the upgrade suite
        • …
      • Directorypublish-images/ pushes the tested images and assembles multi-arch tags
        • …
    • config.toml server settings
    • .env.example credentials and paths

server/cmd/server/main.go creates and connects the services:

  1. Load configuration, initialise logging, connect to the database, and run migrations.
  2. Select the repository adapter and resolve the server mode from environment or saved settings.
  3. Create the Twitch client, session manager, storage backend, and metadata services.
  4. Create the downloader, event bus, storage health monitor, playback cache, and recording webhook dispatcher.
  5. Recover interrupted download jobs and start the HTTP server.
  6. Start polling or EventSub processing, including the relay client when selected.
  7. Register background tasks and start the scheduler.

Shutdown is triggered by SIGINT or SIGTERM. Services stop and persist the state needed for recovery. Storage checks gate recording, playback builds, and cleanup while a volume is unavailable.

The Repository interface is in internal/repository/. sqlc generates typed query code from queries/postgres/ and queries/sqlite/. Shared contract tests check both adapters against the same behaviour.

Migrations are embedded from migrations/{postgres,sqlite}/ as numbered .up.sql and .down.sql pairs. Startup applies pending up migrations. schema_migrations records the applied versions and each complete up file’s SHA-256 checksum, including comments and formatting.

Before applying changes, the runner rejects missing or modified files in the applied history. Restore the original history and rebuild, or use the server build that applied it. Released migrations must stay unchanged; add a new migration for further schema changes. After editing an unpublished migration, recreate any disposable development database that already ran it.

Older published databases without checksums receive them on first startup. Each new migration and its ledger entry commit together. Concurrent startups lock and validate the history before applying a migration, so only one process applies it. Failed or interrupted migrations roll back and retry at the next startup.

Some retired tables, such as video_requests, remain for historical data. See Development → Testing for the upgrade checks.

Component Responsibility
Downloader Fetch stream segments, retry failures, process media, generate previews, and publish files to storage. Save progress for recovery.
EventSub manager Create required subscriptions, remove obsolete ones, and track quota usage.
Scheduler Run tasks at their configured intervals using database state for next run, enabled status, and errors.
Storage services Check storage identity and capacity, find missing media, restore recovered recordings, and enforce retention.
Playback cache Build joined files for compatible multipart recordings and enforce cache limits.
Recording webhook dispatcher Deliver completion and failure events from the persisted queue.

internal/playbackauth/ validates and stores the owner’s Twitch website session. Its encryption key is derived from SESSION_SECRET. The API returns connection details without returning the saved token.

Playback resolution uses the connected session when available. Expired or revoked sessions fall back to anonymous access. Temporary failures retry within a bounded period. Captured footage is saved as a failed partial recording if playback cannot recover.

The dashboard uses React, TanStack Router, TanStack Query, and a tRPC client. Queries and mutations use HTTP batching; subscriptions share a WebSocket connection. dashboard/src/api/links.ts configures that transport.

Base UI and shadcn provide UI components, with Tailwind for styling. i18next handles English and French translations. Translation tests check key parity.

During development, Vite proxies /api/* and /trpc/* to port 8080. The production build writes static files to dashboard/dist/client/, which the Go server serves on the same origin as the API.

Go tRPC procedures generate the TypeScript types, enums, and Zod schemas in dashboard/src/api/generated/. Run task trpcgen from server/ after changing those procedures.

The Connect relay uses a Cloudflare Worker and a Durable Object per token:

  • POST /u/{token} accepts notification bodies and forwards them to subscribers. Normal deliveries return 202; verification challenges wait for a subscriber’s response.
  • GET /u/{token}/subscribe opens a WebSocket and replays buffered notifications after the supplied cursor.

Notifications are buffered for five minutes. Verification challenges are synchronous and aren’t added to the replay buffer. The recorder verifies signatures; the relay forwards bodies without parsing them.

See Relay protocol for frames, acknowledgements, and token validation.