Files
linearvoid/docs/architecture/README.md
T
zwitschi fcb68cfd8d
CI / test (push) Successful in 1m39s
CI / docker (push) Successful in 3m9s
CI / release (push) Skipped
fix: add curl to chat runtime image for healthcheck
2026-08-04 11:00:49 +02:00

23 KiB
Raw Blame History

Architecture Documentation for Linear Void

The architecture documentation for the Linear Void project is structured according to the arc42 template. This template provides a comprehensive framework for documenting software architecture, ensuring that all relevant aspects of the system are covered.

Table of Contents

Introduction and Goals

Requirements Overview

Linear Void is an independent 24/7 linear video channel broadcasting a playlist of self-owned pre-recorded videos. Viewers access it via a custom lightweight HTML/CSS/JavaScript SPA with synchronized HLS playback, schedule metadata, public chat rooms, and private 1:1 messaging. Complete independence from third-party video platforms (YouTube, Twitch, etc.).

Core Requirements (merged from all ADRs):

  • Continuous synchronized playback from playlist (live-style HLS, not VOD).
  • Self-hosted source media, frontend, schedule, and chat data.
  • Primary US audience/origin (East Coast preferred for low latency/peering).
  • Extreme cost efficiency (bandwidth dominant; target near-zero egress via Cloudflare R2 + edge caching).
  • Custom chat with rooms, DMs, presence, moderation, persistence.
  • Storage planning for 48h of 720p30/1080p30 content.
  • Browser-compatible playback (hls.js + native HLS).
  • Operational simplicity and scalability.

Full decision rationale, alternatives, and consequences in adr/ADR-001.md.

Quality Goals

Priority Quality Attribute Goal Scenario
1 Cost Efficiency <$25/month for moderate scale (100s concurrent) R2 $0 egress + immutable .ts edge caching eliminates per-viewer bandwidth cost
2 Performance / Latency Low US latency; acceptable HLS delay Hetzner Ashburn origin + Cloudflare US POPs (15–35s end-to-end)
3 Availability 24/7 with minimal downtime FFmpeg/MediaMTX auto-restart loop, fallback slate, monitoring
4 Scalability Grow from 10s to 1000s viewers without proportional cost Edge caching + R2; only request volume limits
5 Maintainability Lightweight, clear configs, low ops overhead Static SPA, Go/Node chat, SQLite/PostgreSQL+Redis
6 Security / Usability Secure transport, moderation tools, good UX HTTPS/WSS, Cloudflare WAF, rate limits, intuitive chat/player

Stakeholders

Role/Name Contact Expectations
Product Owner - Cost-effective independent 24/7 channel with synchronized viewing and rich chat
Engineering Team - Simple, evolvable stack (FFmpeg, HLS, Cloudflare R2, lightweight WebSocket chat)
US Viewers - Reliable low-latency playback, responsive synchronized chat experience
Moderators / Admins - Easy playlist management, moderation dashboard, monitoring/alerting
Operations - Low TCO, clear cost model, automated failover, backups

(See adr/ADR-001.md for detailed forces, decision drivers, and rationale.)

Architecture Constraints

  • Independence from video hosting platforms (no YouTube, Twitch, paid Cloudflare Stream).
  • US-centric (origin + audience).
  • Cost must remain low even at scale (bandwidth dominant).
  • Standard browser playback only (no plugins).
  • Cloudflare ToS compliance for video delivery (use R2 as origin for large immutable files).
  • HLS as primary protocol (standard, not LL-HLS initially).
  • Self-hosted playlist normalization and continuous playout required.

(See adr/ADR-001.md for full forces and trade-offs.)

Context and Scope

Business Context

Linear Void provides a continuously running scheduled video program (playlist-driven) to a primarily US audience. The custom viewer site replaces traditional platforms, giving full control over content, schedule, UI, and real-time chat (public rooms + private DMs).

High-Level Context Diagram (Mermaid)

