Tuwunel ( [Matrix] サーバ ) for Docker

Tuwunel for Docker

Matrixプロトコルに準拠したサーバTumunelをDockerコンテナで運用するためのティップス。


Matrix

Matrix:分散型ネットワークで通信するためのオープンプロトコル。異なるサーバ間でリアルタイムにメッセージなどの送受信を可能にします(エンドツーエンド暗号化対応)。

Matrix対応サーバ

Matrix対応クライアント


Tumunel GitHub

Matrixプロトコルに準拠したサーバアプリケーション。ソースコードはRustで記述。

設定ファイル(サンプル)

Tuwunel Dockerコンテナ


リバースプロキシ:Caddyコンテナ

TLS認証を自動化するために採用。

Caddy


Turnサーバ:Coturnの設定

turnserver.conf

音声・ビデオ通話(グループ通話)対応

MatrixRTC


MatrixRTC Authorization Service

  • JWT (JSON Web Token)

LiveKit

  • ドキュメント

Element : Matrixクライアント

Elementから提供されている以下のオープンソースMatrixクライアントで、音声・ビデオ通話、メッセージを送受信。

element x android

element x ios

element web

ウェブアプリ


element-call

上記アプリの音声・ビデオ通話機能は、以下のelement-callが担っています。

Self-Hosting Element Call

参考

システム構成

構成ファイル

docker-compose.yaml

services:
  caddy:
    image: lucaslorentz/caddy-docker-proxy:ci-alpine
    container_name: caddy
    ports:
      - 80:80
      - 443:443
    environment:
      - CADDY_INGRESS_NETWORKS=caddy
      - CADDY_DOCKER_CADDYFILE_PATH=/config/caddy/Caddyfile
    networks:
      - caddy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/data
      - ./Caddyfile:/config/caddy/Caddyfile
    restart: unless-stopped
    extra_hosts:
      - "host.docker.internal:host-gateway"
      
  homeserver:     
    image: jevolk/tuwunel:latest
    container_name: tuwunel
    restart: unless-stopped
    volumes:
      - ./db:/var/lib/tuwunel
      - ./tuwunel.toml:/etc/tuwunel.toml
    environment:
      TUWUNEL_CONFIG: '/etc/tuwunel.toml'
    networks:
      - caddy
  
  matrix-rtc-jwt:
    image: ghcr.io/element-hq/lk-jwt-service:latest
    container_name: jwt
    environment:
      - LIVEKIT_JWT_BIND=:8081
      - LIVEKIT_URL=wss://matrix-rtc.example.com
      - LIVEKIT_KEY=${MRTCKEY}
      - LIVEKIT_SECRET=${MRTCSECRET}
      - LIVEKIT_FULL_ACCESS_HOMESERVERS=matrix.example.com
    restart: unless-stopped
    networks:
      - caddy

  matrix-rtc-livekit:
    image: livekit/livekit-server:latest
    container_name: livekit
    command: --config /etc/livekit.yaml
    restart: unless-stopped
    volumes:
      - ./livekit.yaml:/etc/livekit.yaml:ro
    network_mode: "host"
    
  # Certbot just only for Coturn TLS  
  certbot:
    image: certbot/dns-cloudflare
    container_name: certbot
    restart: unless-stopped
    volumes:
      - ./certbot/certs:/etc/letsencrypt
      - ./certbot/cloudflare.ini:/etc/letsencrypt/cloudflare.ini:ro
      - /var/run/docker.sock:/var/run/docker.sock
      - ./certbot/deploy-hook.sh:/etc/letsencrypt/renewal-hooks/deploy/restart-coturn.sh:ro
    entrypoint: /bin/sh -c
    command: >
      "certbot certonly
      --dns-cloudflare
      --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
      --dns-cloudflare-propagation-seconds 30
      -d turn.example.com
      --email [email protected]
      --agree-tos
      --non-interactive
      && trap exit TERM;
      while :; do
        sleep 12h &
        wait $${!};
        certbot renew --quiet;
      done"

  coturn:
    image: docker.io/coturn/coturn
    container_name: coturn    
    restart: unless-stopped
    network_mode: host
    user: root
    volumes:
      - ./turnserver.conf:/etc/coturn/turnserver.conf
      - ./certbot/certs:/etc/letsencrypt:ro 

networks:
  caddy:
    external: true

注)
- LIVEKIT_KEY=${MRTCKEY} —> “$ pwgen -s 20 1” で生成した文字列を.envに記載
- LIVEKIT_SECRET=${MRTCSECRET} ”$ pwgen -s 64 1”で生成した文字列を.envに記載

Caddyfile

