User Tools

Site Tools


computing:element-call

This is an old revision of the document!


# Element Call (LiveKit) — Rootless Podman Quadlet

Rootless Podman as user `worker` on host `support` (`8.28.86.82`, `2604:fa40:0:10::9`). Apache terminates TLS for the two HTTP names. The containers speak plain HTTP on localhost. TURN is UDP on 3479. Talk keeps 3478. Apache keeps TCP 443. Synapse stays on the Matrix VM.

Name Container Bind Public
— — — —
Element Call UI `element-call` `127.0.0.1:8083` → `8080` `https://call.gnulinux.club`
JWT `lk-jwt` `127.0.0.1:8085` → `8080` `https://webrtc.gnulinux.club/livekit/jwt/`
LiveKit signalling `livekit` `127.0.0.1:7880` → `7880` `https://webrtc.gnulinux.club/livekit/sfu/`
ICE TCP same `7881/tcp` `8.28.86.82:7881`
ICE UDP same `50100-50200/udp` that range
TURN listen same `3479/udp` `webrtc.gnulinux.club:3479`
TURN relay same `35000-35100/udp` that range

Do not use 8080, 8082, 8088, 3478, or 443.

Images:

- `ghcr.io/element-hq/element-call:v0.26.1` - `ghcr.io/element-hq/lk-jwt-service:latest` - `docker.io/livekit/livekit-server:latest`

Do not use `podman generate systemd`. Units come from Quadlet files in `~/.config/containers/systemd/`.

—

## 1. DNS

`call.gnulinux.club` and `webrtc.gnulinux.club` → `8.28.86.82` and `2604:fa40:0:10::9`.

—

## 2. Firewall (root)

```bash ufw allow 3479/udp comment 'Element Call TURN UDP' ufw allow 35000:35100/udp comment 'Element Call TURN relay' ufw allow 7881/tcp comment 'Element Call ICE TCP' ufw allow 50100:50200/udp comment 'Element Call ICE UDP' ```

—

## 3. Secret (worker)

```bash mkdir -p ~/element-call umask 077 openssl rand -hex 32 | tee ~/element-call/livekit.secret chmod 600 ~/element-call/livekit.secret ```

Key name: `matrixrtc`. Placeholder used below:

``` LIVEKIT_SECRET=replace-with-your-livekit-secret ```

—

## 4. Certificates (root)

```bash certbot certonly –apache -d call.gnulinux.club -d webrtc.gnulinux.club a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl ```

Apache is the only process that reads these.

—

## 5. LiveKit config (worker)

```bash cat > ~/element-call/livekit.yaml « 'EOF' port: 7880 bind_addresses:

  1. “0.0.0.0”

rtc:

tcp_port: 7881
port_range_start: 50100
port_range_end: 50200
node_ip: 8.28.86.82
use_external_ip: true

room:

auto_create: false

logging:

level: info

keys:

matrixrtc: "replace-with-your-livekit-secret"

webhook:

api_key: matrixrtc
urls:
  - https://webrtc.gnulinux.club/livekit/jwt/sfu_webhook

turn:

enabled: true
domain: webrtc.gnulinux.club
udp_port: 3479
relay_range_start: 35000
relay_range_end: 35100

EOF chmod 600 ~/element-call/livekit.yaml ```

`node_ip` is the public IPv4 clients use for media. It does not take Apache's TCP 443. `auto_create: false` is required. The JWT service creates rooms.

—

## 6. Element Call config (worker)

The image serves files from `/app`. A mount on `/usr/share/nginx/html` is ignored. The file must exist before the container starts, or Podman creates a directory and nginx falls through to `index.html`.

```bash cat > ~/element-call/config.json « 'EOF' {

"default_server_config": {
  "m.homeserver": {
    "base_url": "https://matrix.gnulinux.club",
    "server_name": "gnulinux.club"
  },
  "org.matrix.msc4143.rtc_foci": [
    {
      "type": "livekit",
      "livekit_service_url": "https://webrtc.gnulinux.club/livekit/jwt"
    }
  ]
}

} EOF ```

—

## 7. Quadlets

`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`, not `matrix.gnulinux.club`.

