Hosting Livekit

The server part

Install livekit-server and livekit-cli on a server which isn’t behind NAT.

Firstly, generate your API keys using livekit’s cli tool:

1
2
3
livekit-server generate-keys
API Key: APIp2ow9ec5MJQd
API Secret: CjwZBVnAeaCVebgtYyWfEjLehqjR6I5PHEkTVdUci2Q

Store these secrets in somewhere safe, like in an password manager.

Create a livekit config file using this template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
port: 7880

rtc:
port_range_start: 7881
port_range_end: 17881
tcp_port: 17882
use_external_ip: false
use_ice_lite: true
stun_servers:
- some.random.stun.server:3478
congestion_control:
enabled: true
allow_tcp_fallback: true

keys:
elecall: CjwZBVnAeaCVebgtYyWfEjLehqjR6I5PHEkTVdUci2Q
logging:
level: info
room:
enabled_codecs:
- mime: audio/opus
- mime: video/h264

turn:
enabled: true
udp_port: 3478
tls_port: 5349
external_tls: false
domain: some.livekit.instance
cert_file: /path/to/cert
key_file: /path/to/certkey
  • Replace stun_servers (remove this entry if you don’t have one), domain, cert_file and key_file. If you have coturn or other software using the ports, change the livekit config as required.
  • Open the ports on your firewall.

Add a systemd service using systemctl edit --force --full livekit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
[Unit]
Description="LiveKit: Real-time video, audio and data for developers"
After=network.target
Conflicts=livekit.service
Documentation=https://docs.livekit.io/home/

[Service]
ExecStart=livekit-server --config /path/to/config.yaml --node-ip your.ip.address

Restart=always
RestartSec=5

TimeoutStopSec=4m
TimeoutStartSec=4m
StartLimitInterval=1m
StartLimitBurst=5

AmbientCapabilities=
CapabilityBoundingSet=

DevicePolicy=closed
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
ProcSubset=pid
ProtectClock=yes
ProtectControlGroups=yes
#ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectProc=invisible
ProtectSystem=strict
PrivateDevices=yes
PrivateMounts=yes
PrivateTmp=yes
PrivateIPC=yes
RemoveIPC=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes

SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring
SystemCallErrorNumber=EPERM

[Install]
WantedBy=multi-user.target

At the very least, replace /path/to/config.yaml and your.ip.address, then start livekit.service.

Reverse proxy

We’ll take NGINX for an example. Add a new server block as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server {
#TLS Secure
ssl_certificate /path/to/cert;
ssl_certificate_key /path/to/certkey;
ssl_prefer_server_ciphers off;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=63072000" always;
ssl_stapling on;
ssl_stapling_verify on;
resolver 127.0.0.1;
error_page 497 https://$host;
listen 443 ssl;
server_name some.livekit.instance.domain;
client_max_body_size 10M;
location / {
proxy_pass http://localhost:7880/;
proxy_set_header Host your.ip.address;
proxy_pass_header Authorization;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

Change your.ip.address, some.livekit.instance.domain accordingly, and reload your reverse proxy.

Hosting Livekit JWT

Create a systemd service:

1
systemctl edit --force --full lk-jwt-service.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
[Unit]
Description="LiveKit Management Service"
After=network.target

[Service]
DynamicUser=yes

Environment=LIVEKIT_URL="wss://some.livekit.instance.domain"
Environment=LK_JWT_PORT="27881"
Environment=LIVEKIT_KEY='elecall'
Environment=LIVEKIT_SECRET="CjwZBVnAeaCVebgtYyWfEjLehqjR6I5PHEkTVdUci2Q"
Environment=HOMESERVER_URL="https://matrix.example.org"

ExecStart=lk-jwt-service

Restart=on-failure
RestartSec=5

TimeoutStopSec=4m
TimeoutStartSec=4m
StartLimitInterval=1m
StartLimitBurst=5

AmbientCapabilities=
CapabilityBoundingSet=

DevicePolicy=closed
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
ProcSubset=pid
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectProc=invisible
ProtectSystem=strict
PrivateDevices=yes
PrivateMounts=yes
PrivateTmp=yes
PrivateUsers=yes
PrivateIPC=yes
RemoveIPC=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes

SystemCallFilter=~@clock @debug @module @mount @reboot @swap @cpu-emulation @obsolete @timer @chown @setuid @privileged @keyring
SystemCallErrorNumber=EPERM


[Install]
WantedBy=multi-user.target

Change LIVEKIT_URL, LIVEKIT_SECRET and HOMESERVER_URL accordingly, then enable & start lk-jwt-service.service.

Add a new server block in Nginx config:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Livekit LWT
server {
access_log off;

#TLS Secure
ssl_certificate /var/tmp/fullchain;
ssl_certificate_key /var/tmp/privkey;
ssl_prefer_server_ciphers off;
ssl_protocols TLSv1.3;
add_header Strict-Transport-Security "max-age=63072000" always;
ssl_stapling on;
ssl_stapling_verify on;
resolver 127.0.0.1;
error_page 497 https://$host;
listen 443 ssl;
server_name lk-jwt.example.com;
client_max_body_size 10M;
location / {
proxy_pass http://localhost:27881/;
proxy_set_header Host $http_host;
proxy_pass_header Authorization;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

Replace server_name, ssl_certificate, ssl_certificate_key.

Hosting widget

Install aur/element-call or whatever your distro provides.

Add a server block into Nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
server {
listen 443 ssl;
server_name widget.element.call;
ssl_certificate /var/tmp/fullchain;
ssl_certificate_key /var/tmp/privkey;
ssl_prefer_server_ciphers off;
ssl_protocols TLSv1.3;
add_header Strict-Transport-Security "max-age=63072000" always;
ssl_stapling on;
ssl_stapling_verify on;
resolver 127.0.0.1;
error_page 497 https://$host;
root /usr/share/webapps/element-call;
location / {
add_header Cache-Control 'private no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
if_modified_since off;
expires off;
add_header Last-Modified "";
try_files $uri /$uri /index.html;
}

location /assets {
expires 1w;
add_header Cache-Control "public, no-transform";
}

location /apple-app-site-association {
default_type application/json;
}
}

Replace server_name, widget.element.call, ssl_certificate, ssl_certificate_key. And add a new well-known file to your matrix domain: /.well-known/element/element.json

1
{"call":{"widget_url":"https://widget.element.call"}}

Modifying well-known

Modify your Matrix client well-known like the following text:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"m.homeserver": {
"base_url": "https://matrix.example.org/"
},
"org.matrix.msc3575.proxy": {
"url": "https://matrix.example.org/"
},
"org.matrix.msc4143.rtc_foci": [
{
"type": "livekit",
"livekit_service_url": "https://lk-jwt.example.com"
}
]
}

Replace those URLs!

After those modifications, we should be all set.