flowchart TD
    subgraph "External Users"
        Viewers[US Viewers<br/>Browser SPA + hls.js + Chat UI]
        Admins[Admins / Moderators<br/>Playlist & Schedule UI]
    end

    subgraph "Cloudflare Global Edge"
        CDN[Cloudflare CDN<br/>Cache Rules + WAF + TLS]
        Pages[Cloudflare Pages<br/>Static SPA Hosting]
        R2[Cloudflare R2<br/>HLS Segments + Source MP4s<br/>enam location hint]
    end

    subgraph "US-East Origin (Hetzner Ashburn VPS)"
        VPS[VPS CX22<br/>Nginx Proxy + Monitoring]
        Playout[Playout Service<br/>FFmpeg + MediaMTX/SRS<br/>Continuous HLS from Playlist]
        Chat[Chat Backend<br/>WebSockets + Redis + DB<br/>Public rooms + Private DMs]
    end

    Admins -->|HTTPS - Schedule/Playlist| Playout
    Playout -->|S3-compatible Upload| R2
    R2 -->|Cached HLS| CDN
    Viewers -->|HTTPS| Pages
    Viewers -->|HLS Requests| CDN
    Viewers -->|WSS Chat| Chat
    Chat --> VPS
    VPS -->|Proxy / Static| Pages
    CDN -->|Origin Pull on Miss| R2

    classDef cloud fill:#f0f0ff,stroke:#4263eb
    classDef vps fill:#f0fff0,stroke:#2e7d32
    classDef user fill:#fff0f0,stroke:#c62828
    class Cloudflare,CDN,Pages,R2 cloud
    class VPS,Playout,Chat vps
    class Viewers,Admins user

External Interfaces:

  • Cloudflare: CDN (cache rules for .ts vs .m3u8), R2 (object storage, $0 egress), Pages (static hosting), WAF/TLS.
  • Hetzner VPS: Compute for playout and chat (low-cost US-East with good peering).
  • Browsers: Native HLS (<video> + hls.js) and WebSocket APIs.
  • Source: Pre-normalized MP4 files uploaded to R2.

This diagram synthesizes the context views from all original ADRs. Full decision details, cache rules, storage calculations, and rationale are in adr/ADR-001.md.

Technical Context

  • Playout: FFmpeg consuming normalized playlist → continuous HLS.
  • Packaging/Delivery: MediaMTX/SRS/Nginx → immutable .ts segments to R2; Cloudflare edge cache.
  • Frontend: Static SPA with hls.js.
  • Chat: Lightweight WebSocket service (Node/Go) + Redis (presence/rate limits) + PostgreSQL/SQLite (history/moderation).
  • Normalization: All source videos must be H.264/AAC, 30 fps constant, 2s keyframes.

Mapping:

  • Input: Normalized MP4s in R2 + playlist.
  • Output: Live master.m3u8 + segments served via Cloudflare; chat via WSS.

Solution Strategy

Core Strategy (synthesized & accepted from all ADRs):

  1. US-East Playout on Hetzner Ashburn VPS (CX22 or equivalent): FFmpeg + MediaMTX/SRS for continuous playlist-to-HLS conversion (-c copy where possible).
  2. Immutable HLS to Cloudflare R2: Segments use unique names; R2 acts as origin with $0 egress.
  3. Aggressive Edge Caching: Long TTL for .ts (immutable), short TTL (1–10s) for live .m3u8. Tiered Cache enabled. This makes viewer delivery essentially free.
  4. Lightweight Custom Frontend: Static HTML/JS SPA + hls.js player + embedded chat sidebar.
  5. Separate but co-located Chat Service: WebSockets with persistence and moderation (low bandwidth).
  6. Normalization & Monitoring: Pre-process all videos; use dashboards + alerts for playout health, cache hit ratio, R2 ops.

This eliminates egress economics, ensures synchronization via live playlist, keeps ops simple, and scales well. See adr/ADR-001.md for detailed cache rules, storage tables, cost model, configs (FFmpeg/Nginx examples), and phase-by-phase implementation plan.

Key Trade-off Accepted: Standard HLS latency (15–35s) is acceptable for linear programming/chat. LL-HLS/WebRTC deferred.

Building Block View

Whitebox Overall System

Overview (Mermaid diagram):

graph TD
    subgraph Cloudflare
        CDN[CDN + Cache Rules<br/>.ts immutable long TTL<br/>.m3u8 short TTL]
        R2[R2 - HLS + Source]
        Pages[Pages - Static SPA]
    end
    VPS[VPS Hetzner Ashburn]
    Playout[FFmpeg + MediaMTX/SRS<br/>Playlist → HLS]
    Chat[WebSocket Chat<br/>Node/Go + Redis + DB]
    Viewer[US Viewers]
    Admin[Admin UI]

    Admin --> Playout
    Playout --> R2
    R2 --> CDN
    Viewer --> Pages
    Viewer --> CDN
    Viewer --> Chat
    Chat --> VPS
    VPS --> Nginx[Nginx Proxy]