matrix.example.com {
    handle /.well-known/matrix/server {
        header Content-Type "application/json"
        respond `{"m.server":"matrix.example.com:443"}`
    }
    handle /.well-known/matrix/client {
        header Access-Control-Allow-Origin "*"
        header Content-Type "application/json"
        respond `{"m.homeserver":{"base_url":"https://matrix.example.com"},"org.matrix.msc3575.proxy":{"url":"https://matrix.example.com"},"m.identity_server":{"base_url":"https://vector.im"},"org.matrix.msc4143.rtc_foci":[{"type":"livekit","livekit_service_url":"https://matrix-rtc.example.com"}]}`
    }
    reverse_proxy homeserver:6167
}

matrix-rtc.example.com {
    @jwt_service {
        path /sfu/get* /healthz* /get_token*
    }
    handle @jwt_service {
        reverse_proxy matrix-rtc-jwt:8081
    }
    handle {
        reverse_proxy host.docker.internal:7880 {
            header_up Connection "upgrade"
            header_up Upgrade {http.request.header.Upgrade}
        }
    }
}
  • Caddyfile構文チェック
    $ docker compose exec caddy caddy validate --config /config/caddy/Caddyfile
  • 証明書確認
    $ ls -la ./data/caddy/certificates/acme-v02.api.letsencrypt.org-directory/

tuwunel.toml

[global]
server_name = "matrix.example.com"
database_path = "/var/lib/tuwunel"
address = ["0.0.0.0", "::"]
port = 6167
max_request_size = 20000000
allow_registration = true
registration_token = "xxxxxxxxxxxxxxxxxxxxx"
allow_federation = true
trusted_servers = ["matrix.org"]
turn_uris = ["turns:turn.example.com:5349?transport=tcp", "turns:turn.example.com:5349?transport=udp"]
turn_secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

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

注)registration_tokenはhomeserverのドメイン:matrix.example.comでユーザ登録する際に必要。Caddyで.well-known/matrix/clientに"https://matrix-rtc.example.com"を指定していれば[global.well_known]の項目は不要(全てコメントアウト)
クローズドサーバの場合、allow_federation=false

livekit.yaml

port: 7880
bind_addresses:
  - "::"

rtc:
  tcp_port: 7881
  port_range_start: 50301
  port_range_end: 50400
  use_external_ip: true
  enable_loopback_candidate: false
  turn_servers:
  - host: turn.example.com
    port: 5349
    protocol: tls
    secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

keys:
  xxxxxxxxxxxxxxxxx: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

注)keys:には .env の MRTCKET: MRTCSECRET を記述。

turnserver.conf

listening-port=3478
tls-listening-port=5349
listening-ip=::
realm=turn.example.com
use-auth-secret
static-auth-secret=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
log-file=stdout
no-tcp-relay
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
no-multicast-peers
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.88.99.0-192.88.99.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=240.0.0.0-255.255.255.255
denied-peer-ip=::1
denied-peer-ip=64:ff9b::-64:ff9b::ffff:ffff
denied-peer-ip=::ffff:0.0.0.0-::ffff:255.255.255.255
denied-peer-ip=100::-100::ffff:ffff:ffff:ffff
denied-peer-ip=2001::-2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=2002::-2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
allowed-peer-ip=2404:7a87:9140:1000:5054:ff:fe8e:4360
user-quota=12 # 4 streams per video call, so 12 streams = 3 simultaneous relayed calls per user.
total-quota=1200
cert=/etc/letsencrypt/live/turn.example.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.example.com/privkey.pem
external-ip=EXTERNAL_IPV6
min-port=50201
max-port=50300

公開ポート

  • 7880:7881/tcp ALLOW Anywhere
  • 80/tcp (v6) ALLOW Anywhere (v6)
  • 443/tcp (v6) ALLOW Anywhere (v6)
  • 3478 (v6) ALLOW Anywhere (v6)
  • 5349/tcp (v6) ALLOW Anywhere (v6)
  • 7880:7881/tcp (v6) ALLOW Anywhere (v6)
  • 50201:50400/udp (v6) ALLOW Anywhere (v6)

LiveKit が :: でバインドする際、OSの設定によって動作が変わります。このためIPv4の7880:7881もポート開放必要。

原因

/proc/sys/net/ipv6/bindv6only の値を確認

$ cat /proc/sys/net/ipv6/bindv6only
  • 0 → IPv6ソケットがIPv4も兼用(dual-stack)→ *:7880 と表示される
  • 1 → IPv6専用 → :::7880 と表示される

Admin Roomでの操作

Matrixサーバ:Tuwunelで、ユーザの登録・作成などの各種操作はAdmin Roomで行います。
Admin RoomはデフォルトでTuwunel初回起動時に作成されます。

name: matrix.example.com Admin Room

以下の設定により(デフォルト)、最初に登録したユーザーが自動でAdmin Roomに入ります。

tuwunel.toml

