This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| computing:element-call [2026/10/10 00:57] – created oemb1905 | computing:element-call [2026/10/10 06:03] (current) – oemb1905 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | # Element Call (LiveKit) — Rootless Podman Quadlet | + | ------------------------------------------- |
| - | Rootless Podman as user `worker` on host `support` (`8.28.86.82`, | + | * **element-call** |
| + | * **Jonathan Haack** | ||
| + | * **Haack' | ||
| + | * **webmaster@haacksnetworking.org** | ||
| - | | Name | Container | Bind | Public | | + | ------------------------------------------- |
| - | | --- | --- | --- | --- | | + | |
| - | | Element Call UI | `element-call` | `127.0.0.1: | + | |
| - | | JWT | `lk-jwt` | `127.0.0.1: | + | |
| - | | LiveKit signalling | `livekit` | `127.0.0.1: | + | |
| - | | ICE TCP | same | `7881/tcp` | `8.28.86.82: | + | |
| - | | ICE UDP | same | `50100-50200/udp` | that range | | + | |
| - | | TURN listen | same | `3479/udp` | `webrtc.gnulinux.club: | + | |
| - | | TURN relay | same | `35000-35100/udp` | that range | | + | |
| - | Do not use 8080, 8082, 8088, 3478, or 443. | + | //Element Call// |
| - | Images: | + | ------------------------------------------- |
| + | ~~NOTOC~~ | ||
| - | - `ghcr.io/ | + | ==== Introduction ==== |
| - | - `ghcr.io/ | + | |
| - | - `docker.io/ | + | |
| - | Do not use `podman generate systemd`. Units come from Quadlet files in `~/.config/containers/systemd/`. | + | This tutorial is for Debian users who already self-host Matrix-Synapse and need to build an Element Call instance to support it with. Now, in my case, I manually self-host [[https://wiki.haacksnetworking.org/doku.php? |
| - | --- | + | ^ Name ^ Container ^ Bind ^ Public ^ |
| + | | Element Call UI | '' | ||
| + | | JWT | '' | ||
| + | | LiveKit signalling | '' | ||
| + | | ICE TCP | same | '' | ||
| + | | ICE UDP | same | '' | ||
| + | | TURN listen | same | '' | ||
| + | | TURN relay | same | '' | ||
| - | ## 1. DNS | + | Since this was the 4th container on the same instance, I need to avoid using ports 8080, 8082, 8088, 3478, and 443. I just used 3479 instead, since HPB was already using 3478. The main proxy port was also easy to adjust. |
| - | `call.gnulinux.club` and `webrtc.gnulinux.club` → `8.28.86.82` and `2604:fa40: | + | Images: |
| - | --- | + | * '' |
| + | * '' | ||
| + | * '' | ||
| - | ## 2. Firewall (root) | + | ==== 1. DNS ==== |
| - | ```bash | + | Set your A/AAAA records to point at the virtual machine |
| - | ufw allow 3479/udp comment ' | + | |
| - | ufw allow 35000:35100/udp comment ' | + | |
| - | ufw allow 7881/tcp comment ' | + | |
| - | ufw allow 50100: | + | |
| - | ``` | + | |
| - | --- | + | ==== 2. Firewall (root) ==== |
| - | ## 3. Secret (worker) | + | Here's the ufw rules I came up with: |
| - | ```bash | + | < |
| + | ufw allow 3479/udp | ||
| + | ufw allow 35000: | ||
| + | ufw allow 7881/tcp | ||
| + | ufw allow 50100: | ||
| + | </ | ||
| + | |||
| + | ==== 3. Secret (worker) ==== | ||
| + | |||
| + | Let's get the secrets cut: | ||
| + | |||
| + | < | ||
| mkdir -p ~/ | mkdir -p ~/ | ||
| umask 077 | umask 077 | ||
| openssl rand -hex 32 | tee ~/ | openssl rand -hex 32 | tee ~/ | ||
| chmod 600 ~/ | chmod 600 ~/ | ||
| - | ``` | + | </ |
| - | Key name: `matrixrtc`. Placeholder used below: | + | ==== 4. Certificates (root) ==== |
| - | ``` | + | After the key and project directory are ready, we can cut the cert: |
| - | LIVEKIT_SECRET=replace-with-your-livekit-secret | + | |
| - | ``` | + | |
| - | --- | + | < |
| - | + | ||
| - | ## 4. Certificates (root) | + | |
| - | + | ||
| - | ```bash | + | |
| certbot certonly --apache -d call.gnulinux.club -d webrtc.gnulinux.club | certbot certonly --apache -d call.gnulinux.club -d webrtc.gnulinux.club | ||
| a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl | a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl | ||
| - | ``` | + | </ |
| - | Apache is the only process that reads these. | + | ==== 5. LiveKit config (worker) ==== |
| - | --- | + | Let's pull the images and create our configuration files: |
| - | ## 5. LiveKit config (worker) | + | < |
| + | podman pull ghcr.io/ | ||
| + | podman pull ghcr.io/ | ||
| + | podman pull docker.io/ | ||
| - | ```bash | ||
| cat > ~/ | cat > ~/ | ||
| port: 7880 | port: 7880 | ||
| Line 101: | Line 106: | ||
| EOF | EOF | ||
| chmod 600 ~/ | chmod 600 ~/ | ||
| - | ``` | + | </ |
| - | `node_ip` is the public IPv4 clients use for media. It does not take Apache's TCP 443. `auto_create: | + | Make sure '' |
| - | --- | + | ==== 6. Element Call config (worker) ==== |
| - | ## 6. Element Call config (worker) | + | The image serves files from ''/ |
| - | The image serves files from `/app`. A mount on `/ | + | < |
| - | + | ||
| - | ```bash | + | |
| cat > ~/ | cat > ~/ | ||
| { | { | ||
| Line 128: | Line 131: | ||
| } | } | ||
| EOF | EOF | ||
| - | ``` | + | </ |
| - | --- | + | ==== 7. Quadlets ==== |
| - | ## 7. Quadlets | + | Also on the VM, make sure the quadlet is setup. You should note that '' |
| - | `lk-jwt` must resolve `webrtc.gnulinux.club` to the host. Without `AddHost`, room creation hairpins to the public IP and gets connection refused. `LIVEKIT_FULL_ACCESS_HOMESERVERS` is `gnulinux.club`, | + | < |
| - | + | ||
| - | ```bash | + | |
| systemctl --user disable --now container-element-call.service container-lk-jwt.service container-livekit.service 2>/ | systemctl --user disable --now container-element-call.service container-lk-jwt.service container-livekit.service 2>/ | ||
| rm -f ~/ | rm -f ~/ | ||
| Line 202: | Line 203: | ||
| systemctl --user start container-lk-jwt.service | systemctl --user start container-lk-jwt.service | ||
| systemctl --user start container-element-call.service | systemctl --user start container-element-call.service | ||
| - | ``` | + | </ |
| - | `WantedBy=default.target` starts them. Do not `systemctl enable` a Quadlet unit. Do not `podman generate systemd`. | + | ==== 8. Verify ==== |
| - | --- | + | Let's make sure those quadlets all function: |
| - | ## 8. Verify | + | < |
| - | + | ||
| - | ```bash | + | |
| systemctl --user is-active container-livekit.service container-lk-jwt.service container-element-call.service | systemctl --user is-active container-livekit.service container-lk-jwt.service container-element-call.service | ||
| podman inspect element-call lk-jwt livekit --format ' | podman inspect element-call lk-jwt livekit --format ' | ||
| Line 216: | Line 215: | ||
| curl -sS http:// | curl -sS http:// | ||
| podman logs --tail 15 livekit | podman logs --tail 15 livekit | ||
| - | ``` | + | </ |
| - | Expect `active` on all three. `config.json` must be JSON, not the HTML page. LiveKit should log `turn.portUDP` 3479, `relay_range_start` 35000, and `nodeIP` `8.28.86.82`. | + | Here, we are looking for '' |
| - | --- | + | ==== 9. Upgrade script ==== |
| - | ## 9. Upgrade | + | Here's a simple upgrade |
| - | `/ | + | < |
| - | + | ||
| - | ```bash | + | |
| #!/bin/bash | #!/bin/bash | ||
| set -euo pipefail | set -euo pipefail | ||
| + | export XDG_RUNTIME_DIR=/ | ||
| + | export DBUS_SESSION_BUS_ADDRESS=unix: | ||
| podman pull docker.io/ | podman pull docker.io/ | ||
| Line 248: | Line 247: | ||
| curl -sS http:// | curl -sS http:// | ||
| echo | echo | ||
| - | ``` | + | </ |
| - | Run it as `worker`: | + | ==== 10. Apache (root) ==== |
| - | ```bash | + | Now that the service is created and running, make sure dns for a/aaaa is ready and then let's cut the cert. |
| - | su - worker -c '/bin/bash / | + | |
| - | ``` | + | |
| - | Do not `sudo -u worker`. That drops the session bus. | + | |
| + | sudo certbot certonly --apache -d webrtc.gnulinux.club | ||
| - | --- | + | We also need to create the reverse proxy so we can forward external requests upstream to the local listening services we just created. For the cert(s) '' |
| - | ## 10. Apache (root) | + | < |
| - | + | ||
| - | `/ | + | |
| - | + | ||
| - | ```apache | + | |
| < | < | ||
| ServerName call.gnulinux.club | ServerName call.gnulinux.club | ||
| Line 270: | Line 264: | ||
| RewriteRule ^ https:// | RewriteRule ^ https:// | ||
| </ | </ | ||
| + | </ | ||
| + | |||
| + | Inside '' | ||
| + | < | ||
| < | < | ||
| ServerName call.gnulinux.club | ServerName call.gnulinux.club | ||
| Line 282: | Line 280: | ||
| ProxyPassReverse / http:// | ProxyPassReverse / http:// | ||
| </ | </ | ||
| - | ``` | + | </ |
| - | `/ | + | Inside '' |
| - | ```apache | + | < |
| < | < | ||
| ServerName webrtc.gnulinux.club | ServerName webrtc.gnulinux.club | ||
| Line 292: | Line 290: | ||
| RewriteRule ^ https:// | RewriteRule ^ https:// | ||
| </ | </ | ||
| + | </ | ||
| + | Inside '' | ||
| + | |||
| + | < | ||
| < | < | ||
| ServerName webrtc.gnulinux.club | ServerName webrtc.gnulinux.club | ||
| Line 311: | Line 313: | ||
| ProxyPassReverse / | ProxyPassReverse / | ||
| </ | </ | ||
| - | ``` | + | </ |
| - | ```bash | + | Once that's done, let's enable them and check the endpoints: |
| + | |||
| + | < | ||
| a2ensite call.gnulinux.club.conf webrtc.gnulinux.club.conf | a2ensite call.gnulinux.club.conf webrtc.gnulinux.club.conf | ||
| apache2ctl configtest && systemctl reload apache2 | apache2ctl configtest && systemctl reload apache2 | ||
| Line 319: | Line 323: | ||
| curl -fsS -o /dev/null -w 'call %{http_code}\n' | curl -fsS -o /dev/null -w 'call %{http_code}\n' | ||
| curl -s https:// | curl -s https:// | ||
| - | ``` | + | </ |
| - | Both status lines must be `200`. The config curl must be JSON. | + | Both status lines must be '' |
| - | --- | + | ==== 11. Synapse (Matrix VM) ==== |
| - | ## 11. Synapse (Matrix VM) | + | On the Matrix VM, we need to edit '' |
| - | No new ports. Existing HTTPS is enough. Append this at the bottom of `/ | + | < |
| - | + | ||
| - | ```yaml | + | |
| experimental_features: | experimental_features: | ||
| msc3266_enabled: | msc3266_enabled: | ||
| Line 349: | Line 351: | ||
| - type: livekit | - type: livekit | ||
| livekit_service_url: | livekit_service_url: | ||
| - | ``` | + | </ |
| - | ```bash | + | Then, restart the service: |
| + | |||
| + | < | ||
| sudo systemctl restart matrix-synapse | sudo systemctl restart matrix-synapse | ||
| - | ``` | + | </code> |
| - | + | ||
| - | Wait until it is active. A curl during those few seconds returns nginx 502. The LiveKit secret is not added here. Synapse only advertises the JWT URL. `lk-jwt` checks the OpenID token. | + | |
| - | + | ||
| - | This Synapse serves the unstable route only. `/_matrix/ | + | |
| - | With a real Element access token: | + | You can test with: |
| - | ```bash | + | < |
| curl -s -H " | curl -s -H " | ||
| https:// | https:// | ||
| - | ``` | + | </ |
| - | The body must contain | + | The body of the output should |
| - | --- | + | ==== 12. Well-known (Matrix VM) ==== |
| - | ## 12. Well-known | + | In an initial build, I left old comments/ |
| - | `/ | + | < |
| - | + | ||
| - | ```json | + | |
| { | { | ||
| " | " | ||
| Line 386: | Line 384: | ||
| ] | ] | ||
| } | } | ||
| - | ``` | + | </ |
| - | The nginx vhost for `gnulinux.club` must allow the Element Call origin to read it: | + | The nginx vhost for '' |
| - | ```nginx | + | < |
| location / | location / | ||
| default_type application/ | default_type application/ | ||
| Line 400: | Line 398: | ||
| } | } | ||
| } | } | ||
| - | ``` | + | </ |
| - | ```bash | + | Reload service and check API endpoint: |
| + | |||
| + | < | ||
| nginx -t && systemctl reload nginx | nginx -t && systemctl reload nginx | ||
| curl -s https:// | curl -s https:// | ||
| - | ``` | + | </ |
| - | --- | + | ==== 13. Element Web ==== |
| - | ## 13. Element Web | + | In '' |
| - | In `element.gnulinux.club` `config.json`, | + | < |
| - | + | ||
| - | ```json | + | |
| " | " | ||
| " | " | ||
| Line 420: | Line 418: | ||
| " | " | ||
| } | } | ||
| - | ``` | + | </code> |
| - | + | ||
| - | `https:// | + | |
| - | + | ||
| - | --- | + | |
| - | ## Facts | + | That should be it. Debug and review line by line if stuff is failing. Reach out on Matrix if needed. |
| - | - | + | --- // |