Files
OwnCord/docs/livekit-setup.md
T

202 lines
11 KiB
Markdown
Raw Normal View History

# 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.
2026-04-03 14:36:10 +02:00
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) |
2026-04-03 14:36:10 +02:00
---
2026-04-03 14:36:10 +02:00
## Docker <a name="docker"></a>
2026-04-03 14:36:10 +02:00
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
2026-04-03 14:36:10 +02:00
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 |
2026-04-03 14:36:10 +02:00
> **`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**: <https://github.com/livekit/livekit/releases>
- Grab the `livekit_*_windows_amd64.zip` asset
- **LiveKit website**: <https://livekit.io/> (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"}`