Skip to content

farbhaus/Farbstrom

Repository files navigation

Farbstroem

Private low-latency streaming platform for color-grading review sessions. Combines an OvenMediaEngine broadcast pipeline with a LiveKit SFU for participant voice/video, plus chat, shared pointer, and session file sharing.

Architecture

flowchart TB
    Encoder["Encoder<br/>OBS · hardware"]
    Browser["Browser<br/>OvenPlayer · LiveKit SDK · WebSocket"]

    subgraph host["Docker host — stream-net"]
        Caddy["stream-caddy<br/>TLS + routing"]
        Backend["stream-backend<br/>Rust/Axum + SQLite"]
        OME["stream-ome<br/>OvenMediaEngine"]
        LK["stream-livekit<br/>SFU"]
        Valkey[("stream-valkey")]
    end

    Encoder -->|"SRT · RTMP"| OME
    Browser -->|HTTPS / WSS| Caddy

    Caddy -->|"/live/* · WHIP"| OME
    Caddy -->|"/livekit/*"| LK
    Caddy -->|"API · WS · static"| Backend

    Backend -->|RoomService| LK
    Backend -->|admission webhook| OME
    LK --- Valkey
Loading

All services run on a single Docker bridge network (stream-net) and reference each other by container name.

Container Image Purpose
stream-caddy caddy:2-alpine TLS + routing (/live/* → OME, everything else → backend)
stream-ome airensoft/ovenmediaengine:latest Broadcast ingest (SRT/RTMP/WHIP) + viewer delivery (WebRTC/LLHLS)
stream-backend built from backend/Dockerfile Rust/Axum API, WebSocket hub, SQLite, static file serving
stream-livekit livekit/livekit-server:latest SFU for participant conference
stream-valkey valkey/valkey:8-alpine Required by LiveKit (Valkey is the BSD-3 fork of Redis 7.2)

Tech stack

Layer Choice
Broadcast engine OvenMediaEngine (SRT / RTMP / WHIP in, WebRTC / LLHLS out, H.265 passthrough)
Conference SFU LiveKit
Backend Rust + Axum 0.8, Tokio
Database SQLite (WAL) via rusqlite + r2d2 pool
Frontend TypeScript ES modules compiled with tsc (no bundler, no runtime npm deps) — admin SPA, viewer page, landing page. CDN-loaded OvenPlayer + HLS.js + LiveKit JS SDK
Reverse proxy Caddy 2 (container)

Features

  • Room management with expiry, passwords, waiting rooms
  • Presenter vs viewer roles (presenter role only grantable by admin)
  • Per-room viewer delivery mode (WebRTC or LLHLS)
  • LiveKit-backed voice/video conference, screen sharing, watch-only mode
  • Presenter moderation: kick + server-side mute
  • Text chat (persisted per session), file sharing, shared pointer overlay
  • Custom branding (logo + background) per deployment
  • Keyboard shortcuts for the viewer toolbar (see below)
1 2 3 4 5 6 7 8 9 10 11

Keyboard shortcuts

Single-key shortcuts on the viewer page (/watch/{slug}). Ignored while typing in a text field; modifier combos (Ctrl/Cmd/Alt) are left to the browser. The key is also shown in each button's hover tooltip.

Key Action
Q Toggle camera
W Toggle microphone
E Toggle pointer (focus view only)
F Enter/exit fullscreen
M Mute/unmute the stream
X Toggle focus view
C Toggle chat panel
V Toggle call strip (focus view only)

Ingest protocols

Protocol Port Notes
SRT 9999/udp Primary — H.265 passthrough. OBS URL: srt://<host>:9999?streamid=default/live/<STREAM_KEY>
RTMP 1935/tcp Universal encoder support. URL: rtmp://<host>:1935/live, stream name = stream key
WHIP via Caddy /live/* OBS 30+, browser-based encoders

Local development

No deploy script for dev. Fill the secrets (the backend refuses empty/short ones — the rest of the .env.example defaults are already correct for localhost), then run the stack plus the frontend watcher in a side terminal:

cp .env.example .env
for k in JWT_SECRET OME_WEBHOOK_SECRET OME_API_TOKEN LIVEKIT_API_SECRET; do
  sed -i "s|^$k=.*|$k=$(openssl rand -hex 32)|" .env
done
sed -i "s|^ADMIN_PASSWORD=.*|ADMIN_PASSWORD=devpassword123|" .env   # ≥12 chars

docker compose up -d --build                 # full stack on localhost
cd frontend && npm install && npm run watch  # rebuilds www/dist/ on every .ts save

The backend bind-mounts ./www, so a browser refresh picks up tsc rebuilds — no Docker rebuild for frontend changes. (Production hosts run npm ci && npm run build once so www/dist/ exists; deploy.sh does this for you.)

Production deployment

One command on a fresh VPS where only Farbstroem runs:

sudo ./deploy.sh stream.yourdomain.com

That's it. The script installs missing prerequisites (Docker + Compose, Node, openssl), generates .env with all secrets, opens the firewall, builds the frontend, pulls the published backend image, brings the stack up, and prints the admin password once. The containerized Caddy provisions Let's Encrypt and serves stream.yourdomain.com — app, /live/* (OME), and LiveKit (proxied same-origin at /livekit/*) — no host web server to configure.

Before running:

  • Point DNS at the VPS for stream.yourdomain.com (needed for Let's Encrypt).
  • Run as root / with sudo (installs packages, opens the firewall).
  • Prereq auto-install is apt-based; on other distros install Docker/Node/openssl first.

Re-running is safe — an existing .env is reused and secrets are not rotated, so a redeploy keeps sessions alive. Flags:

Flag Effect
--regenerate Rewrite .env from scratch (rotates secrets)
--yes Skip confirmation prompts (unattended)

The script targets a clean box: if something already holds ports 80/443, it stops and points you at manual configuration (below) rather than failing cryptically.

Quick local smoke test

sudo ./deploy.sh 127.0.0.1

Brings the full stack up on the local machine for an end-to-end check of the script itself. On linux/amd64 the published backend image is pulled (instant); on ARM hosts (e.g. Apple Silicon Macs) the script builds the backend from source first. Caddy serves the site over its internal self-signed cert, so the browser will warn once. Use the dotted IP — the script's hostname check rejects bare localhost.

Manual / advanced configuration

Skip deploy.sh and configure .env by hand (cp .env.example .env). Required secrets, all enforced at startup (backend panics with a clear FATAL: otherwise):

Var Min Generate
JWT_SECRET, OME_WEBHOOK_SECRET, OME_API_TOKEN, LIVEKIT_API_SECRET 32 chars openssl rand -hex 32
ADMIN_PASSWORD 12 chars (bcrypt-hashed once at startup)
LIVEKIT_API_KEY any identifier (the LiveKit JWT iss)
PUBLIC_ORIGIN exact browser origin, e.g. https://stream.yourdomain.com (WebAuthn RP — no path)

The containerized Caddy (caddy/Caddyfile) owns all routing — app, /live/* → OME, and LiveKit (proxied same-origin at /livekit/*). For a standalone host where Caddy gets its own Let's Encrypt certs, set:

SITE_ADDRESS=stream.yourdomain.com
LIVEKIT_URL=wss://stream.yourdomain.com/livekit
PUBLIC_ORIGIN=https://stream.yourdomain.com

To run behind an existing reverse proxy, set SITE_ADDRESS=:80 plus HTTP_PORT/HTTPS_PORT overrides so the published ports don't collide with the front, terminate TLS at your proxy, and forward stream.yourdomain.com to the stack's HTTP port. Then docker compose -f docker-compose.yml up -d.

Firewall ports (the script opens these via ufw/firewalld when active): tcp 80 443 1935 3478 7881, udp 443 9999 9998 10000-10009 50000-50100. The 50000-50100/udp LiveKit range is deliberately narrow — wider ranges create thousands of iptables rules and make docker compose up/down take minutes.

Repository layout

.
├── backend/            Rust/Axum backend
├── frontend/           TypeScript sources (`tsc` only, no bundler) for admin/viewer/landing SPAs
├── caddy/Caddyfile     Container Caddy config (SITE_ADDRESS envar-driven)
├── livekit/            LiveKit server config
├── ome/                OvenMediaEngine config
├── www/                Static HTML/CSS + compiled JS (dist/) served by the backend
├── docker-compose.yml
├── .env.example        Required env vars, documented inline
└── docs/               Architecture notes, security model, design system

Tests

cd backend && cargo test

Integration tests live in backend/tests/ and use axum-test.

License

Farbstroem is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) — see LICENSE. In short: you are free to use, study, modify, and self-host it, but if you run a modified version as a network service you must make your modified source available to its users.

Contributions are accepted under the same license via the Developer Certificate of Origin — see CONTRIBUTING.md. Attribution notices for bundled dependencies are collected in THIRD_PARTY_NOTICES.md.

Acknowledgements

Farbstroem is built on the work of these open-source projects:

  • OvenMediaEngine — broadcast ingest/delivery engine (AGPL-3.0)
  • OvenPlayer — LLHLS/WebRTC player (MIT)
  • LiveKit — WebRTC SFU for participant conference (Apache-2.0)
  • Caddy — TLS termination and routing (Apache-2.0)
  • Axum and the broader Rust/Tokio ecosystem (MIT)
  • hls.js — HLS playback fallback (Apache-2.0)

…and the many crates enumerated in THIRD_PARTY_NOTICES.md.

About

Self hosted streaming platform for realtime live reviews sessions

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors