9.8 KiB
Table of Contents
Overview
Element Call consists of a few parts, you don't have to host all of them yourself. In this document, we're going to host everything ourselves, so here's what you need.
- lk-jwt. This authenticates Synapse users to LiveKit.
- LiveKit. This is the "SFU", which actually handles the audio and video, and does TURN.
- Element Call widget. This is basically the webapplication, the user interface.
As mentioned in the checklist you need to define these three entries in DNS and get certificates for them:
turn.example.com
livekit.example.com
call.example.com
You may already have DNS and TLS for turn.example.com
, as it is also used
for coturn.
For more inspiraten, check https://sspaeth.de/2024/11/sfu/
LiveKit
The actual SFU, Selective Forwarding Unit, is LiveKit; this is the part that handles the audio and video feeds and also does TURN (this TURN-functionality does not support the legacy calls, you'll need coturn for that).
Downloading and installing is easy: download the binary from Github to /usr/local/bin, chown it to root:root and you're done.
The quickest way to do precisely that, is to run the script:
curl -sSL https://get.livekit.io | bash
You can do this as a normal user, it will use sudo to do its job.
While you're at it, you might consider installing the cli tool as well, you can use it -for example- to generate tokens so you can test LiveKit's connectivity:
curl -sSL https://get.livekit.io/cli | bash
Configuring LiveKit is documented
here. We're going to
run LiveKit under authorization of user turnserver
, the same users we use
for coturn. This user is created when installing coturn, so if you
haven't installed that, you should create the user yourself:
adduser --system turnserver
Configure
Start by creating a key and secret:
livekit-server generate-keys
This key and secret have to be fed to lk-jwt-service too, see here. Create the directory for LiveKit's configuration:
mkdir /etc/livekit
chown root:turnserver /etc/livekit
chmod 750 /etc/livekit
Create a configuration file for livekit, /etc/livekit/livekit.yaml
:
port: 7880
bind_addresses:
- ::1
rtc:
tcp_port: 7881
port_range_start: 50000
port_range_end: 60000
use_external_ip: true
enable_loopback_candidate: false
turn:
enabled: true
domain: livekit.example.com
cert_file: /etc/coturn/ssl/fullchain.pem
key_file: /etc/coturn/ssl/privkey.pem
tls_port: 5349
udp_port: 3478
external_tls: true
keys:
# KEY: SECRET were generated by "livekit-server generate-keys"
<KEY>: <SECRET>
Being a bit paranoid: make sure LiveKit can only read this file, not write it:
chown root:turnserver /etc/livekit/livekit.yaml
chmod 640 /etc/livekit/livekit.yaml
Port 7880
is forwarded by nginx: authentication is also done there, and that
bit has to be forwarded to lk-jwt-service
on port 8080
. Therefore, we
listen only on localhost.
The TURN ports are the normal, default ones. If you also use coturn, make sure it doesn't use the same ports as LiveKit. Also, make sure you open the correct ports in the firewall.
TLS certificate
The TLS-certificate files are not in the usual place under
/etc/letsencrypt/live
, see DNS and
certificate under coturn why that is.
As stated before, we use the same user as for coturn. Because this user does
not have the permission to read private keys under /etc/letsencrypt
, we copy
those files to a place where it can read them. For coturn we copy them to
/etc/coturn/ssl
, and if you use coturn and have this directory, LiveKit can
read them there too.
If you don't have coturn installed, you should create a directory under
/etc/livekit
and copy the files to there. Modify the livekit.yaml
file and
the script to copy the files to use that
directory. Don't forget to update the renew_hook
in Letsencrypt if you do.
The LiveKit API listens on localhost, IPv6, port 7880. Traffic to this port is forwarded from port 443 by nginx, which handles TLS, so it shouldn't be reachable from the outside world.
See LiveKit's config documentation for more options.
Systemd
Now define a systemd servicefile, like this:
[Unit]
Description=LiveKit Server
After=network.target
Documentation=https://docs.livekit.io
[Service]
User=turnserver
Group=turnserver
LimitNOFILE=500000
Restart=on-failure
WorkingDirectory=/etc/livekit
ExecStart=/usr/local/bin/livekit-server --config /etc/livekit/livekit.yaml
[Install]
WantedBy=multi-user.target
Enable and start it.
Clients don't know about LiveKit yet, you'll have to give them the information
via the .well-known/matrix/client
: add this bit to it to point them at the
SFU:
"org.matrix.msc4143.rtc_foci": [
{
"type": "livekit",
"livekit_service_url": "https://livekit.example.com"
}
]
Make sure it is served as application/json
, just like the other .well-known
files.
lk-jwt-service
lk-jwt-service is a small Go program that handles authorization tokens for use with LiveKit. You'll need a Go compiler, but the one Debian provides is too old (at the time of writing this, at least), so we'll install the latest one manually. Check the Go website to see which version is the latest, at the time of writing it's 1.23.3, so we'll install that:
wget https://go.dev/dl/go1.23.3.linux-amd64.tar.gz
tar xvfz go1.23.3.linux-amd64.tar.gz
cd go/bin
export PATH=`pwd`:$PATH
cd
This means you now have the latest Go compiler in your path, but it's not
installed system-wide. If you want that, copy the whole go
directory to
/usr/local
and add /usr/local/go/bin
to everybody's $PATH.
Get the latest lk-jwt-service source code and comile it (preferably NOT as root):
git clone https://github.com/element-hq/lk-jwt-service.git
cd lk-jwt-service
go build -o lk-jwt-service
Now, compile:
cd lk-jwt-service
go build -o lk-jwt-service
Copy and chown the binary to /usr/local/sbin
(yes: as root):
cp ~user/lk-jwt-service/lk-jwt-service /usr/local/sbin
chown root:root /usr/local/sbin/lk-jwt-service
Systemd
Create a service file for systemd, something like this:
# This thing does authorization for Element Call
[Unit]
Description=LiveKit JWT Service
After=network.target
[Service]
Restart=always
User=www-data
Group=www-data
WorkingDirectory=/etc/lk-jwt-service
EnvironmentFile=/etc/lk-jwt-service/config
ExecStart=/usr/local/sbin/lk-jwt-service
[Install]
WantedBy=multi-user.target
Configuration
We read the options from /etc/lk-jwt-service/config
,
which we make read-only for group www-data
and non-accessible by anyone
else.
mkdir /etc/lk-jwt-service
vi /etc/lk-jwt-service/config
chgrp -R root:www-data /etc/lk-jwt-service
chmod 750 /etc/lk-jwt-service
This is what you should put into that config file,
/etc/lk-jwt-service/config
. The LIVEKIT_SECRET
and LIVEKIT_KEY
are the
ones you created while configuring LiveKit.
LIVEKIT_URL=wss://livekit.example.com
LIVEKIT_SECRET=xxx
LIVEKIT_KEY=xxx
LK_JWT_PORT=8080
Change the permission accordingly:
chown root:www-data /etc/lk-jwt-service/config
chmod 640 /etc/lk-jwt-service/config
Now enable and start this thing:
systemctl enable --now lk-jwt-service
Element Call widget
This is a Node.js thingy, so start by installing yarn. Unfortunately both npm
and yarnpkg
in Debian are antique, so we need to update them after installation.
Install Node.js and upgrade everything. Do not do this as root, we'll only
need to "compile" Element Call once.
See the Node.js website for instructions.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
Exit and login again to set some environment variables (yes, the installation changes .bashrc). Then install and upgrade:
nvm install 23
sudo apt install yarnpkg
/usr/share/nodejs/yarn/bin/yarn set version stable
/usr/share/nodejs/yarn/bin/yarn install
Yes, this whole Node.js, yarn and npm thing is a mess. Better documentation could be written, but for now this will have to do.
Now clone the Element Call repository and "compile" stuff (again: not as root):
git clone https://github.com/element-hq/element-call.git
cd element-call
/usr/share/nodejs/yarn/bin/yarn
/usr/share/nodejs/yarn/bin/yarn build
If it successfully compiles (warnings are more or less ok, errors aren't), you will
find the whole shebang under "dist". Copy that to /var/www/element-call
and point
nginx to it (see nginx).
Configuring
It needs a tiny bit of configuring. The default configuration under config/config.sample.json
is a good place to start, copy it to /etc/element-call
and change where
necessary:
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.example.com",
"server_name": "example.com"
}
},
"livekit": {
"livekit_service_url": "https://livekit.example.com"
},
"features": {
"feature_use_device_session_member_events": true
},
"eula": "https://www.example.com/online-EULA.pdf"
}
Now tell the clients about this widget. Create
.well-known/element/element.json
, which is opened by Element Web, Element Desktop
and ElementX to find the Element Call widget. It should look this:
{
"call": {
"widget_url": "https://call.example.com"
}
}