Motivation: Decouple playout (continuous linear), delivery (cached immutable segments), and interaction (chat). Cloudflare R2 + edge caching solves the bandwidth cost problem while maintaining synchronization and independence.

Contained Building Blocks (high-level):

  • Playout: FFmpeg + MediaMTX/SRS (continuous HLS from playlist).
  • Storage/Delivery: Cloudflare R2 + CDN (immutable segments).
  • Frontend: Static SPA (hls.js player + chat UI).
  • Chat Service: WebSocket backend with persistence/moderation.
  • Proxy/Static: Nginx on VPS.

Important Interfaces: HTTP(S) for HLS, WSS for chat, S3-compatible for R2 uploads.

Detailed black-box descriptions, level-2/3 views, and code locations are in adr/ADR-001.md and future implementation ADRs.

Level 2 & 3

Deferred to implementation (see ADR for component responsibilities, interfaces, quality attributes, and risks). Playout enforces normalization; Delivery layer focuses on cache rules and immutable naming.

Runtime View

Playout & Delivery Scenario

  1. Admin adds normalized MP4 to R2 + updates playlist.
  2. Playout worker (FFmpeg loop with concat demuxer) reads next file, packages as HLS segments (4-6s).
  3. Segments uploaded to R2 with unique immutable names; live .m3u8 updated.
  4. Viewer loads SPA → requests master/live manifest → hls.js player follows current edge.
  5. Cloudflare serves .ts from nearest edge cache (origin/R2 hit only once per POP/region).
  6. Parallel WebSocket connection to chat service for synchronized interaction.

Notable aspects: Manifest freshness ensures all viewers stay on same timeline; cache hit ratio directly impacts cost and latency. Fallback slate activates on playout failure. Full cache policy, example configs (FFmpeg bash loop, Nginx RTMP HLS), and storage tables in adr/ADR-001.md.

(Additional scenarios: moderation events, scale spikes, failover covered in the ADR.)

Deployment View

CI/CD Pipeline Architecture

The deployment follows a two-stage pipeline: CI builds and pushes images, then CD pulls and deploys them to the production host.

flowchart LR
    subgraph "Gitea Actions"
        CI[CI Workflow<br/>Build + Push Images]
        CD[CD Workflow<br/>Pull + Deploy]
    end
    subgraph "Gitea Container Registry"
        REGISTRY[git.allucanget.biz<br/>playout:latest<br/>chat:latest<br/>nginx:latest]
    end
    subgraph "Production Host (192.168.88.40)"
        DOCKER[Docker Engine<br/>via TCP :2375]
        PLAYOUT[linearvoid-playout]
        CHAT[linearvoid-chat]
        NGINX[linearvoid-nginx]
        REDIS[linearvoid-redis]
        DB[linearvoid-db]
        PROM[linearvoid-prometheus]
        GRAF[linearvoid-grafana]
    end

    CI -->|push| REGISTRY
    REGISTRY -->|pull| CD
    CD -->|deploy via DOCKER_HOST| DOCKER
    DOCKER --> PLAYOUT
    DOCKER --> CHAT
    DOCKER --> NGINX
    DOCKER --> REDIS
    DOCKER --> DB
    DOCKER --> PROM
    DOCKER --> GRAF

    classDef ci fill:#e1f5fe,stroke:#0288d1
    classDef reg fill:#fff3e0,stroke:#f57c00
    classDef prod fill:#e8f5e9,stroke:#388e3c
    class CI,CD ci
    class REGISTRY reg
    class DOCKER,PLAYOUT,CHAT,NGINX,REDIS,DB,PROM,GRAF prod

