mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-11-13 12:16:54 +00:00
545 lines
19 KiB
Plaintext
545 lines
19 KiB
Plaintext
# Matrix RTC/Element Call Setup
|
|
|
|
:::info
|
|
This guide assumes that you are using docker compose for deployment. LiveKit only provides Docker images.
|
|
:::
|
|
|
|
:::tip
|
|
You can find help setting up MatrixRTC in our dedicated room - [#matrixrtc:continuwuity.org](https://matrix.to/#/%23matrixrtc%3Acontinuwuity.org)
|
|
:::
|
|
|
|
## Instructions
|
|
|
|
### 1. Domain
|
|
|
|
LiveKit should live on its own domain or subdomain. In this guide we use `livekit.example.com` - this should be replaced with a domain you control.
|
|
|
|
Make sure the DNS record for the (sub)domain you plan to use is pointed to your server.
|
|
|
|
### 2. Services
|
|
|
|
Using LiveKit with Matrix requires two services - LiveKit itself, and a service (`lk-jwt-service`) that grants Matrix users permission to connect to it.
|
|
|
|
You must generate a key and secret to allow the Matrix service to authenticate with LiveKit. `LK_MATRIX_KEY` should be around 20 random characters, and `LK_MATRIX_SECRET` should be around 64. Remember to replace these with the actual values!
|
|
|
|
:::tip Generating the secrets
|
|
LiveKit provides a utility to generate secure random keys
|
|
```bash
|
|
docker run --rm livekit/livekit-server:latest generate-keys
|
|
# API Key: APIUxUnMnSkuFWV
|
|
# API Secret: t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A
|
|
```
|
|
:::
|
|
|
|
Create a `docker-compose.yml` file as following:
|
|
|
|
```yaml
|
|
services:
|
|
lk-jwt-service:
|
|
image: ghcr.io/element-hq/lk-jwt-service:latest
|
|
container_name: lk-jwt-service
|
|
environment:
|
|
- LIVEKIT_JWT_BIND=:8081
|
|
- LIVEKIT_URL=wss://livekit.example.com # your LiveKit domain
|
|
- LIVEKIT_FULL_ACCESS_HOMESERVERS=example.com # your server_name
|
|
# Replace these with the generated values as above
|
|
- LIVEKIT_KEY=LK_MATRIX_KEY # APIUxUnMnSkuFWV
|
|
- LIVEKIT_SECRET=LK_MATRIX_SECRET # t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8081:8081"
|
|
|
|
livekit:
|
|
image: livekit/livekit-server:latest
|
|
container_name: livekit
|
|
command: --config /etc/livekit.yaml
|
|
restart: unless-stopped
|
|
volumes:
|
|
- ./livekit.yaml:/etc/livekit.yaml:ro
|
|
network_mode: "host" # /!\ LiveKit binds to all addresses by default.
|
|
# Make sure port 7880 is blocked by your firewall to prevent access bypassing your reverse proxy
|
|
# Alternatively, uncomment the lines below and comment `network_mode: "host"` above to specify port mappings.
|
|
# ports:
|
|
# - "127.0.0.1:7880:7880/tcp"
|
|
# - "7881:7881/tcp"
|
|
# - "50100-50200:50100-50200/udp"
|
|
```
|
|
|
|
Next, we need to configure LiveKit. In the same directory, create `livekit.yaml` with the following content - remembering to replace `LK_MATRIX_KEY` and `LK_MATRIX_SECRET` with the values you generated:
|
|
|
|
```yaml
|
|
port: 7880
|
|
bind_addresses:
|
|
- ""
|
|
rtc:
|
|
tcp_port: 7881
|
|
port_range_start: 50100
|
|
port_range_end: 50200
|
|
use_external_ip: true
|
|
enable_loopback_candidate: false
|
|
keys:
|
|
LK_MATRIX_KEY: LK_MATRIX_SECRET
|
|
# replace these with your key-secret pair. Example:
|
|
# APIUxUnMnSkuFWV: t93ZVjPeoEdyx7Wbet3kG4L3NGZIZVEFvqe0UuiVc22A
|
|
```
|
|
|
|
Consult [`config-sample.yaml`][config-sample-yaml] for all LiveKit options.
|
|
|
|
[livekit-config-sample-yaml]: https://github.com/livekit/livekit/blob/master/config-sample.yaml
|
|
|
|
#### Firewall hints
|
|
|
|
You will need to allow ports `7881/tcp` and `50100:50200/udp` through your firewall. If you use UFW, the commands are: `ufw allow 7881/tcp` and `ufw allow 50100:50200/udp`.
|
|
|
|
### 3. Telling clients where to find LiveKit
|
|
|
|
To tell clients where to find LiveKit, you need to add the address of your `lk-jwt-service` to the `[global.matrix_rtc]` config section using the `foci` option.
|
|
|
|
The variable should be a list of servers serving as MatrixRTC endpoints. Replace the URL with the address you are deploying your instance of lk-jwt-service to:
|
|
|
|
```toml
|
|
[global.matrix_rtc]
|
|
foci = [
|
|
{ type = "livekit", livekit_service_url = "https://livekit.example.com" },
|
|
]
|
|
```
|
|
|
|
This will expose LiveKit information on the following endpoints for clients to discover:
|
|
|
|
- `/_matrix/client/unstable/org.matrix.msc4143/rtc/transports` (MSC4143 unstable, behind auth)
|
|
- `/.well-known/matrix/client` (fallback, not behind auth. Only enabled if `[global.well_known].client` is set)
|
|
|
|
### 4. Configure your Reverse Proxy
|
|
|
|
Reverse proxies can be configured in many different ways - so we can't provide a step by step for this.
|
|
|
|
All paths should be forwarded to LiveKit by default, with the exception of the following path prefixes, which should be forwarded to the JWT/Authentication service:
|
|
|
|
- `/sfu/get`
|
|
- `/healthz`
|
|
- `/get_token`
|
|
|
|
<details>
|
|
<summary>Example caddy config</summary>
|
|
|
|
```
|
|
livekit.example.com {
|
|
|
|
# for lk-jwt-service
|
|
@lk-jwt-service path /sfu/get* /healthz* /get_token*
|
|
route @lk-jwt-service {
|
|
reverse_proxy 127.0.0.1:8081
|
|
}
|
|
|
|
# for livekit
|
|
reverse_proxy 127.0.0.1:7880
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Example nginx config</summary>
|
|
|
|
```
|
|
server {
|
|
server_name livekit.example.com;
|
|
|
|
# for lk-jwt-service
|
|
location ~ ^/(sfu/get|healthz|get_token) {
|
|
proxy_pass http://127.0.0.1:8081$request_uri;
|
|
proxy_set_header X-Forwarded-For $remote_addr;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header Host $http_host;
|
|
proxy_buffering off;
|
|
}
|
|
|
|
# for LiveKit
|
|
location / {
|
|
proxy_pass http://127.0.0.1:7880$request_uri;
|
|
proxy_set_header X-Forwarded-For $remote_addr;
|
|
proxy_set_header X-Real-IP $remote_addr;
|
|
proxy_set_header Host $http_host;
|
|
proxy_buffering off;
|
|
|
|
# websocket
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade $http_upgrade;
|
|
proxy_set_header Connection $connection_upgrade;
|
|
}
|
|
}
|
|
```
|
|
|
|
Note that for websockets to work, you need to have this somewhere outside your server block:
|
|
```
|
|
map $http_upgrade $connection_upgrade {
|
|
default upgrade;
|
|
'' close;
|
|
}
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Example traefik router</summary>
|
|
|
|
```
|
|
# on LiveKit itself
|
|
traefik.http.routers.livekit.rule=Host(`livekit.example.com`)
|
|
# on the JWT service
|
|
traefik.http.routers.livekit-jwt.rule=Host(`livekit.example.com`) && (PathPrefix(`/sfu/get`) || PathPrefix(`/healthz`) || PathPrefix(`/get_token`))
|
|
```
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Example docker compose file with caddy-docker-proxy labels</summary>
|
|
```yaml
|
|
# This setup assumes all containers share the same bridge network
|
|
services:
|
|
lk-jwt-service:
|
|
image: ghcr.io/element-hq/lk-jwt-service:latest
|
|
container_name: lk-jwt-service
|
|
# lk-jwt-service environment config here..
|
|
labels:
|
|
caddy: livekit.example.com
|
|
caddy.@lk-jwt-service.path: "/sfu/get* /healthz* /get_token*"
|
|
caddy.reverse_proxy: "@lk-jwt-service {{upstreams 8081}}"
|
|
|
|
|
|
livekit:
|
|
image: livekit/livekit-server:latest
|
|
container_name: livekit
|
|
command: --config /etc/livekit.yaml
|
|
restart: unless-stopped
|
|
labels:
|
|
caddy: livekit.example.com
|
|
caddy.reverse_proxy: "{{upstreams 7880}}"
|
|
volumes:
|
|
- ./livekit.yaml:/etc/livekit.yaml:ro
|
|
ports:
|
|
- "127.0.0.1:7880:7880/tcp"
|
|
- "7881:7881/tcp"
|
|
- "50100-50200:50100-50200/udp"
|
|
|
|
|
|
caddy:
|
|
image: lucaslorentz/caddy-docker-proxy:ci-alpine
|
|
ports:
|
|
- 80:80
|
|
- 443:443
|
|
environment:
|
|
- CADDY_INGRESS_NETWORKS=caddy
|
|
volumes:
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
- ./data:/data
|
|
restart: unless-stopped
|
|
labels:
|
|
# If you already configured `[global.well_known]` with Continuwuity,
|
|
# comment out the *_respond labels and add this line
|
|
# caddy.reverse_proxy: /.well-known/matrix/* homeserver:8008
|
|
caddy.1_respond: /.well-known/matrix/server {"m.server":"matrix.example.com:443"}
|
|
caddy.2_respond: /.well-known/matrix/client {"m.server":{"base_url":"https://matrix.example.com"},"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://livekit.example.com"}]}
|
|
|
|
# If you are having problems with continuwuity serving headers uncomment
|
|
# the header section below.
|
|
|
|
# caddy: example.com
|
|
# caddy.0_header: "*"
|
|
# caddy.0_header.Access-Control-Allow-Origin: "*"
|
|
# caddy.0_header.Access-Control-Allow-Methods: "GET, POST, OPTIONS"
|
|
# caddy.0_header.Access-Control-Allow-Headers: "Authorization"
|
|
# caddy.0_header.Content-Type: "application/json"
|
|
|
|
homeserver:
|
|
image: forgejo.ellis.link/continuwuation/continuwuity:latest
|
|
restart: unless-stopped
|
|
# add additional environment, volume, and network config here...
|
|
labels:
|
|
caddy: matrix.example.com
|
|
caddy.reverse_proxy: "{{upstreams 8008}}"
|
|
|
|
```
|
|
</details>
|
|
|
|
|
|
### 6. Start Everything
|
|
|
|
Start up the services using your usual method - for example `docker compose up -d`.
|
|
|
|
## Testing
|
|
|
|
To test that LiveKit is successfully integrated with Continuwuity, you will need to replicate its [Token Exchange Flow](https://github.com/element-hq/lk-jwt-service#%EF%B8%8F-how-it-works--token-exchange-flow). Follow the steps below while checking Docker logs (`docker-compose logs --follow`), in order to help [troubleshooting](#troubleshooting) any issues.
|
|
|
|
First, you will need an access token for your current login session. These can be found in your client's settings or obtained via [this website](https://timedout.uk/mxtoken.html).
|
|
|
|
Then, using that token, fetch the discovery endpoints for MatrixRTC services:
|
|
|
|
```bash
|
|
curl -H "Authorization: Bearer <session-access-token>" \
|
|
https://matrix.example.com/_matrix/client/unstable/org.matrix.msc4143/rtc/transports
|
|
```
|
|
|
|
In the output, you should see the LiveKit URL matching the one [configured above](#3-telling-clients-where-to-find-livekit).
|
|
|
|
With the same token, request another OpenID token for use with the lk-jwt-service:
|
|
|
|
```bash
|
|
curl -X POST -H "Authorization: Bearer <session-access-token>" \
|
|
https://matrix.example.com/_matrix/client/v3/user/@user:example.com/openid/request_token
|
|
```
|
|
|
|
You will see a response as below:
|
|
|
|
```json
|
|
{"access_token":"<openid_access_token>","token_type":"Bearer","matrix_server_name":"example.com","expires_in":3600}
|
|
```
|
|
|
|
Next, create a `payload.json` file with the following content:
|
|
|
|
<details>
|
|
|
|
<summary>`payload.json`</summary>
|
|
|
|
```json
|
|
{
|
|
"room_id": "abc",
|
|
"slot_id": "xyz",
|
|
"openid_token": {
|
|
"matrix_server_name": "example.com",
|
|
"access_token": "<openid_access_token>",
|
|
"token_type": "Bearer"
|
|
},
|
|
"member": {
|
|
"id": "xyz",
|
|
"claimed_device_id": "DEVICEID",
|
|
"claimed_user_id": "@user:example.com"
|
|
}
|
|
}
|
|
```
|
|
|
|
Replace `matrix_server_name` and `claimed_user_id` with your information, and `<openid_access_token>` with the one you got from the previous step. Other values can be left as-is.
|
|
|
|
</details>
|
|
|
|
You can then send this payload to the lk-jwt-service:
|
|
|
|
```bash
|
|
curl -X POST -d @payload.json https://livekit.example.com/get_token
|
|
```
|
|
|
|
The lk-jwt-service will, after checking against Continuwuity, answer with a `jwt` token to create a LiveKit media room:
|
|
|
|
```json
|
|
{"url":"wss://livekit.example.com","jwt":"a_really_really_long_string"}
|
|
```
|
|
|
|
Use this token to test at the [LiveKit Connection Tester](https://livekit.io/connection-test). If everything works there, then you have set up LiveKit successfully!
|
|
|
|
## Troubleshooting
|
|
|
|
To debug any issues, you can place a call or redo the Testing instructions, and check the container logs for any specific errors. Use `docker-compose logs --follow` to follow these logs in real-time.
|
|
|
|
### Common errors in Element Call UI
|
|
|
|
- `MISSING_MATRIX_RTC_FOCUS`: LiveKit is missing from Continuwuity's config file
|
|
- "Waiting for media" popup always showing: a LiveKit URL has been configured in Continuwuity, but your client cannot connect to it for some reason
|
|
|
|
For browser-based clients, you can also inspect connections using DevTools' Networking tab, to see which requests are erroring out.
|
|
|
|
### Docker loopback networking issues
|
|
|
|
Some distros do not allow Docker containers to connect to its host's public IP by default. This would cause `lk-jwt-service` to fail connecting to `livekit` or `continuwuity` on the same host. As a result, you would see connection refused/connection timeouts log entries in the JWT service, even when `LIVEKIT_URL` has been configured correctly.
|
|
|
|
You can also test that this is the case by cURLing from a sidecar container:
|
|
|
|
```bash
|
|
docker run --rm --net container:lk-jwt-service docker.io/curlimages/curl https://livekit.example.com
|
|
# --- some errors ---
|
|
```
|
|
|
|
To alleviate this, you can try one of the following workarounds:
|
|
|
|
- Use `network_mode: host` for the `lk-jwt-service` container (instead of the default bridge networking).
|
|
|
|
- Add an `extra_hosts` file mapping livekit's (and continuwuity's) domain name to a locally-reachable address:
|
|
|
|
```diff
|
|
# in docker-compose.yaml
|
|
services:
|
|
lk-jwt-service:
|
|
...
|
|
+ extra_hosts:
|
|
+ - "livekit.example.com:127.0.0.1"
|
|
+ - "matrix.example.com:127.0.0.1"
|
|
```
|
|
|
|
- (**untested, use at your own risk**) Implement an iptables workaround as shown [here](https://forums.docker.com/t/unable-to-connect-to-host-service-from-inside-docker-container/145749/6).
|
|
|
|
After implementing the changes and restarting your compose, `lk-jwt-service` should now connect to your other services. The sidecar container test above should now return an `OK` from LiveKit.
|
|
|
|
### Workaround for non-federating servers
|
|
|
|
When deploying on servers with federation disabled (`allow_federation = false`), LiveKit will fail as it can't fetch the required [OpenID endpoint](https://spec.matrix.org/v1.17/server-server-api/#get_matrixfederationv1openiduserinfo) via federation paths.
|
|
|
|
As a workaround, you can enable federation, but forbid all remote servers via the following config parameters:
|
|
|
|
```toml
|
|
### in your continuwuity.toml file ###
|
|
allow_federation = true
|
|
forbidden_remote_server_names = [".*"]
|
|
```
|
|
|
|
Subscribe to issue [!1440](https://forgejo.ellis.link/continuwuation/continuwuity/issues/1440) for future updates on this matter.
|
|
|
|
## Related Documentation
|
|
|
|
Guides:
|
|
|
|
- [Element Call self-hosting documentation from element-hq][element-call-selfhosting]
|
|
- [Community guide with overview of LiveKit's mechanisms][tom-livekit-guide]
|
|
- [Community guide using systemd][kimiblock-livekit-guide]
|
|
|
|
[element-call-selfhosting]: https://github.com/element-hq/element-call/blob/livekit/docs/self-hosting.md
|
|
[tom-livekit-guide]: https://tomfos.tr/matrix/livekit/
|
|
[kimiblock-livekit-guide]: https://blog.kimiblock.top/2024/12/24/hosting-element-call/
|
|
|
|
Configurations:
|
|
|
|
- [Livekit's `config-sample.yaml`][livekit-config-sample-yaml] - LiveKit configuration file with full options
|
|
|
|
Specifications:
|
|
|
|
- [MSC4143 - MatrixRTC proposal][MSC4143]
|
|
- [MSC4195 - LiveKit proposal][MSC4195]
|
|
|
|
[MSC4143]: https://github.com/matrix-org/matrix-spec-proposals/pull/4143
|
|
[MSC4195]: https://github.com/matrix-org/matrix-spec-proposals/pull/4195
|
|
|
|
Source code:
|
|
|
|
- [Element Call][element-call-github]
|
|
- [lk-jwt-service][lk-jwt-service-github]
|
|
- [LiveKit server][livekit-server-github]
|
|
|
|
[element-call-github]: https://github.com/element-hq/element-call
|
|
[lk-jwt-service-github]: https://github.com/element-hq/lk-jwt-service
|
|
[livekit-server-github]: https://github.com/livekit/livekit
|
|
|
|
Other:
|
|
|
|
- [Matrix VOIP and LiveKit][sspaeth-matrix-voip] - Community member deep dive on current VOIP solutions on Matrix
|
|
|
|
[sspaeth-matrix-voip]: https://sspaeth.de/2026/04/matrix-voip-and-livekit/
|
|
|
|
## Appendix
|
|
|
|
### Additional TURNS-over-443 configuration
|
|
|
|
In most situations, LiveKit [does not need TURN][sspaeth-matrix-voip-turn] to function. However, when clients are in very restrictive networks where UDP traffic and non-standard ports are disallowed, a TURN-over-TLS server on port `:443` could be employed to relay traffic.
|
|
|
|
[sspaeth-matrix-voip-turn]: https://sspaeth.de/2026/04/matrix-voip-and-livekit/#turn
|
|
|
|
You can either use LiveKit's built-in TURN server, or integrate LiveKit with [coturn](./turn).
|
|
|
|
<details>
|
|
|
|
<summary>Using LiveKit's built-in TURN server</summary>
|
|
|
|
First, set up LiveKit's built-in TURN server with its own domain - we'll use `livekit-turn.example.com` in our example.
|
|
|
|
```yaml
|
|
## add this to `livekit.yaml` ##
|
|
turn:
|
|
enabled: true
|
|
tls_port: 5349
|
|
relay_range_start: 50300
|
|
relay_range_end: 50400
|
|
domain: livekit-turn.example.com
|
|
# replace these with your actual cert/key files
|
|
cert_file: /path/to/livekit-turn.example.com.crt
|
|
key_file: /path/to/livekit-turn.example.com.key
|
|
```
|
|
|
|
```yaml
|
|
### add these to livekit's docker-compose ###
|
|
ports:
|
|
- "127.0.0.1:5349:5349/tcp"
|
|
- "50300-50400:50300-50400/udp"
|
|
### if you're using `network_mode: host`, you can skip this part
|
|
```
|
|
|
|
Recreate the LiveKit container (with `docker-compose up -d livekit`) to apply these changes. Remember to allow the new `50300:50400/udp` ports through your firewall.
|
|
|
|
Then, we will configure a route from port 443 of the host back to our `livekit-turn.example.com` service on port 5349. To both **multiplex** this and LiveKit's websocket on the same port, we will use a layer-4 reverse proxy with **SNI routing** capabilities, such as [caddy-l4][caddy-l4].
|
|
|
|
```
|
|
## in your Caddyfile ##
|
|
{
|
|
servers {
|
|
listener_wrappers {
|
|
# intercept packets meant for the TURN domain first
|
|
# before forwarding other packets to "normal" HTTP listeners
|
|
layer4 {
|
|
@turn {
|
|
tls {
|
|
sni livekit-turn.example.com
|
|
}
|
|
}
|
|
route @turn {
|
|
proxy {
|
|
upstream 127.0.0.1:5349 # forward to normal TURNS port
|
|
}
|
|
}
|
|
}
|
|
tls
|
|
}
|
|
}
|
|
}
|
|
|
|
# livekit stuff
|
|
https://livekit.example.com {
|
|
@lk-jwt-service path /healthz /get_token /sfu/get
|
|
route @lk-jwt-service {
|
|
reverse_proxy 127.0.0.1:8081
|
|
}
|
|
reverse_proxy http://127.0.0.1:7880
|
|
}
|
|
```
|
|
|
|
[caddy-l4]: https://github.com/mholt/caddy-l4
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Using an external TURN server</summary>
|
|
|
|
Before continuing, make sure coturn's `min-port` and `max-port` do not overlap with LiveKit's port range:
|
|
|
|
```ini
|
|
# in your coturn.conf
|
|
min-port=50201
|
|
max-port=65535
|
|
```
|
|
|
|
Then, generate a long random secret for LiveKit, and add it to your coturn config under the `static-auth-secret` option. You can add as many secrets as you want, so set a different one for LiveKit to use.
|
|
|
|
After that, refer to the following [**TURN instructions**](./turn#turns-over-443) to set up coturn with TLS, as well as multiplexing with LiveKit's websocket on port 443.
|
|
|
|
Then configure LiveKit, making sure to replace `COTURN_SECRET` with the one you generated:
|
|
|
|
```yaml
|
|
# livekit.yaml
|
|
rtc:
|
|
turn_servers:
|
|
- host: coturn.example.com
|
|
port: 443
|
|
protocol: tls
|
|
secret: "COTURN_SECRET"
|
|
```
|
|
|
|
Restart LiveKit, coturn, and Caddy-l4 to apply these changes.
|
|
|
|
</details>
|
|
|
|
After finishing configuration, you can run the Testing steps again to check that TURN-over-TLS is working. In the LiveKit connection test page, there should be a green tick saying "Can connect to TURN". |