--- gitea: none include_toc: true --- # 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](../checklist.md) 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](../coturn). For more inspiraten, check https://sspaeth.de/2024/11/sfu/ # LiveKit {#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](coturn) for that). Downloading and installing is easy: download the [binary from Github](https://github.com/livekit/livekit/releases/download/v1.8.0/livekit_1.8.0_linux_amd64.tar.gz) 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](https://livekit.io/connection-test): ``` curl -sSL https://get.livekit.io/cli | bash ``` Configuring LiveKit is [documented here](https://docs.livekit.io/home/self-hosting/deployment/). We're going to run LiveKit under authorization of user `turnserver`, the same users we use for [coturn](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 {#keysecret} 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](#jwtconfig). 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" : ``` 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](../firewall). ## TLS certificate The TLS-certificate files are not in the usual place under `/etc/letsencrypt/live`, see [DNS and certificate](../coturn/README.md#dnscert) 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](../coturn/README.md#fixssl) 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](https://github.com/livekit/livekit/blob/master/config-sample.yaml) 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 {#lkjwt} 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](https://go.dev/dl/) 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 {#jwtconfig} 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](#keysecret). ``` 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 {#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](https://nodejs.org/en/download/package-manager/current) 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](../nginx#callwidget)). ## 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 this: ``` { "call": { "widget_url": "https://call.example.com" } } ```