Table of Contents

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 Configuration

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

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

[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
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
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

##################
#
# 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
}

Open Firewall Ports / Port Forwarding

Start all services

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: