Files
linearvoid/docs/SETUP.md
T
zwitschi 33b6296c5e
CI / test (push) Successful in 2m50s
CI / docker (push) Successful in 5m12s
Refactor load testing scripts to support Bash and PowerShell
- Updated README.md to reflect changes in load testing commands and added usage examples for Bash scripts.
- Converted PowerShell load testing scripts to Bash equivalents for better cross-platform compatibility.
- Added new Bash scripts: run-loadtests.sh, summarize-artillery.sh, and summarize-k6.sh to mirror existing PowerShell functionality.
- Updated admin-api.js and ws-chat.yml for consistency and improved performance metrics reporting.
- Enhanced error handling and output formatting in summary scripts for better readability.
2026-09-02 19:47:34 +02:00

8.7 KiB

Linear Void Setup Guide

Last Updated: 2026-08-03

Prerequisites, environment setup, and running the app locally and in production. Architecture: docs/architecture/README.md.

Prerequisites

  • Docker + Docker Compose — runs the full stack (playout, chat, nginx, redis, db, prometheus, grafana).
  • Node.js 22+ / npm — frontend dev and builds (cd frontend && npm install && npm run build).
  • Go 1.25+ — working on the Go services outside Docker.
  • FFmpeg — playout HLS generation (apt install ffmpeg).
  • Git.
  • Cloudflare account — R2 storage + CDN.
  • Gitea account — image registry + CI/CD. Set Actions secrets at https://git.allucanget.biz/zwitschi/linearvoid/settings/actions/secrets: CI_REGISTRY_USER, CI_REGISTRY_TOKEN (scope write:packages), ADMIN_USERNAME, ADMIN_PASSWORD, DATABASE_URL.
  • Hetzner Cloud (optional, prod) — US-East VPS (Ashburn, VA per ADR-001).

Network Environments

Two distinct targets; know which one you're using.

flowchart LR
    subgraph Test_Lab["Test Lab (192.168.88.0/24)"]
        TEST_HOST["192.168.88.40<br/>Docker Engine :2375"]
    end
    subgraph Prod["Production (Hetzner — NOT provisioned)"]
        PROD_HOST["Hetzner CX22 Ashburn, VA<br/>Public IP TBD"]
    end
    subgraph Gitea["Gitea CI/CD"]
        CI["CI: build + push images"]
        CD["CD: deploy via DOCKER_HOST"]
    end
    subgraph CF["Cloudflare"]
        DNS["linearvoid.tv"]
        R2["R2 Bucket"]
    end
    CD -->|"DOCKER_HOST=tcp://192.168.88.40:2375"| TEST_HOST
    CD -.->|"DOCKER_HOST=tcp://&lt;hetzner-ip&gt;:2375 (when provisioned)"| PROD_HOST
    TEST_HOST -->|"test traffic"| DNS
    PROD_HOST -.->|"production traffic"| DNS
    TEST_HOST --> R2
    PROD_HOST -.-> R2
  • Test Lab (active): internal 192.168.88.0/24, host 192.168.88.40; receives automated CD deploys from Gitea Actions. Used for staging validation.
  • Production (pending): Hetzner CX22, Ashburn VA — not yet provisioned. Compose/CD are ready; only DOCKER_HOST needs changing. See DEPLOYMENT.md.

Environment Setup

  1. git clone https://git.allucanget.biz/zwitschi/linearvoid.git && cd linearvoid
  2. cp .env.example .env then replace placeholders.
  3. Compose overrides: docker-compose.yml builds images locally and mounts frontend/dist into nginx; docker-compose.prod.yml pulls prebuilt images from Gitea.
    • Internal URLs use compose hostnames: REDIS_URL=redis://redis:6379/0, DATABASE_URL=postgres://linearvoid:<pw>@db:5432/linearvoid?sslmode=disable.
    • Public routing: VITE_PUBLIC_APP_URL=https://linearvoid.tv, VITE_PUBLIC_WS_URL=wss://linearvoid.tv/ws, VITE_PUBLIC_HLS_URL=https://media.linearvoid.tv/hls/playlist.m3u8.
  4. Create data dirs: mkdir -p data/playlist data/media data/hls data/chat
  5. Optional: place normalized MP4s in data/media/ and create data/playlist/playlist.txt (see ADR-001 for normalization).

Gitea Setup

  1. User with repo + registry access; generate a token (scope write:packages) stored as CI_REGISTRY_TOKEN.
  2. Set Actions secrets (see Prerequisites) to enable automated CI/CD and deploys.
  3. Set registry vars: CI_REGISTRY_URL, CI_REGISTRY_NAMESPACE (e.g. zwitschi/linearvoid), CI_REGISTRY_USER.
  4. Auth: docker login git.allucanget.biz.
  5. Local dev builds from source; prod pulls playout, chat, nginx images from the registry namespace. Verify :latest tags exist before rollout.