Pipeline Sequence:

  1. CI Workflow (.gitea/workflows/ci.yml):
    • Triggers on push to main or pull_request
    • Runs test job: frontend lint/build, Go validation, docker compose build
    • Runs docker job (after test passes, only on main push):
      • Builds playout, chat, nginx images with Buildx
      • Pushes to Gitea Container Registry with main and latest tags
  2. CD Workflow (.gitea/workflows/cd.yml):
    • Triggers on workflow_run when CI completes successfully, or manually via workflow_dispatch
    • Pulls latest images from registry
    • Generates .env from secrets
    • Seeds Prometheus config into named volume
    • Deploys via docker compose -f docker-compose.prod.yml up -d to remote host (192.168.88.40:2375)
    • Runs health checks with retry logic for playout initialization

Infrastructure Level 1

Overview Diagram: See Mermaid in Building Block section (VPS + Cloudflare R2/Pages/CDN).

Motivation: Low-cost US-East origin with global edge caching. Hetzner provides excellent price/performance and included traffic; Cloudflare eliminates egress fees.

Quality/Performance Features: High cache-hit ratio for video, low US latency, automatic scaling at edge, built-in WAF/DDoS protection.

Mapping of Building Blocks to Infrastructure:

  • Playout + Chat + Nginx → Hetzner Cloud CX22 (Ashburn, VA).
  • HLS storage, caching, static SPA → Cloudflare R2 + CDN + Pages (custom domains, enam location hint).
  • All public traffic routed through Cloudflare.

Infrastructure Level 2

Hetzner VPS: Runs playout (FFmpeg/MediaMTX), chat backend, Nginx. Monitoring via dashboards + UptimeRobot. Backups to R2.

Cloudflare Layer:

  • R2 bucket for media.
  • Cache Rules (.ts: Cache Everything, immutable, long TTL; .m3u8: short TTL/must-revalidate).
  • WAF, rate limiting, Tiered Cache.
  • Pages for SPA hosting.

Future evolution: Docker Compose, managed DB, secondary regions (detailed in subsequent ADRs).

Cross-cutting Concepts

Caching & Cost Optimization

Immutable segment naming + targeted Cloudflare Cache Rules + R2 ($0 egress) = near-zero viewer bandwidth cost. Long TTL on .ts, short on live manifests. Monitor hit ratio and R2 Class B ops.

Video Normalization & Playout

All source assets must be pre-normalized (H.264/AVC, AAC-LC, constant 30 fps, 2s keyframe interval, CRF). Prevents glitches during continuous concat. FFmpeg -c copy preferred for efficiency.

Monitoring & Resilience

Alert on no new segment (>30–60s), low cache hits, high R2 ops, WebSocket errors. Maintain emergency slate, auto-restart, weekly backups, incident runbook.

Security & Moderation

HTTPS/WSS only, strict CORS, Cloudflare WAF, authenticated admin paths, rate limiting, immutable moderation log. Private DMs not E2EE by default.

Chat

WebSockets + Redis (presence, pub/sub, rate limits) + DB (history, audit). Public rooms + on-demand private chats. Scales independently of video.

Full details, cache rule table, bitrate/storage calculations, cost model, example configs, ToS notes, and implementation phases are in adr/ADR-001.md.

Architecture Decisions

The primary decisions are captured in the following consolidated ADRs (all located in adr/):

  • ADR-001: Overall architecture, HLS + Cloudflare R2 caching strategy, storage calculations, cost model, risks.
  • ADR-002: Chosen project folder/module structure (frontend, backend/playout, backend/chat, infrastructure, scripts, monitoring) and rationale.
  • ADR-003: Technology stack, programming language choices per module (TypeScript frontend, Go for chat/playout wrapper, Bash/Python scripts), Docker-first approach, and explicit documentation of all implicit assumptions.
  • ADR-004: Video admin web interface (upload, playlist management, schedule overrides, normalization enforcement).
  • ADR-005: Web admin interfaces for user management, moderation dashboard, analytics, and system configuration.
  • ADR-006: Data storage strategy (SQLite vs PostgreSQL for v1, migration path, admin interface capabilities).
  • ADR-007: Redis usage (exact use cases, TTL policies, persistence, fallback behavior, admin configuration).

These ADRs together form the canonical decision log.

Summary of Key Decisions (from ADR-001 through ADR-007):

  • Self-managed US-East playout (Hetzner Ashburn) with FFmpeg + MediaMTX/SRS → HLS → Cloudflare R2 + edge caching.
  • Module structure that directly reflects the arc42 building blocks and context diagram.
  • Go preferred for performance-critical services; TypeScript for frontend; Docker for all deployment.
  • Admin interfaces for video management, moderation, analytics, and system configuration using HTMX + Tailwind.
  • SQLite as primary database for v1 with PostgreSQL migration path if needed.
  • Redis for rate limiting, presence, pub/sub, session storage with defined TTL policies and fallback behavior.

