# Linear Void [Linear Void](https://linearvoid.tv) is a self-managed **24/7 linear video channel** for a primarily US audience. It runs a continuous playlist of self-owned videos with true/near-true synchronization, a custom lightweight viewer SPA (hls.js player + chat), and full independence from third-party platforms (YouTube, Twitch, etc.). **Repository**: [git.allucanget.biz/zwitschi/linearvoid](https://git.allucanget.biz/zwitschi/linearvoid) [![Build Status](https://git.allucanget.biz/zwitschi/linearvoid/actions/workflows/ci.yml/badge.svg)](https://git.allucanget.biz/zwitschi/linearvoid/actions) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Gitea](https://img.shields.io/badge/Gitea-1.27.1-blue)](https://git.allucanget.biz) [![Version](https://img.shields.io/badge/version-1.0.11-blue)](https://git.allucanget.biz/zwitschi/linearvoid/releases) [![Release Notes](https://img.shields.io/badge/release-notes-green)](CHANGELOG.md) ## Architecture The architecture (detailed in [`docs/architecture/README.md`](docs/architecture/README.md)) uses: - Hetzner Ashburn VPS for playout (FFmpeg + MediaMTX/SRS) and chat _(planned production; currently test lab at 192.168.88.40)_. - Cloudflare R2 + CDN with aggressive immutable caching of `.m4s` segments ($0 egress). - Lightweight static frontend and WebSocket chat backend. - Gitea self-hosted CI/CD (Actions) and Container Registry. See [`docs/architecture/adr/`](docs/architecture/adr/) for the full set of decision records (ADR-001 through ADR-007) covering architecture, storage calculations, cache rules, cost model, module structure, tech stack, admin interfaces, data storage, and caching strategy. ## Key Features - Continuous synchronized playback from a playlist (standard HLS, 15–35s latency). - Self-hosted custom SPA with embedded chat (public rooms + private 1:1 DMs, moderation). - Extreme cost efficiency via Cloudflare R2 edge caching. - Video normalization, monitoring, fallback slate, and easy playlist management. - US-centric performance (Ashburn origin + Cloudflare POPs). ## Architecture Diagram ```mermaid flowchart TD V[US Viewers] -->|HTTPS| CF[Cloudflare CDN] CF -->|Cache HIT| R2[Cloudflare R2] CF -->|Cache MISS| N[Nginx :80] N -->|/ws| CH[Chat :3000] N -->|/admin| CH N -->|/hls| P[Playout :8081] N -->|/*| FE[Frontend SPA] CH --> R[(Redis)] CH --> DB[(PostgreSQL)] P -->|ffmpeg| HLS[/data/hls/] P -->|sync| R2 ``` ## Project Structure ```text linearvoid/ ├── frontend/ # Static Single-Page Application (HTML/CSS/JS) │ ├── src/ │ │ ├── components/ # Video player, chat sidebar, now-playing overlay │ │ ├── js/ # hls.js integration, WebSocket client, UI logic │ │ └── styles/ # Tailwind / custom CSS │ ├── index.html │ └── package.json # Build tools (Vite recommended) ├── backend/ │ ├── playout/ # Continuous HLS generation from playlist │ │ ├── src/ # FFmpeg orchestration, playlist manager │ │ ├── config/ │ │ └── Dockerfile │ ├── chat/ # Real-time WebSocket service │ │ ├── src/ # Rooms, DMs, moderation, presence (Node/Go) │ │ ├── config/ │ │ └── Dockerfile │ └── shared/ # Common utilities, types, normalization helpers ├── infrastructure/ # Deployment, IaC, Cloudflare configs │ ├── docker-compose.yml # Local/dev orchestration │ ├── nginx/ # Proxy, static serving, HLS delivery │ ├── cloudflare/ # Cache rule examples, Workers │ └── terraform/ # (optional) provisioning ├── scripts/ # Automation scripts │ ├── normalize-video.sh # Pre-process MP4s (codec, keyframes, CRF) │ ├── sync-to-r2.sh # Upload segments/manifests to R2 │ └── backup.sh ├── monitoring/ # Dashboards, alerts, logging │ ├── grafana/ │ └── prometheus/ ├── docs/ │ └── architecture/ │ ├── README.md # arc42 template (fully populated) │ └── adr/ │ ├── ADR-001.md # Consolidated decision record │ ├── ADR-002-module-structure.md │ ├── ADR-003-tech-stack.md │ ├── ADR-004-admin-video.md │ ├── ADR-005-admin-moderation-analytics.md │ ├── ADR-006-data-storage.md │ └── ADR-007-redis-usage.md ├── .env.example ├── .gitignore ├── docker-compose.yml # Top-level for dev (playout + chat + nginx) ├── README.md └── LICENSE ``` ### Folder Explanations - **`frontend/`**: The viewer experience — minimal, modern SPA with Tailwind + Alpine.js (or vanilla). Uses `hls.js` for playback and a WebSocket client for chat. - **`backend/playout/`**: Core linear engine. FFmpeg consumes a normalized playlist and outputs continuous HLS. Includes normalization validation. - **`backend/chat/`**: Independent WebSocket service (Node.js/Socket.io or Go). Handles public rooms, private DMs, presence (Redis), persistence (PostgreSQL/SQLite), rate limiting, and moderation. - **`backend/shared/`**: Reusable code (types, utils, video validation). - **`infrastructure/`**: Everything needed to deploy and configure the stack (Docker, Nginx, Cloudflare rules, future Terraform). - **`scripts/`**: One-off and CI-friendly scripts for video preparation, R2 sync, backups, and playlist management. - **`monitoring/`**: Grafana dashboards, Prometheus rules, logging configuration. - **`docs/architecture/`**: Complete arc42 documentation + ADRs 001-007 (the single source of truth for all architectural decisions). This structure directly reflects the building blocks, runtime flows, deployment view, and cross-cutting concepts defined in the arc42 documentation. ## Getting Started > **Note:** The `docker compose up` below targets local development. The test lab (192.168.88.40) receives automated CD deploys. Production Hetzner VPS is not yet provisioned — see `DEPLOYMENT.md`. ```bash git clone https://git.allucanget.biz/zwitschi/linearvoid.git cd linearvoid cp .env.example .env # Edit .env with your R2 credentials and domain settings docker compose up --build ``` Then open: - **Viewer**: [https://linearvoid.tv](https://linearvoid.tv) (or `http://localhost`) - **Admin**: [https://linearvoid.tv/admin](https://linearvoid.tv/admin) (or `http://localhost/admin`) - **Metrics**: [https://linearvoid.tv/metrics](https://linearvoid.tv/metrics) Full setup guide: [`SETUP.md`](SETUP.md). See the [architecture docs](docs/architecture/README.md) for detailed architecture and ADRs. ## Development - Use Docker Compose for local development (playout + chat + frontend). - Test lab host: `192.168.88.40` (internal network, receives CD deploys). Production Hetzner VPS TBD. - Public production host model: `https://linearvoid.tv` for viewer/admin/chat and `https://media.linearvoid.tv` for HLS delivery. - Normalize all source videos before adding to playlist (see ADR-001 for exact codec/fps/keyframe requirements). - Monitor cache hit ratio and R2 operations closely — they determine real cost. ### Environment Variables | Variable | Default | Description | | ---------------------- | ----------------------------------------------- | -------------------------------- | | `R2_ACCESS_KEY_ID` | — | Cloudflare R2 access key | | `R2_SECRET_ACCESS_KEY` | — | Cloudflare R2 secret key | | `R2_BUCKET_NAME` | `linearvoid-media` | R2 bucket for HLS segments | | `DATABASE_URL` | `postgres://...@db:5432/...` | Chat DB (Postgres or SQLite) | | `REDIS_URL` | `redis://redis:6379/0` | Redis for presence/rate limiting | | `VITE_PUBLIC_APP_URL` | `https://linearvoid.tv` | Public viewer URL | | `VITE_PUBLIC_WS_URL` | `wss://linearvoid.tv/ws` | WebSocket endpoint | | `VITE_PUBLIC_HLS_URL` | `https://media.linearvoid.tv/hls/playlist.m3u8` | HLS playlist URL | | `ADMIN_USERNAME` | `admin` | Admin panel username | | `ADMIN_PASSWORD` | — | Admin panel password | | `HLS_SEGMENT_DURATION` | `6` | HLS segment duration in seconds | | `HLS_WINDOW_SIZE` | `24` | HLS playlist window size | Full reference: [`.env.example`](.env.example). ### Troubleshooting | Problem | Check | | ------------------------ | ------------------------------------------------------------------------------------- | | **Playout not starting** | Verify playlist exists in `data/playlist/playlist.txt` and media files are accessible | | **HLS not playing** | Check `docker compose logs playout` for ffmpeg errors; verify `data/hls/` permissions | | **Chat not connecting** | Ensure Redis is running: `docker compose logs redis`; check `DATABASE_URL` | | **Admin panel 401** | Verify `ADMIN_USERNAME`/`ADMIN_PASSWORD` in `.env` match login credentials | | **Docker build fails** | Run `docker compose build --no-cache`; check `backend/playout/.dockerignore` | | **R2 upload failures** | Verify `R2_ACCESS_KEY_ID` and `R2_SECRET_ACCESS_KEY`; check bucket CORS settings | ### API Endpoints | Service | Endpoint | Description | | ----------- | ------------------------ | -------------------------- | | **Chat** | `GET /health` | Health check | | **Chat** | `GET /metrics` | Prometheus metrics | | **Chat** | `GET /ws` | WebSocket upgrade for chat | | **Chat** | `GET /api/admin/*` | Admin API (auth required) | | **Chat** | `POST /api/admin/login` | Admin login | | **Playout** | `GET /health` | Health check (port 8081) | | **Nginx** | `GET /` | Viewer SPA | | **Nginx** | `GET /admin/` | Admin panel | | **Nginx** | `GET /hls/playlist.m3u8` | HLS manifest | ## Architecture Documentation The architecture is fully documented using the [arc42 template](docs/architecture/README.md) (with populated sections, Mermaid context and building-block diagrams, deployment view, cross-cutting concepts, quality goals, risks, and glossary). **Decision Records** (in `docs/architecture/adr/`): - **[ADR-001](docs/architecture/adr/ADR-001.md)**: Overall system architecture, HLS + Cloudflare R2 caching strategy, storage calculations, cost model, risks, and implementation plan. - **[ADR-002](docs/architecture/adr/ADR-002-module-structure.md)**: Chosen project folder/module structure (frontend, backend/playout, backend/chat, infrastructure, scripts, monitoring) and mapping to arc42 building blocks. - **[ADR-003](docs/architecture/adr/ADR-003-tech-stack.md)**: Technology stack, language choices per module (TypeScript for frontend, Go preferred for chat and playout wrapper, Bash/Python for scripts), Docker-first approach, and all implicit assumptions. - **[ADR-004](docs/architecture/adr/ADR-004-admin-video.md)**: Video admin web interface (upload, playlist management, schedule overrides, normalization enforcement). - **[ADR-005](docs/architecture/adr/ADR-005-admin-moderation-analytics.md)**: Web admin interfaces for user management, moderation dashboard, analytics, and system configuration. - **[ADR-006](docs/architecture/adr/ADR-006-data-storage.md)**: Data storage strategy (SQLite vs PostgreSQL for v1, migration path, admin interface capabilities). - **[ADR-007](docs/architecture/adr/ADR-007-redis-usage.md)**: Redis usage (exact use cases, TTL policies, persistence, fallback behavior, admin configuration). See the [architecture README](docs/architecture/README.md) for the big-picture view and the individual ADR files for detailed rationale, alternatives, and consequences. Contributions, issues, and further evolution (LL-HLS, ABR, multi-channel, E2EE) should reference the architecture documentation. ## Resources - [CHANGELOG.md](CHANGELOG.md) - Version history and completed features. - [CONTRIBUTING.md](CONTRIBUTING.md) - Guidelines for contributing to the project. - [SETUP.md](docs/SETUP.md) - Complete setup guide with prerequisites, environment configuration, running the application, and troubleshooting. - [API.md](docs/API.md) - Implemented WebSocket and REST API reference. - [DEPLOYMENT.md](docs/DEPLOYMENT.md) - Production deployment and operations guide. - [MONITORING.md](docs/MONITORING.md) - Prometheus and Grafana setup and troubleshooting. - [SECURITY.md](docs/SECURITY.md) - Security controls, hardening checklist, and incident baseline. - [Scaling Playbook](docs/operations/scaling-performance-playbook.md) - Performance test method, thresholds, and scaling decision flow. - [Architecture Documentation](docs/architecture/README.md) - Full arc42 template with diagrams and decisions. - [ADRs](docs/architecture/adr/) - Detailed decision records (ADR-001 to ADR-007). **CI/CD**: [View workflows](https://git.allucanget.biz/zwitschi/linearvoid/actions) on Gitea Actions. **Container Registry**: `git.allucanget.biz/zwitschi/linearvoid/:latest` ## Contributing Contributions are welcome! See [`CONTRIBUTING.md`](CONTRIBUTING.md) for guidelines. Quick overview: 1. Read the [architecture docs](docs/architecture/README.md) and ADRs first. 2. Code against the defined module structure (frontend, backend/playout, backend/chat, infrastructure, scripts, monitoring). 3. Open PRs on [Gitea](https://git.allucanget.biz/zwitschi/linearvoid). 4. CI validates builds and tests automatically. Since this is a linear project, we also rely on a linear development and release model: only `main` branch is used for releases, and all features are merged into `main` after review and CI validation. Releases use semantic version tags in `*.*.*` format (for example `1.0.9`) and are automatically built/pushed to the container registry. Version numbers are declared in `frontend/package.json`, `CHANGELOG.md`, and the version badge in this README. [MIT License](LICENSE) The project is ready for use. See [`SETUP.md`](docs/SETUP.md) for detailed installation instructions.