User Tools

Site Tools


tech:tuwunel

Tuwunel - Matrix Chat + Voice/Video/Screen Conferencing for Groups

This is the instruction for configuring a standalone, non-federated Matrix server with voice/video conferencing. For private groups, this could be a suitable, privacy-oriented alternative to Discord.

I used Fedora as the OS. The instructions mostly apply regardless of distribution.

My setup uses two servers: One that hosts all the services, and a separate server for caddy's proxy. Adjust your caddy config to point to localhost if you run it all on one machine.

I disabled SELinux which is probably stupid. Try getting things running without doing that. If you run into issues, that might be it.

Final note: This is set up with LXC containers and static binaries. I am more comfortable with that method, so it's what I did. If you are more comfortable with Docker, that is probably better because each of these components have official distribution channels there and updates are easier. This guide can still help tie the pieces together.

High level:

  • DNS entries: matrix. , matrix-rtc. , chat.
  • Tuwunel RPM installation
    • Configure toml
    • Listens on matrix.example.com 443 and 8448
    • chown files
  • Livekit-server curl installation
    • Configure yaml
    • chown files
    • systemd file
  • lk-jwt-service
    • download static, move to /usr/local/bin
    • configure .env
    • chown file
    • systemd file
  • Caddy config
  • Start all services

DNS Configuration

You will need two domains/subdomains at a minimum; three if you setup your own web client hosting.

  • matrix.example.com - Matrix server location
  • matrix-rtc.exapmle.com - RTC and JWT location
  • chat.example.com - Optional, not covered here - self-hosted web app location.

Set these up now, so by the time you're ready to stand up your caddy config, everything works right away.

Tuwunel installation

https://github.com/matrix-construct/tuwunel

Install tuwunel - static RPM for fedora, static DEB for Debian. Packages for arch and some others.

The RPM builds the config directory, data directory and installs the binary.

This is a sample config block of /etc/tuwunel/tuwunel.conf.

Modify any line with “example.com” and change the token value to something secure - passphrase is good so you can easily share with friends.

[global]
server_name = "matrix.example.com"
registration_token = "<secret phrase to give to friends so they can register>"
database_path = "/var/lib/tuwunel"
new_user_displayname_suffix = ""
address = ["0.0.0.0"]
port = 8008
ip_source = "rightmost_x_forwarded_for"

allow_registration = true
allow_encryption = true
allow_federation = false
allow_public_room_directory_over_federation = false
allow_public_room_directory_without_auth = false
allow_guest_registration = false

[global.well_known]
client = "https://matrix.example.com"
server = "matrix.example.com:443"
livekit_url = "matrix-rtc.zeropolis.net"

After installation, lock tuwunel files down a bit.

find /var/lib/tuwunel -type d -exec chmod 750 {} + && find /var/lib/tuwunel -type f -exec chmod 640 {} +

Livekit Installation

Livekit has two components: The main RTC server and a JWT server that bonds RTC to Tuwunel.

livekit-server

  • Download livekit-server at https://docs.livekit.io/transport/self-hosting/local/ - I trusted the curl command.
    • This extracts to /usr/local/bin/livekit-server.
  • Create user
    • useradd –system –no-create-home –shell /sbin/nologin –comment “Service account for livekit” livekit
  • Create /etc/systemd/system/livekit-server.service
[Unit]
Description=LiveKit Server
After=network.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/livekit-server --config /etc/livekit-server.yaml
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=livekit

# Run as a dedicated user if one exists, otherwise remove these two lines
User=livekit
Group=livekit

[Install]
WantedBy=multi-user.target
  • Configure /etc/livekit-server.yaml and chown to livekit
  • Generate two random strings, which are used as a key:secret pair and links the livekit service with the JWT service. On Linux, pwgen -s -1 <20|64> will do.
port: 7880
log_level: info
rtc:
  tcp_port: 7881
  port_range_start: 50100
  port_range_end: 50200
  # use_external_ip should be set to true for most cloud environments where
  # the host has a public IP address, but is not exposed to the process.
  # LiveKit will attempt to use STUN to discover the true IP, and advertise
  # that IP with its clients
  use_external_ip: true