Quality Requirements

See Quality Goals table in the Introduction section. Primary scenarios focus on cost (near-zero egress), availability (auto-restart + fallback), scalability (edge caching), and usability (synchronized player + responsive chat).

Risks and Technical Debts

  • Cloudflare ToS risk for heavy video on free tier (use R2 path; fallback to BunnyCDN if challenged).
  • HLS latency (15–35s) may affect perceived chat synchronization.
  • Single VPS as SPOF (mitigate with DNS failover in Phase 3).
  • Upfront effort for video normalization and cache tuning.
  • Moderation/abuse prevention operational load at scale.
  • R2 request costs on cache misses (optimize with Tiered Cache; measure in prod).
  • Deferred items: LL-HLS, ABR, E2EE, paid access controls, full Docker Compose.

Technical Debt: Detailed service configs, CI/CD, and advanced resilience are left for implementation ADRs.

Glossary

Term Definition
HLS HTTP Live Streaming – protocol using .m3u8 manifests and media segments (.ts or fMP4).
Immutable Segment HLS chunk with unique, unchanging name enabling long-lived caching.
R2 Cloudflare object storage ($0 egress, ideal origin for cached HLS).
Edge Cache / POP Cloudflare points-of-presence that cache and serve content close to viewers.
Playout Continuous generation of a scheduled linear program from a playlist.
Normalization Pre-processing videos to consistent codec/fps/keyframe settings for seamless playback.
Cache Rule Cloudflare configuration defining caching behavior, TTL, and headers per URL pattern.
SPOF Single Point of Failure (current single VPS origin).
Tiered Cache Cloudflare feature reducing origin requests by checking upper-tier caches first.

References: Cloudflare R2/Cache docs, MediaMTX/SRS/FFmpeg HLS guides, Hetzner pricing, hls.js documentation, standard video bitrate calculators. All original root ADR-001-*.md files have been consolidated and can be archived/deleted.

Whitebox Overall System

<Overview Diagram>

Motivation

<text explanation>

Contained Building Blocks

<Description of contained building block (black boxes)>

Important Interfaces

<Description of important interfaces>

<Name black box 1>

<Purpose/Responsibility>

<Interface(s)>

<(Optional) Quality/Performance Characteristics>

<(Optional) Directory/File Location>

<(Optional) Fulfilled Requirements>

<(optional) Open Issues/Problems/Risks>

<Name black box 2>

<black box template>

<Name black box n>

<black box template>

<Name interface 1>

...​

<Name interface m>

Level 2

White Box <building block 1>

<white box template>

White Box <building block 2>

<white box template>

...​

White Box <building block m>

<white box template>

Level 3

White Box <_building block x.1_>

<white box template>

White Box <_building block x.2_>

<white box template>

White Box <_building block y.1_>

<white box template>

Runtime View

<Runtime Scenario 1>

  • <insert runtime diagram or textual description of the scenario>

  • <insert description of the notable aspects of the interactions between the building block instances depicted in this diagram.>

<Runtime Scenario 2>

...​

<Runtime Scenario n>

Deployment View

Infrastructure Level 1

<Overview Diagram>

Motivation

<explanation in text form>

Quality and/or Performance Features

<explanation in text form>

Mapping of Building Blocks to Infrastructure

<description of the mapping>

Infrastructure Level 2

<Infrastructure Element 1>

<diagram + explanation>

<Infrastructure Element 2>

<diagram + explanation>

...​

<Infrastructure Element n>

<diagram + explanation>

Cross-cutting Concepts

<Concept 1>

<explanation>

<Concept 2>

<explanation>

...​

<Concept n>

<explanation>

Architecture Decisions

Quality Requirements

Quality Requirements Overview

Quality Scenarios

Risks and Technical Debts

Glossary

+----------------------+-----------------------------------------------+ | Term | Definition | +======================+===============================================+ | <Term-1> | <definition-1> | +----------------------+-----------------------------------------------+ | <Term-2> | <definition-2> | +----------------------+-----------------------------------------------+