grant_admin_to_first_user = true
create_admin_room = true

このAdmin Roomでユーザ作成など各種操作をするには、メッセージ欄に

”!admin + コマンド"

と入力します。

例)ヘルプ:コマンド一覧

!admin help

> Usage: !admin <COMMAND>

Commands:
  appservices  - Commands for managing appservices
  users        - Commands for managing local users
  rooms        - Commands for managing rooms
  federation   - Commands for managing federation
  server       - Commands for managing the server
  media        - Commands for managing media
  debug        - Commands for debugging things
  query        - Low-level queries for database getters and iterators
  token        - Commands for managing registration tokens
  help         Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

Element-Web

Elementのウェブアプリを自前で用意する場合、以下のサービスをdocker-compose.ymlに追加。

docker-compose.yaml

  element-web:
    # https://hub.docker.com/r/vectorim/element-web
    image: vectorim/element-web:latest
    container_name: element-web
    volumes:
      - ./element-config.json:/app/config.json:ro
    networks:
      - caddy

設定ファイル
element-config.json

{
    "default_server_name": "example.com",
    "default_server_config": {
        "m.homeserver": {
            "base_url": "https://matrix.example.com"
        },
        "m.identity_server": {
            "base_url": "https://vector.im"
        }
    },
    "brand": "Element",
    "integrations_ui_url": "https://scalar.vector.im/",
    "integrations_rest_url": "https://scalar.vector.im/api",
    "integrations_widgets_urls": [
        "https://scalar.vector.im/_matrix/integrations/v1",
        "https://scalar.vector.im/api",
        "https://scalar-staging.vector.im/_matrix/integrations/v1",
        "https://scalar-staging.vector.im/api"
    ],
    "show_labs_settings": true,
    "room_directory": {
        "servers": ["matrix.example.com", "matrix.org"]
    },
    "enable_presence_by_hs_url": {
        "https://matrix.example.com": true
    },
    "map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx",
    "setting_defaults": {
        "RustCrypto.staged_rollout_percent": 60
    },
    "features": {
        "feature_video_rooms": true,
        "feature_group_calls": true,
        "feature_element_call_video_rooms": true
    },
    "element_call": {
        "url": "https://call.element.io",
        "use_exclusively": false
    }
}

Caddyプロキシには以下を追加。

Caddyfile

chat.example.com {
    handle /.well-known/matrix/client {
        header Content-Type application/json
        header Access-Control-Allow-Origin *
        respond `{
          "m.homeserver": {
            "base_url": "https://matrix.example.com"
          },
          "m.identity_server": {
            "base_url": "https://vector.im"
          }
        }` 200
    }
    reverse_proxy element-web:80
}

アプリで直接ユーザ登録を行うには、registration_tokenが必要。

Element-Web

Element X

補足説明

フェデレーション:

federationという設定項目がありますが、これは他のMatrixサーバと連携するための設定です。クローズドサーバの場合、allow_federation = false として下さい。

tuwunel.toml

# Controls whether federation is allowed or not. It is not recommended to
# disable this after installation due to potential federation breakage but
# this is technically not a permanent setting.
#
allow_federation = false

# Sets the default `m.federate` property for newly created rooms when the
# client does not request one. If `allow_federation` is set to false at
# the same this value is set to false it then always overrides the client
# requested `m.federate` value to false.
#
# Rooms are fixed to the setting at the time of their creation and can
# never be changed; changing this value only affects new rooms.
#
federate_created_rooms = false

Element-Webのスレッド

チャット画面の内部にスレッドという吹出しアイコンがありますが、これは特定のチャットメッセージを枝分かれさせるためのものです。

ユーザアドレス

ユーザアドレスの表記ルール(@ユーザ名:サーバドメイン)

@user001:matrix.example.com

Tumunel Dockerイメージについて

Tuwunelはdistrolessイメージで、これはGoogleが開発するアプリケーションの実行に必要な最小限の依存関係(ランタイム)のみを含んだコンテナイメージ。コンテナ内でシェルコマンドは使用できません。

APIエンドポイント

サーバとクライアントのエンドポイントは以下を参照して下さい。

クライアント:Elementの特徴(Matrix準拠)

暗号化

ダブルラチェットプロトコルによる暗号化。

クロスサイン認証:ユーザーを認証すると、その信頼はそのユーザーが持つ全デバイスに自動的に引き継がれます。デバイスごとに個別に認証する手間が省けます。

フェデレーション

他のネットワークとの連携。メールサーバのように他のドメインに所属するユーザアドレス同士のコミュニケーションが可能。

データ所有権

データは登録したドメインのサーバのみが所有・管理できるが、データそのものは暗号化されているためユーザ以外では解読不可能。

分散型ネットワーク

分散型サーバで完全に独立した運営が可能。