keys:
  <20 character random key>: <64 character random secret>

lk-jwt-service

[Unit]
Description=LiveKit JWT Service
After=network.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/lk-jwt-service
EnvironmentFile=/etc/lk-jwt.env
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
SyslogIdentifier=lk-jwt-service
User=livekit
Group=livekit

[Install]
WantedBy=multi-user.target
  • Create /etc/lk-jwt-service.env and chown to livekit
LIVEKIT_JWT_BIND=:8081
LIVEKIT_URL=wss://matrix-rtc.example.com
LIVEKIT_KEY=<20 character key>
LIVEKIT_SECRET=<64 character key>
LIVEKIT_FULL_ACCESS_HOMESERVERS=matrix.example.com

Enable systemd services

systemctl daemon-reload
systemctl enable livekit-server
systemctl enable lk-jwt-service

We will start them later.

Caddy Configuration

  • Install caddy per distro instructions. I recommend checking Caddy's website for their own repositories to add, as they stay ahead of upstream often.
  • Edit the config with your own values for domains and server IP address.
##################
#
# Port 443 - .well-known response, matrix server
#
##################

matrix.example.com, matrix.example.com:8448 {
        handle /.well-known/matrix/client {
                header Access-Control-Allow-Origin *
                respond `{"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://matrix-rtc.example.com"}]}`
        }
        handle /.well-known/matrix/server {
                header Content-Type application/json
                respond `{"m.server": "matrix.example.com:443"}`
        }
        reverse_proxy /_matrix/* <app-server-ip>:8008
}

##################
#
# RTC Servers
#
##################

matrix-rtc.example.com {
        # This is matrix-rtc-jwt
        @jwt_service {
                path /sfu/get* /healthz* /get_token*
        }
        handle @jwt_service {
                reverse_proxy <app-server-ip>:8081
        }
        # This is livekit
        handle {
                reverse_proxy <app-server-ip>:7880 {
                        header_up Connection "upgrade"
                        header_up Upgrade {http.request.header.Upgrade}
                }
        }
}

##################
#
# Element backend - Optional, not enabled in my environment yet
#
##################

chat.example.com {
        reverse_proxy <app-server-ip>:8088
}
  • caddy fmt –overwrite /etc/caddy/Caddyfile # formats the file

Open Firewall Ports / Port Forwarding

  • TCP 443: Caddy server access
  • TCP 8448: Caddy server access (Normally used for federation but I believe the port is needed for the RTC component)
  • TCP 7880/7881: RTC Server
  • UDP 50100:50200: RTC Server

Start all services

  • systemctl start caddy
  • systemctl start tuwunel
  • systemctl start livekit-server
  • systemctl start lk-jwt-service

If all went well, you should now be able to access your server!

First Use / Client access

The Matrix client ecosystem is fragmented. For RTC support (voice/video/screenshare), options are limited. Clients:

To create a user, you must use the web app, not mobile. Your server is matrix.example.com and the registration token is the one you added in /etc/tuwunel/tuwunel.conf. That's a reusable token - anyone with it can register to your server.

The first user to register is automatically an admin.

If you want your friends to auto-join some default *public* (to the server) channels, create those channels and add those channels to the tuwunel config then restart the tuwunel service.

auto_join_rooms = [“#<channel1>:matrix.example.com”, “#<channel2>:matrix.example.com”]

Epilogue - Clients and encryption

Some random notes:

  • Everything to the server is encrypted in transit, but if you want things to be encrypted *to the server* and *on the server*, you must enable encryption per room. If you do this, and your users don't properly link their devices or save their encryption keys, they could lose access to chat history as they switch clients.
  • The Element X app automatically tells the Tuwunel server to send push notifications to a central Element-hosted push service, which works with Google Play Services. So a modicum of metadata is sent to other servers. Message contents are fetched by the app directly from the Tuwunel server.
  • If you don't use RTC, other clients I like are Cinny for web desktop, and Fluffychat for mobile.
tech/tuwunel.txt · Last modified: by webmaster