```bash systemctl –user disable –now container-element-call.service container-lk-jwt.service container-livekit.service 2>/dev/null || true rm -f ~/.config/systemd/user/container-element-call.service \

    ~/.config/systemd/user/container-lk-jwt.service \
    ~/.config/systemd/user/container-livekit.service

podman rm -f element-call lk-jwt livekit

mkdir -p ~/.config/containers/systemd

cat > ~/.config/containers/systemd/container-element-call.container « 'EOF' [Container] ContainerName=element-call Image=ghcr.io/element-hq/element-call:v0.26.1 PublishPort=127.0.0.1:8083:8080 Volume=/home/worker/element-call/config.json:/app/config.json:ro,Z PodmanArgs=–cpus=1 –memory=512m [Service] Restart=always TimeoutStopSec=120 [Install] WantedBy=default.target EOF

cat > ~/.config/containers/systemd/container-lk-jwt.container « 'EOF' [Container] ContainerName=lk-jwt Image=ghcr.io/element-hq/lk-jwt-service:latest PublishPort=127.0.0.1:8085:8080 AddHost=webrtc.gnulinux.club:host-gateway Environment=LIVEKIT_JWT_BIND=:8080 Environment=LIVEKIT_URL=wss://webrtc.gnulinux.club/livekit/sfu Environment=LIVEKIT_KEY=matrixrtc Environment=LIVEKIT_SECRET=replace-with-your-livekit-secret Environment=LIVEKIT_FULL_ACCESS_HOMESERVERS=gnulinux.club PodmanArgs=–cpus=1 –memory=512m [Service] Restart=always TimeoutStopSec=120 [Install] WantedBy=default.target EOF

cat > ~/.config/containers/systemd/container-livekit.container « 'EOF' [Container] ContainerName=livekit Image=docker.io/livekit/livekit-server:latest PublishPort=127.0.0.1:7880:7880/tcp PublishPort=7881:7881/tcp PublishPort=3479:3479/udp PublishPort=35000-35100:35000-35100/udp PublishPort=50100-50200:50100-50200/udp Volume=/home/worker/element-call/livekit.yaml:/etc/livekit.yaml:Z Exec=–config /etc/livekit.yaml PodmanArgs=–cpus=2 –memory=4g [Service] Restart=always TimeoutStopSec=120 [Install] WantedBy=default.target EOF

systemctl –user daemon-reload systemctl –user reset-failed container-element-call.service container-lk-jwt.service container-livekit.service systemctl –user start container-livekit.service systemctl –user start container-lk-jwt.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

```bash systemctl –user is-active container-livekit.service container-lk-jwt.service container-element-call.service podman inspect element-call lk-jwt livekit –format 'name cpus=hostconfig.nanocpus memory=hostconfig.memory' curl -fsS http://127.0.0.1:8085/healthz; echo curl -sS http://127.0.0.1:8083/config.json; echo 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`.

—

## 9. Upgrade script

`/usr/local/bin/upgrade-element-call.sh`. A tag change is an edit to `Image=` in the matching `.container` file before `daemon-reload`. The script does not recreate the units.

```bash #!/bin/bash set -euo pipefail

podman pull docker.io/livekit/livekit-server:latest podman pull ghcr.io/element-hq/lk-jwt-service:latest podman pull ghcr.io/element-hq/element-call:v0.26.1

systemctl –user stop container-livekit.service systemctl –user stop container-lk-jwt.service systemctl –user stop container-element-call.service

systemctl –user daemon-reload systemctl –user reset-failed container-livekit.service container-lk-jwt.service container-element-call.service systemctl –user start container-livekit.service systemctl –user start container-lk-jwt.service systemctl –user start container-element-call.service

curl -fsS http://127.0.0.1:8085/healthz echo curl -sS http://127.0.0.1:8083/config.json echo ```

Run it as `worker`:

```bash su - worker -c '/bin/bash /usr/local/bin/upgrade-element-call.sh' ```

Do not `sudo -u worker`. That drops the session bus.

—

## 10. Apache (root)

`/etc/apache2/sites-available/call.gnulinux.club.conf`:

```apache <VirtualHost *:80>

  ServerName call.gnulinux.club
  RewriteEngine On
  RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]

</VirtualHost>

<VirtualHost *:443>

  ServerName call.gnulinux.club
  SSLEngine on
  SSLCertificateFile /etc/letsencrypt/live/call.gnulinux.club/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/call.gnulinux.club/privkey.pem
  Include /etc/letsencrypt/options-ssl-apache.conf
  ProxyPreserveHost On
  RequestHeader set X-Forwarded-Proto "https"
  ProxyPass / http://127.0.0.1:8083/
  ProxyPassReverse / http://127.0.0.1:8083/

</VirtualHost> ```

`/etc/apache2/sites-available/webrtc.gnulinux.club.conf`:

