Initial commit: Project scaffolding + architecture documentation

- arc42 template fully populated in docs/architecture/README.md with Mermaid diagrams, quality goals, deployment view, cross-cutting concepts, risks and glossary
- Consolidated ADR-001 (merged all previous root ADR drafts)
- ADR-002: Module/folder structure decision and rationale
- ADR-003: Technology stack, language choices per module (TypeScript, Go, Bash/Python), Docker-first approach, and explicit implicit assumptions
- Updated root README.md with comprehensive Project Structure section and links to all ADRs
- Created .github/instructions/TODO.md with detailed tracking (architecture phase marked complete)
- Folder structure created matching ADR-002 (frontend/, backend/playout/, backend/chat/, backend/shared/, infrastructure/, scripts/, monitoring/)
- Git repository initialized with main branch

This establishes the foundation defined in the arc42 documentation and the three ADRs.
This commit is contained in:
zwitschi committed 2026-08-02 15:31:33 +02:00
commit dafcec1303
8 files changed
+1158

No files matched your search

+170
View File
@@ -0,0 +1,170 @@
# ADR-001: Cost-Efficient Self-Hosted 24/7 Synchronized Linear Video Streaming with Custom Chat (US-Centric, Cloudflare + R2 Optimized)
**Status:** Accepted
**Date:** 2026-08-02
**Authors:** Synthesized from Grok, GPT-Terra, DeepSeek, Gemini (research-assisted)
**Decision Owners:** Product & Engineering
**Scope:** One 24/7 linear video channel, custom viewer website, public/private chat, playlist-driven playout. Related ADRs: authentication, moderation, payments, multi-channel.
## Context
We require a completely independent 24/7 linear video channel that broadcasts a continuous playlist of self-owned pre-recorded videos. Viewers consume it via a lightweight custom HTML/CSS/JavaScript single-page application (no reliance on YouTube, Twitch, Vimeo, or paid Cloudflare Stream tiers). The experience must include:
- True (or near-true) synchronized playback — all viewers see the same program timeline.
- Embedded custom chat with public rooms, private 1:1/direct messages, presence, moderation (mute/ban/report), rate limiting.
- “Now playing” schedule/metadata.
- Full control over source media, frontend, schedule, and chat data.
- Primary audience and operational origin in the **United States** (East/Central preferred for uploads and low latency).
**Key distinctions**:
- **Playout**: Continuous scheduled program from playlist.
- **Packaging**: FFmpeg → standard HLS (`.m3u8` + `.ts` segments, 4–6s duration).
- **Delivery**: Immutable segments served via Cloudflare edge caching + R2 (zero egress cost).
- **Interaction**: Separate lightweight WebSocket service for chat.
Bandwidth (not storage or compute) is the dominant cost driver. A single viewer at ~4 Mbps consumes ~1.3–1.8 TB/month; 100 concurrent viewers = ~130–180 TB/month. Direct VPS delivery is only viable for PoC/small audiences.
## Decision Drivers
1. Minimize monthly cost (target <$25 for moderate scale).
2. Full independence and ownership (no video platform lock-in).
3. Strong US performance and low latency.
4. True/near-true synchronization via live-style HLS (not pure VOD).
5. Aggressive immutable caching of `.ts` segments to achieve near-zero egress.
6. Browser-native playback (`hls.js` + native HLS).
7. Lightweight, maintainable stack with modest operational complexity.
8. Support for custom multi-room + private chat with persistence and moderation.
9. Scalability from tens to thousands of concurrent viewers without linear cost growth.
10. Compliance with Cloudflare ToS (use R2 as origin for large video files).
## Decision
Adopt a **self-managed US-East playout + Cloudflare R2 + edge-cached HLS** architecture:
### 1. Video Pipeline & Playout (US-East VPS)
- **Location**: Hetzner Cloud CX22 (or equivalent) in **Ashburn, VA** (~$4–8/mo, 2 vCPU/4 GB, 40 GB NVMe, 20 TB included traffic). Excellent peering.
- **Tools**:
- FFmpeg (concat demuxer + `while true` loop for auto-restart) for playlist playout and normalization (`-c copy` where possible, CRF encoding).
- MediaMTX (preferred), SRS, or Nginx-RTMP as lightweight media server/packager for continuous HLS output.
- Sliding window HLS: 4–6s segments, playlist length ~24–60s.
- **Source Normalization** (mandatory before scheduling):
- Container: MP4
- Video: H.264/AVC, yuv420p, constant 30 fps, keyframe interval 2s
- Audio: AAC-LC, 48 kHz, stereo, 128–192 kbps
- Bitrate targets: 720p30 (2.5–5 Mbps), 1080p30 (5–8 Mbps)
- Dashboard for playlist management, current file, health.
### 2. Storage & Delivery (Cloudflare R2 + CDN)
- HLS segments (`.ts`) and manifests uploaded to **Cloudflare R2** (Eastern North America hint `enam`).
- Custom domain (`media.example.com`) fronted by Cloudflare with **Cache Rules**:
- `*.ts` (and init `.mp4` if using fMP4 later): `Cache Everything`, immutable, Edge TTL 24h–1yr (`Cache-Control: public, max-age=31536000, immutable`).
- `*.m3u8`: Short TTL (1–10s) or `must-revalidate` (`Cache-Control: public, max-age=1..10, must-revalidate`).
- Master playlist: TTL ~30s.
- Tiered Cache enabled where possible.
- **R2 as origin** satisfies ToS for large video; $0 egress, free 10 GB, then ~$0.015/GB-mo (Standard).
- Viewer player hits Cloudflare edge → vast majority of traffic served from cache (origin/R2 hit only once per region).
### 3. Frontend
- Static SPA (HTML/CSS/JS + Tailwind + Alpine.js or vanilla) hosted on Cloudflare Pages or same VPS/Nginx.
- Video player: `hls.js` (Chromium/Firefox) or native HLS (Safari). Single live `master.m3u8`.
- Embedded chat sidebar with room switcher, private DMs, now-playing overlay.
### 4. Custom Chat
- Lightweight backend (Go, Node.js + Socket.io, or Elixir) on same VPS.
- WebSockets (`wss://`), Redis (presence, pub/sub, rate limits), PostgreSQL/SQLite (message history, moderation log, users).
- Features: public rooms, private 1:1, presence, rate limiting, mute/timeout/ban/report, basic search.
- Persistence with retention policy. Moderation audit log. No E2EE by default (separate ADR if required).
### 5. Infrastructure Summary & Cost Model
- **Primary**: Hetzner Ashburn VPS (playout + chat + Nginx) + Cloudflare R2 + Pages.
- **Monitoring**: MediaMTX/SRS dashboard, UptimeRobot, systemd logs, Cloudflare analytics.
- **Backup**: Weekly R2/S3-compatible snapshots of playlist, DB, config.
- **Fallback**: Emergency slate/loop playlist, automated restart.
- **Monthly Cost (early production, moderate US audience)**: $5–25 (VPS $4–8 + R2 $1–3 + negligible requests). Scales to hundreds/thousands of viewers with near-zero additional egress.
**Initial Targets**:
- Protocol: Standard HLS (`.ts` segments). Defer LL-HLS/CMAF/WebRTC.
- Max rendition: 720p30 initially (upgrade to 1080p30 or ABR later).
- Latency: 15–35s (acceptable for linear programming/chat).
## Storage for 48 Hours of Source Footage
Formula: `GB ≈ (bitrate_Mbps × duration_seconds) / 8 / 1024` (+5–10% overhead).
| Resolution | Video Bitrate | Total w/ Audio | 48h Raw | Recommended Allocation | R2 Cost/mo (Standard) |
| ---------- | ------------- | -------------- | ---------- | ---------------------- | --------------------- |
| 720p30 | 2.5–4 Mbps | ~2.6–4.2 Mbps | 57–90 GB | ~80–110 GB | ~$1.20–$1.65 |
| 1080p30 | 5–8 Mbps | ~5.1–8.2 Mbps | 110–175 GB | ~130–180 GB | ~$1.95–$2.70 |
Plan 2–3× headroom for variants, backups, artwork, processing. Use R2 Infrequent Access if latency acceptable.
## Rationale
- **Hetzner Ashburn + Cloudflare R2** delivers unmatched price/performance for US traffic. R2’s $0 egress + edge caching eliminates the dominant bandwidth cost.
- HLS is the most compatible, cache-friendly, operationally simple protocol for one-to-many linear broadcast.
- FFmpeg + MediaMTX/SRS provides reliable playlist-driven continuous playout with low CPU.
- Immutable segment naming + targeted Cache Rules ensure high hit ratios while keeping live manifests fresh.
- Lightweight static frontend + separate lightweight chat service keeps complexity low.
- Starts simple (single VPS) and evolves (ABR, secondary origins, managed DBs, LL-HLS) without breaking changes.
This hybrid synthesizes the strongest elements from all evaluated ADRs while resolving minor variances (Hetzner vs generic VPS, direct origin vs R2, SQLite vs PostgreSQL+Redis, exact bitrates).
## Considered Alternatives
- **Pure VPS origin (no R2/CDN)**: Rejected for production (20 TB cap limits ~12–20 concurrent HD viewers).
- **Managed platforms (YouTube/Twitch/Cloudflare Stream)**: Rejected (loss of control, higher cost at scale, ToS restrictions).
- **WebRTC / P2P**: High complexity, poor caching, not suited for passive linear broadcast.
- **Owncast**: Good for turnkey but less flexible for custom playlist/schedule/chat.
- **LL-HLS or CMAF initially**: Deferred (added complexity; standard HLS sufficient for v1).
## Consequences
**Positive**:
- Near-zero egress cost → unlimited viewer scaling.
- Full ownership, US performance, synchronized linear experience.
- Extremely low TCO (~$5–25/mo).
- Cacheable, maintainable, evolvable architecture.
**Negative / Risks & Technical Debt**:
- Cloudflare ToS scrutiny for heavy video (mitigated by R2 path; monitor & have BunnyCDN fallback).
- HLS latency (15–35s) affects real-time chat feel.
- Single-origin SPOF (mitigate with DNS failover/secondary region later).
- Video normalization and cache-rule tuning required upfront.
- Chat moderation and abuse prevention operational burden.
- R2 request costs on cache misses (measure in production; Tiered Cache helps).
- No built-in E2EE for private chats (future ADR).
**Operational Requirements**:
- Monitor playout health, segment generation, cache hit ratio, R2 ops, WebSocket connections.
- Maintain fallback slate, emergency playlist, backup/restore procedures.
- Strict CORS, HTTPS, WAF, rate limiting, authenticated admin paths.
## Implementation Plan
**Phase 1 (Prototype)**: Hetzner VPS, normalized playlist, FFmpeg+MediaMTX → HLS to R2, basic hls.js page + Socket.io chat, cache rules, load test.
**Phase 2 (MVP)**: Scheduler/metadata, PostgreSQL+Redis, full moderation, dashboards/alerts, emergency fallback.
**Phase 3 (Scale)**: ABR, multi-region, LL-HLS evaluation, paid access controls.
## References
- Cloudflare R2 pricing, custom domains, cache rules, Tiered Cache.
- MediaMTX / SRS / FFmpeg HLS documentation.
- Hetzner Cloud Ashburn pricing (2026).
- hls.js, Video.js, standard HLS best practices.
- Video bitrate/storage calculators.
---
_This consolidated ADR merges all prior drafts. Future ADRs will detail Docker Compose, exact cache rules, playlist normalization scripts, chat schema, monitoring, and access control._
@@ -0,0 +1,94 @@
# ADR-002: Project Module / Folder Structure
**Status:** Accepted
**Date:** 2026-08-02
**Author:** AI Assistant (based on arc42 analysis)
**Related ADRs:** [ADR-001](./ADR-001.md), [ADR-003](./ADR-003-tech-stack.md)
## Context
The arc42 documentation in `docs/architecture/README.md` defines several clear building blocks:
- Playout Service (FFmpeg + MediaMTX/SRS, playlist handling, normalization)
- Chat Backend (WebSocket server, rooms, DMs, moderation, Redis/DB)
- Frontend SPA (hls.js player, chat UI, static assets)
- Infrastructure / Delivery (Nginx, Cloudflare R2/CDN rules, Docker, IaC)
- Supporting concerns (scripts for normalization/sync/backup, monitoring, shared utilities)
We need a folder layout that makes these modules obvious, supports independent development/deployment (Docker), keeps the root clean, and aligns with the high-level context and deployment diagrams.
## Decision Drivers
1. Clear mapping to arc42 building blocks and runtime/deployment views.
2. Easy separation for frontend vs backend services.
3. Docker / container friendliness (one Dockerfile per major service).
4. Support for scripts, infrastructure-as-code, and monitoring configs.
5. Minimal cognitive load for new contributors (standard `frontend/`, `backend/`, `infrastructure/` layout).
6. Future extensibility (multi-channel, ABR, LL-HLS, paid features).
## Decision
Adopt the following structure (created in the workspace):
```text
linearvoid/
├── frontend/ # Static SPA (HTML/CSS/JS + hls.js + chat UI)
├── backend/
│ ├── playout/ # Continuous HLS playout engine (FFmpeg + MediaMTX/SRS)
│ ├── chat/ # WebSocket chat service (Go/Node + Redis + DB)
│ └── shared/ # Common utilities, types, normalization logic
├── infrastructure/ # Docker Compose, Nginx, Cloudflare rules, IaC
├── scripts/ # Normalization, R2 sync, backup, deployment scripts
├── monitoring/ # Dashboards, Prometheus rules, Grafana
├── docs/architecture/
│ ├── README.md # arc42 (populated)
│ └── adr/
│ ├── ADR-001.md
│ ├── ADR-002-module-structure.md
│ └── ADR-003-tech-stack.md
├── .env.example
├── docker-compose.yml
├── README.md
└── .gitignore
```
**Key conventions**:
- `backend/<service>/` contains its own `src/`, `config/`, and `Dockerfile`.
- `infrastructure/` owns all deployment and cloud configuration.
- `scripts/` holds automation that is not part of a running service.
- All documentation lives under `docs/architecture/`.
## Rationale
- **Alignment with arc42**: Each top-level folder corresponds to a major building block or cross-cutting concern shown in the context and deployment diagrams.
- **Developer Experience**: Clear separation reduces merge conflicts and makes it obvious where to add player logic vs chat logic vs cache rules.
- **Operational Simplicity**: Docker Compose can easily orchestrate `playout`, `chat`, and `nginx` services from the root.
- **Scalability**: Services can be split into separate repositories or Kubernetes deployments later without major refactoring.
- **Alternatives Considered**:
- Flat `src/` structure → rejected (hard to see architectural boundaries).
- Monorepo with `apps/` and `packages/` (Turborepo style) → overkill for current scale.
- All-in-one `services/` folder → less explicit than `backend/playout` vs `backend/chat`.
This structure was created via the `create_directory` tool and is reflected in the updated root and architecture READMEs.
## Consequences
**Positive**:
- Excellent traceability from architecture docs → code.
- Easy to add new modules (e.g., `backend/scheduler` later).
- Supports the normalization-first and caching-first principles from ADR-001.
**Negative / Risks**:
- Slightly more directories than a minimal prototype (acceptable trade-off).
- Need to keep documentation in sync when structure evolves (mitigated by ADRs).
**Future**: Next ADRs will document Docker Compose layout (ADR-004) and API contracts between services.
## References
- `docs/architecture/README.md` (Building Block View, Deployment View, Context Diagram)
- ADR-001 (overall architecture and technology constraints)
- ADR-003 (language and runtime choices per module)
@@ -0,0 +1,87 @@
# ADR-003: Technology Stack, Languages, and Implicit Assumptions
**Status:** Accepted
**Date:** 2026-08-02
**Author:** AI Assistant (based on arc42 and ADR-001)
**Related ADRs:** [ADR-001](./ADR-001.md), [ADR-002](./ADR-002-module-structure.md)
## Context
From ADR-001 and the arc42 documentation we have the following constraints and preferences:
- Lightweight, cost-efficient, maintainable stack.
- Strong preference for US-East hosting (Hetzner), Cloudflare R2/CDN, HLS.
- Playout must be reliable and low-CPU (FFmpeg + MediaMTX/SRS).
- Chat needs real-time WebSockets, persistence, moderation, and presence.
- Frontend must be a minimal static SPA with good browser compatibility.
- Operational simplicity and easy monitoring are critical.
- Implicit assumptions include Docker for deployment, normalization of all video assets, heavy reliance on Cloudflare caching, and a focus on immutable infrastructure.
We must explicitly document language/runtime choices per module and the assumptions that were made.
## Decision
**Chosen Stack:**
| Layer | Technology | Language | Rationale |
| ------------------- | ------------------------------------------ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Frontend SPA** | HTML + Tailwind + Alpine.js + hls.js | TypeScript / JavaScript | Minimal bundle, excellent HLS support, fast iteration. Vite for build. |
| **Playout Service** | FFmpeg + MediaMTX (or SRS) | Bash + Go (wrapper) | FFmpeg is the proven tool for continuous playlist HLS. Go wrapper for reliability and monitoring. |
| **Chat Backend** | Go (preferred) or Node.js + Socket.io | Go | High-performance WebSockets, low memory, excellent Redis/PostgreSQL clients. Node.js acceptable for rapid prototyping. |
| **Shared Code** | TypeScript definitions + Go structs | TS + Go | Clear contracts between frontend and backend. |
| **Infrastructure** | Docker, Docker Compose, Nginx, Cloudflare | YAML + Bash | Standard, reproducible, Cloudflare-native. |
| **Scripts** | Bash (with Python helpers where needed) | Bash/Python | Simple, no extra runtime for normalization and R2 sync. |
| **Monitoring** | Prometheus + Grafana + UptimeRobot | - | Standard open-source stack that integrates well with Docker. |
| **Storage** | Cloudflare R2 (primary), SQLite/PostgreSQL | - | $0 egress, simple for chat history. |
**Implicit Assumptions** (now made explicit):
1. All video assets **must** be normalized before being added to any playlist (H.264, AAC, 30 fps constant, 2s keyframes, CRF encoding).
2. Deployment is **Docker-first**; services will be containerized.
3. Cloudflare is the primary CDN and will be configured with specific Cache Rules (`.ts` immutable long TTL, `.m3u8` short TTL).
4. Chat does **not** require end-to-end encryption in v1 (separate ADR if needed).
5. Single VPS origin initially; multi-region is a later evolution.
6. Monitoring focuses on playout health, cache hit ratio, and R2 operation counts.
7. Development environment uses `docker compose` for local testing of playout + chat + frontend.
## Decision Drivers
- Match the low-resource, high-reliability requirements from ADR-001.
- Minimize new language overhead (Go for performance-critical services, JS/TS for frontend and rapid scripting).
- Leverage mature tools (FFmpeg, hls.js, Redis) rather than reinventing.
- Keep operational complexity low (Docker + Bash scripts).
## Alternatives Considered
- **Full Node.js stack**: Simpler for small team but higher memory usage for WebSockets and less efficient HLS packaging.
- **Python for everything**: Excellent for scripting but poor real-time WebSocket performance.
- **Rust for chat/playout**: Overkill for initial velocity.
- **Separate repositories per service**: Considered but rejected for early stage (monorepo with clear folders is better).
Go was chosen for the chat backend because it offers excellent concurrency, small binaries, and native performance for WebSockets and Redis.
## Consequences
**Positive**:
- Clear, justified choices that align with cost, performance, and maintainability goals.
- Easy onboarding — developers know exactly which language belongs where.
- Explicit documentation of assumptions prevents future architectural drift.
**Risks**:
- Go learning curve if team is primarily JS (mitigated by allowing Node.js fallback for chat).
- Assumption that normalization scripts will be followed (enforce via CI/validation in playout service).
**Future ADRs**:
- ADR-004: Docker Compose and deployment pipeline.
- ADR-005: Monitoring and alerting strategy.
- ADR-006: API contracts between playout, chat, and frontend.
## References
- ADR-001 (overall architecture, HLS, Cloudflare caching, normalization requirements)
- ADR-002 (folder structure that this stack maps into)
- arc42 documentation (`docs/architecture/README.md`) – Building Block View, Deployment View, Cross-cutting Concepts
- MediaMTX, hls.js, Go WebSocket best practices, Cloudflare R2 caching documentation