Files
homelab/continuwuity/README.md
2026-03-18 14:54:02 +01:00

239 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Matrix Self-Hosted Stack — Setup Guide
## File Overview
```
matrix-stack/
├── docker-compose.yml # All services: Continuwuity, Traefik, Coturn, LiveKit, lk-jwt-service
├── continuwuity.toml # Homeserver config: TURN URIs, LiveKit foci
├── livekit.yaml # LiveKit media server config
├── coturn.conf # TURN/STUN server config
└── README.md # This file
```
---
## Prerequisites
1. Two DNS A records pointing to your server's public IP:
- `matrix.example.com`
- `livekit.example.com`
(If you want your Matrix IDs as `@user:example.com` rather than
`@user:matrix.example.com`, also point `example.com` to your server.)
2. Docker + Docker Compose installed.
3. Create the external proxy network Traefik uses:
```bash
docker network create proxy
```
---
## One-Time Secret Generation
### Coturn secret
```bash
# Install pwgen if needed: apt install pwgen
pwgen -s 64 1
```
Paste the output into both `coturn.conf` (static-auth-secret)
and `continuwuity.toml` (turn_secret). They MUST match.
### LiveKit keys
```bash
docker run --rm livekit/livekit-server:latest generate-keys
```
This outputs a key (~20 chars) and secret (~64 chars).
Paste them into:
- `livekit.yaml` → keys section
- `docker-compose.yml` → lk-jwt-service LIVEKIT_KEY / LIVEKIT_SECRET
### Registration token (for invite-only signup)
```bash
pwgen -s 32 1
```
Paste into docker-compose.yml → CONTINUWUITY_REGISTRATION_TOKEN.
---
## Starting Up
```bash
# First boot — creates admin user automatically
docker compose up -d
# Get your admin password
docker compose logs homeserver | grep "Created user"
# IMPORTANT: Edit docker-compose.yml and remove the --execute flag
# from the homeserver command, then restart:
docker compose up -d homeserver
```
Log in with any Matrix client (Element, Cinny, etc.) using
`@admin:example.com` and the generated password.
---
## Firewall Rules
Open these ports on your server's firewall (ufw examples shown).
### HTTP/HTTPS — handled by Traefik
```bash
ufw allow 80/tcp # HTTP (redirected to HTTPS by Traefik)
ufw allow 443/tcp # HTTPS for matrix.example.com and livekit.example.com
```
### Coturn — TURN/STUN (raw UDP/TCP, NOT proxied by Traefik)
```bash
ufw allow 3478/tcp # STUN + TURN
ufw allow 3478/udp
ufw allow 5349/tcp # TURN over TLS (if you configured TLS in coturn.conf)
ufw allow 5349/udp
ufw allow 50201:65535/udp # Coturn media relay port range
```
### LiveKit — RTC media (raw UDP/TCP, NOT proxied by Traefik)
```bash
ufw allow 7881/tcp # LiveKit direct TCP (for clients that can't UDP)
ufw allow 50100:50200/udp # LiveKit media relay port range
```
### Summary table
| Port(s) | Protocol | Service | Via Traefik? |
|----------------|-----------|--------------------|--------------|
| 80 | TCP | HTTP (→ HTTPS) | Yes |
| 443 | TCP | HTTPS | Yes |
| 3478 | TCP + UDP | Coturn TURN/STUN | No — direct |
| 5349 | TCP + UDP | Coturn TURN TLS | No — direct |
| 7881 | TCP | LiveKit direct TCP | No — direct |
| 5010050200 | UDP | LiveKit media | No — direct |
| 5020165535 | UDP | Coturn media relay | No — direct |
**Key point**: Traefik only handles ports 80 and 443. The Matrix
homeserver (6167) and LiveKit HTTP (7880) are never exposed directly
— Traefik proxies them internally. Coturn and LiveKit's RTC ports
bypass Traefik entirely and are opened directly to the internet.
---
## Can I use an External NGINX Proxy Manager instead of Traefik?
**Short answer: yes, but with important caveats.**
### What NPM can handle
- `matrix.example.com` → proxy to `http://YOUR_SERVER_IP:6167`
- Enable WebSockets
- Set `X-Forwarded-For`, `Host`, `X-Real-IP` headers
- `livekit.example.com` with path splitting:
- `/sfu/get`, `/healthz`, `/get_token` → `http://YOUR_SERVER_IP:8081`
- Everything else → `http://YOUR_SERVER_IP:7880`
- Enable WebSockets on both
- NPM handles Let's Encrypt TLS automatically
### What NPM cannot handle
- **Coturn** — raw UDP/TCP, not HTTP. NPM (like Traefik) can't proxy it.
You just open ports 3478/5349 directly and point DNS at your server IP.
- **LiveKit RTC ports** (7881/tcp, 50100-50200/udp) — same story.
These bypass any reverse proxy entirely.
### Changes needed to docker-compose.yml for NPM
1. **Remove the entire `traefik:` service block.**
2. **Remove all `labels:` blocks** from homeserver and lk-jwt-service.
3. **Expose the ports NPM needs to reach:**
```yaml
homeserver:
ports:
- "127.0.0.1:6167:6167" # NPM proxies this
lk-jwt-service:
ports:
- "127.0.0.1:8081:8081" # NPM proxies this
# livekit already uses network_mode: host, so 7880 is available
# on the host at 127.0.0.1:7880 automatically
```
Using `127.0.0.1:` prefix means only NPM (on the same host or
your network) can reach them — not the open internet.
4. **Remove the `acme:` volume** (NPM handles TLS).
5. In NPM, configure two proxy hosts:
**matrix.example.com**
- Forward to: `http://YOUR_SERVER_IP:6167`
- Websockets: ON
- Custom nginx config:
```nginx
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
```
**livekit.example.com**
- Forward to: `http://YOUR_SERVER_IP:7880` (default)
- Websockets: ON
- Advanced tab — add this custom config for path splitting:
```nginx
location ~ ^/(sfu/get|healthz|get_token) {
proxy_pass http://YOUR_SERVER_IP:8081$request_uri;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_buffering off;
}
```
### NPM on a separate machine?
If NPM runs on a different server, replace `127.0.0.1` with your
Matrix server's internal IP, and make sure port 6167 is firewalled
to only allow connections from NPM's IP.
---
## Verification
### Test TURN credentials
```bash
curl "https://matrix.example.com/_matrix/client/r0/voip/turnServer" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" | jq
```
Should return a JSON object with `uris`, `username`, `password`.
### Test TURN connectivity
1. Go to https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
2. Paste the credentials from above
3. Click "Gather candidates" — look for `relay` type candidates
### Test LiveKit
1. GET `https://livekit.example.com/healthz` — should return 200
2. Use https://livekit.io/connection-test with a token from `/get_token`
### Test federation
https://federationtester.matrix.org — enter your domain
---
## Troubleshooting
**Federation not working**
- Check `.well-known/matrix/server` is reachable:
`curl https://example.com/.well-known/matrix/server`
- Should return: `{"m.server":"matrix.example.com:443"}`
**TURN not working**
- Verify firewall allows 3478/udp from the internet
- Check coturn logs: `docker compose logs coturn`
- Confirm `turn_secret` in continuwuity.toml matches `static-auth-secret` in coturn.conf
**Element Call / group calls failing**
- Check lk-jwt-service is reachable: `curl https://livekit.example.com/healthz`
- Confirm LiveKit UDP ports (50100-50200) are open
- Check `foci` URL in continuwuity.toml matches where lk-jwt-service is deployed