- 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.
151 lines
8.7 KiB
Markdown
151 lines
8.7 KiB
Markdown
# 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`](./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.
|
|
|
|
```mermaid
|
|
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://<hetzner-ip>: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](./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`](./infrastructure/cloudflare/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
|
|
|
|
```bash
|
|
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`:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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`](./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`.
|