Cloudflare R2 Setup

  1. Create R2 bucket (e.g. linearvoid-media, matching R2_BUCKET_NAME); generate an access key pair → R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_REGION=auto.
  2. S3 endpoint: https://<account-id>.r2.cloudflarestorage.com → R2_ENDPOINT.
  3. Attach media.linearvoid.tv as custom media domain → MEDIA_DOMAIN; add CORS allowing https://linearvoid.tv GET for HLS delivery.
  4. Point linearvoid.tv (+ www redirect) at the app entrypoint; keep media.* on R2. Proxied DNS, TLS Full (strict).
  5. Apply the .m4s/.m3u8 cache rules from cache-rules.md; enable Smart Tiered Cache; optionally deploy infrastructure/cloudflare/worker.js for header normalization.
  6. Verify: aws s3 ls --endpoint-url $R2_ENDPOINT; check cache hit ratio after serving traffic.

Running the Application

Local development

docker compose up --build      # start all services
docker compose down            # stop

Access: app/admin https://linearvoid.tv (via proxy) or http://localhost; WS wss://linearvoid.tv/ws / ws://localhost/ws. Health: playout :8081/health, chat :3000/health. Monitoring: Prometheus :9090, Grafana :3001 (default admin/admin).

Frontend dev (optional): cd frontend && npm install && npm run dev (http://localhost:5173, proxies API). For nginx-backed local runs: npm run build (nginx serves frontend/dist).

Test lab (remote Docker host)

Deploy to 192.168.88.40 over Docker's TCP socket (port 2375).

Enable TCP on Alpine host — edit /etc/conf.d/docker:

DOCKER_OPTS="-H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375"

Then rc-service docker restart; verify with netstat -tlnp | grep 2375; allow port 2375 from 192.168.88.0/24 in the firewall.

Deploy from a machine with the Docker CLI:

export DOCKER_HOST="tcp://192.168.88.40:2375"
docker info                                   # confirm remote host
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d --remove-orphans   # registry images
# or build from source: docker compose up -d --build
unset DOCKER_HOST                             # back to local

Health: http://192.168.88.40:8081/health (playout), :3000/health (chat), nginx entrypoint :8080.

Coolify deployment

Coolify replaces the Gitea CD DOCKER_HOST orchestration (Cloudflare DNS/R2/cache unchanged). Install (curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash), add the target server via SSH, create a Docker Compose resource from docker-compose.prod.yml (or the repo), configure env vars in Coolify's store, map persistent data/ and named volumes, and assign linearvoid.tv to nginx for Traefik TLS. Full walkthrough: COOLIFY.md.

Production setup

  1. Provision a Linux VPS (Docker, Compose, Git); open HTTP/HTTPS ports.
  2. git clone … && cd linearvoid && cp .env.example .env.
  3. Set production .env: strong POSTGRES_PASSWORD/GRAFANA_PASSWORD, compose-hostname internal URLs, MEDIA_DOMAIN=media.linearvoid.tv, public VITE_PUBLIC_* URLs. For local-only testing bypassing the proxy, use http://localhost/ws://localhost/ws/http://localhost/hls/playlist.m3u8.
  4. mkdir -p data/playlist data/media data/hls data/chat.
  5. Ensure playout, chat, nginx images exist in the registry; docker login from the VPS.
  6. Deploy: docker compose -f docker-compose.prod.yml pull && docker compose -f docker-compose.prod.yml up -d.
  7. Verify: docker compose -f docker-compose.prod.yml ps, health endpoints, HLS written to data/hls, media domain serves via Cloudflare.
  8. Harden: HTTPS in front of nginx (Cloudflare, www → apex), restrict Grafana/Prometheus to trusted IPs, run under systemd for auto-start.

Production (Hetzner VPS + Cloudflare)

  1. Provision CX22 in Ashburn, VA (ADR-001).
  2. Cloudflare: R2 bucket (enam hint), media.linearvoid.tv custom domain + cache rules, linearvoid.tv/www at the app entrypoint with www→apex redirect, Full (strict) TLS, deploy infrastructure/cloudflare/worker.js.
  3. Deploy from the project: docker compose -f docker-compose.prod.yml up -d (or pull from Gitea registry); manage under systemd.
  4. Monitoring: Grafana dashboards from monitoring/grafana/.

See docs/architecture/README.md (arc42) and the ADRs for decisions and cost/scaling guidance (ADR-001).

Troubleshooting

  • R2 upload fails — check .env creds/bucket; aws s3 ls --endpoint-url=$R2_ENDPOINT.
  • Normalization errors — run backend/playout/start-playout.sh to see ffprobe output. Video must be H.264/AAC, 30 fps constant, 2s keyframes (ADR-001).
  • Chat won't connect — check Redis and WS URL; /health should return 200.
  • Cache not working — verify .m4s long-TTL / .m3u8 short-TTL cache rules and hit ratio.
  • Docker issues — docker compose logs; check port conflicts.
  • Admin interface — ADMIN_USERNAME, ADMIN_PASSWORD, ADMIN_SESSION_TTL_HOURS set; session-based login protects /admin/ and /api/admin/*.
  • Prod pull fails — docker login to Gitea succeeded and image tags exist.

Further Reading

Architecture: docs/architecture/README.md · Decisions: docs/architecture/adr/ (ADR-001…007) · Contributing: CONTRIBUTING.md · Overview: root README.md.