```apache <VirtualHost *:80>

  ServerName webrtc.gnulinux.club
  RewriteEngine On
  RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]

</VirtualHost>

<VirtualHost *:443>

  ServerName webrtc.gnulinux.club
  SSLEngine on
  SSLCertificateFile /etc/letsencrypt/live/webrtc.gnulinux.club/fullchain.pem
  SSLCertificateKeyFile /etc/letsencrypt/live/webrtc.gnulinux.club/privkey.pem
  Include /etc/letsencrypt/options-ssl-apache.conf
  ProxyPreserveHost On
  RequestHeader set X-Forwarded-Proto "https"
  ProxyTimeout 3600
  ProxyPass /livekit/jwt/ http://127.0.0.1:8085/
  ProxyPassReverse /livekit/jwt/ http://127.0.0.1:8085/
  RewriteEngine On
  RewriteCond %{HTTP:Upgrade} websocket [NC]
  RewriteCond %{HTTP:Connection} upgrade [NC]
  RewriteRule ^/livekit/sfu/(.*) ws://127.0.0.1:7880/$1 [P,L]
  ProxyPass /livekit/sfu/ http://127.0.0.1:7880/
  ProxyPassReverse /livekit/sfu/ http://127.0.0.1:7880/

</VirtualHost> ```

```bash a2ensite call.gnulinux.club.conf webrtc.gnulinux.club.conf apache2ctl configtest && systemctl reload apache2 curl -fsS -o /dev/null -w 'jwt %{http_code}\n' https://webrtc.gnulinux.club/livekit/jwt/healthz curl -fsS -o /dev/null -w 'call %{http_code}\n' https://call.gnulinux.club/ curl -s https://call.gnulinux.club/config.json ```

Both status lines must be `200`. The config curl must be JSON.

—

## 11. Synapse (Matrix VM)

No new ports. Existing HTTPS is enough. Append this at the bottom of `/etc/matrix-synapse/homeserver.yaml`, same indent as `pid_file`. Do not nest it under `email` or `database`.

```yaml experimental_features:

msc3266_enabled: true
msc4143_enabled: true
msc4222_enabled: true

max_event_delay_duration: 24h

rc_message:

per_second: 0.5
burst_count: 30

rc_delayed_event_mgmt:

per_second: 1
burst_count: 20

matrix_rtc:

transports:
  - type: livekit
    livekit_service_url: "https://webrtc.gnulinux.club/livekit/jwt"

```

```bash sudo systemctl restart matrix-synapse ```

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/client/v1/rtc/transports` returns `M_UNRECOGNIZED`. Element Call uses the unstable route. That is expected.

With a real Element access token:

```bash curl -s -H “Authorization: Bearer TOKEN” \

https://matrix.gnulinux.club/_matrix/client/unstable/org.matrix.msc4143/rtc/transports

```

The body must contain `https://webrtc.gnulinux.club/livekit/jwt`.

—

## 12. Well-known (Matrix VM)

`/var/www/gnulinux.club/.well-known/matrix/client` must be valid JSON. No `#` comments. A comment makes Element fail parse and show `MISSING_MATRIX_RTC_TRANSPORT`.

```json {

"m.homeserver": {
  "base_url": "https://matrix.gnulinux.club"
},
"org.matrix.msc4143.rtc_foci": [
  {
    "type": "livekit",
    "livekit_service_url": "https://webrtc.gnulinux.club/livekit/jwt"
  }
]

} ```

The nginx vhost for `gnulinux.club` must allow the Element Call origin to read it:

```nginx location /.well-known/matrix/client {

  default_type application/json;
  add_header Access-Control-Allow-Origin "*" always;
  add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
  add_header Access-Control-Allow-Headers "Origin, Content-Type, Accept, Authorization" always;
  if ($request_method = OPTIONS) {
      return 204;
  }

} ```

```bash nginx -t && systemctl reload nginx curl -s https://gnulinux.club/.well-known/matrix/client ```

—

## 13. Element Web

In `element.gnulinux.club` `config.json`, replace the hosted call URL:

```json “element_call”: {

"url": "https://call.gnulinux.club",
"use_exclusively": true,
"participant_limit": 8,
"brand": "Element Call"

} ```

`https://call.element.io` shows `MISSING_MATRIX_RTC_TRANSPORT` even when Synapse is correct. Hard-refresh after the change.

—

## Facts

-

computing/element-call.1791593845.txt.gz · Last modified: by oemb1905