# LiveKit Setup Guide LiveKit is an open-source SFU (Selective Forwarding Unit) that handles real-time voice and video. OwnCord uses it instead of rolling its own WebRTC stack -- LiveKit handles all the hard parts (DTLS, ICE, codec negotiation, simulcast) while OwnCord manages permissions, state, and room lifecycle. There are two ways to run LiveKit alongside OwnCord: | Method | Best for | LiveKit managed by | |--------|----------|--------------------| | **Docker Compose** | Linux servers | Docker (separate container) | | **Companion process** | Windows / bare-metal Linux | OwnCord (auto-start) | --- ## Docker When running OwnCord via `docker compose`, LiveKit runs as a separate container on the same internal network. OwnCord reaches it at `ws://livekit:7880` via Docker's internal DNS — no port forwarding needed between containers. ### Setup 1. **Edit `.env`** (in `Server/`) — set `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET`: ``` LIVEKIT_API_KEY=my-unique-key LIVEKIT_API_SECRET=my-secret-at-least-32-characters-long ``` 2. **Edit `livekit.yaml`** (copy from `livekit.yaml.example`) — use the same key/secret and set your public IP: ```yaml port: 7880 rtc: tcp_port: 7881 port_range_start: 50000 port_range_end: 60000 node_ip: "YOUR_SERVER_PUBLIC_IP" # required for remote clients keys: my-unique-key: my-secret-at-least-32-characters-long logging: level: info ``` 3. **Leave `voice.livekit_binary` unset** in your `config.yaml`. The `voice.livekit_url` should be `ws://livekit:7880` (Docker DNS). 4. **Open firewall ports** on your host: | Port | Protocol | Purpose | |------|----------|---------| | `7880` | TCP | LiveKit signaling | | `7881` | TCP | TCP fallback for WebRTC | | `50000-60000` | UDP | WebRTC media | > **`node_ip` is required** for remote clients. Without it, LiveKit advertises internal Docker IP addresses as ICE candidates, which are unreachable from the internet. If your cloud VM has a metadata service (AWS, GCP, DigitalOcean) you can use `use_external_ip: true` instead. --- ## Companion Process (Windows / bare-metal Linux) ### 1. Get the LiveKit Binary **You usually don't have to do anything.** With `voice.auto_download_livekit` enabled (the default in a freshly generated `config.yaml`, and offered as a toggle in the first-run setup wizard), OwnCord downloads a pinned `livekit-server` release from the official LiveKit GitHub releases in the background at startup, verifies it against the release's `checksums.txt`, stores it in `data/livekit/`, and manages it as the companion process. Pin a different release with `voice.livekit_version`. To provide the binary yourself instead, download `livekit-server` for your platform from one of: - **GitHub releases**: - Grab the `livekit_*_windows_amd64.zip` asset - **LiveKit website**: (Docs > Self Hosting) Extract the binary somewhere permanent (e.g. `C:\livekit\livekit-server.exe`) and set `voice.livekit_binary` to that path — a configured path always wins over auto-download. --- ## 2. Server Configuration LiveKit settings live in the `voice:` section of `config.yaml`: ```yaml voice: livekit_api_key: "devkey" livekit_api_secret: "owncord-dev-secret-key-min-32chars" livekit_url: "ws://localhost:7880" livekit_binary: "C:/livekit/livekit-server.exe" quality: "medium" ``` | Field | Purpose | Default | |-------|---------|---------| | `livekit_api_key` | Shared API key between OwnCord and LiveKit | `"devkey"` | | `livekit_api_secret` | Shared secret for JWT signing (min 32 chars) | `"owncord-dev-secret-key-min-32chars"` | | `livekit_url` | LiveKit WebSocket URL | `ws://localhost:7880` | | `livekit_binary` | Path to `livekit-server` binary. Empty + auto-download off = assume externally managed | `""` | | `auto_download_livekit` | Download and manage a pinned `livekit-server` release automatically when `livekit_binary` is empty | `true` in generated config | | `livekit_version` | Override the pinned auto-download release (e.g. `"1.13.5"`) | `""` (built-in pin) | | `node_ip` | Public IP for WebRTC ICE candidates (remote users behind NAT) | `""` (auto-detect) | | `advertise_internal_ip` | Also advertise LAN IPs — enable on dual-homed servers (LAN + public IP) so local clients can connect | `false` | | `quality` | Default voice quality preset | `"medium"` | Environment variable overrides use the `OWNCORD_` prefix: `OWNCORD_VOICE_LIVEKIT_API_KEY`, `OWNCORD_VOICE_LIVEKIT_API_SECRET`, etc. > **Warning**: The server logs a warning at startup if you use the default dev key/secret. Always change these for production. --- ## 3. Ports and Firewall | Port | Protocol | Purpose | |------|----------|---------| | **7880** | TCP (HTTP/WS) | LiveKit signaling (WebSocket + REST API) | | **7881** | TCP | LiveKit internal RTC (TURN/TCP fallback) | | **50000-60000** | UDP | Media transport (RTP audio/video) | For LAN-only setups, ensure these ports are open on Windows Firewall. For remote access, forward these through your router or use [Tailscale](tailscale.md). --- ## 4. How the Companion Process Works When `livekit_binary` is set, OwnCord manages LiveKit as a companion process: 1. **Config generation**: OwnCord auto-generates `data/livekit.yaml` with the API key/secret, port 7880, and UDP range 50000-60000. To manage the file yourself (custom `rtc` options, multiple interfaces, ...), delete the header line containing the auto-generated marker — OwnCord then leaves the file untouched on future starts. Your `keys:` entry must still match `voice.livekit_api_key` / `voice.livekit_api_secret`. 2. **Process launch**: `livekit-server --config data/livekit.yaml` 3. **Crash recovery**: Exponential backoff restart (3s -> 6s -> 12s ... up to 60s), gives up after 10 consecutive rapid failures 4. **Health checks**: `GET http://localhost:7880/` verifies LiveKit is responding 5. **Graceful shutdown**: Stops the process when OwnCord shuts down (5s timeout before kill) If `livekit_binary` is empty and `auto_download_livekit` is off, OwnCord assumes LiveKit is managed externally (e.g. Docker, systemd, or manual start). --- ## 5. Token Flow How a client joins voice: ``` Client OwnCord Server LiveKit Server | | | |-- voice_join (channel_id)-->| | | |-- check CONNECT_VOICE | | |-- persist to voice_states | | |-- GenerateToken() | |<-- voice_token ------------| | | { token, url, | | | direct_url } | | | | | |-- connect with JWT --------|-------------------------->| |<--- media streams ----------|--------------------------| ``` **Token details:** - Room name: `"channel-{channelID}"` - Identity: `"user-{userID}"` - TTL: 24 hours (refresh at 23h) - `canPublish` is derived from the `SPEAK_VOICE` permission - `canSubscribe` is always true - Client can request refresh via `voice_token_refresh` (rate limited to 1/60s) **Client connection paths:** - **Proxy path** (`/livekit`): Client connects through OwnCord's HTTPS server. Avoids mixed-content issues. - **Direct URL** (`ws://localhost:7880`): Used when the client is on localhost. --- ## 6. Webhook Integration LiveKit sends webhooks to `POST /api/v1/livekit/webhook`. The endpoint verifies the JWT and handles `participant_left` to clean up ghost voice states when a user disconnects from LiveKit without sending a `voice_leave` message. --- ## 7. Troubleshooting | Symptom | Cause | Fix | |---------|-------|-----| | "voice not configured" error | LiveKit client failed to initialize | Check `livekit_api_key` and `livekit_api_secret` are set and secret is >= 32 chars | | "failed to generate voice token" | API key/secret mismatch | Ensure `config.yaml` key/secret match what LiveKit is using | | Voice connects but no audio | Firewall blocking UDP 50000-60000 | Open UDP port range in Windows Firewall | | "backend unavailable" from `/livekit` proxy | LiveKit not running on port 7880 | Check `livekit_binary` path or start LiveKit manually | | "too many rapid failures, giving up" in logs | LiveKit binary crashes on startup | Run `livekit-server --config data/livekit.yaml` manually to see errors | | Mixed content / insecure WS error | Client using direct URL over HTTPS page | Client should use the `/livekit` proxy path | | Voice works via public IP but not on the LAN (dual-homed server) | LiveKit only advertises the public `node_ip` | Set `voice.advertise_internal_ip: true` so LAN host candidates are advertised too | | `GET /api/v1/livekit/health` returns degraded | LiveKit server not reachable | Verify LiveKit is running: `curl http://localhost:7880` | --- ## 8. Production Checklist - [ ] Change `livekit_api_key` from `"devkey"` to a random string - [ ] Change `livekit_api_secret` to a random 32+ character string - [ ] Open firewall ports: 7880/TCP, 50000-60000/UDP - [ ] If using ACME/manual TLS, ensure LiveKit proxy at `/livekit` is working - [ ] Test voice by joining a voice channel from two clients - [ ] Check `/api/v1/livekit/health` returns `{"status": "ok"}`