FICUSONLINE F9E
チャット・ビデオ通話システム:Tuwunel (Matrix Server) on Docker
Matrixとは、リアルタイムのオンラインコミュニケーションに最適化された、比較的新しい分散型のオープンソース通信プロトコルです。1対1のチャットや音声通話はもちろん、グループチャットやビデオ通話にも優れています。 このMatrixに準拠したサーバは分散型であり、異なるサーバに所属するユーザー同士でも、メールのように共通プロトコルを通じて容易に相互アクセスできるよう設計されています。
Takanobu FuseAdministrator

5 months ago

Cloud / Server

Matrixサーバの構築

Matrixというプロトコルを利用したチャット・ビデオ通話システムをDockerコンテナにより運用します。

フォーラム記事:Tuwunel ( [Matrix] サーバ ) for Docker を整理した内容です。

  • Matrixとは
  • 導入効果
  • 特徴
  • Matrixサーバ
  • Matrixクライアント
  • システム条件
  • システム構成
  • 各コンテナ設定ファイル
  • Docker Composeファイル
  • 起動・操作

Matrixとは

Matrixとは、リアルタイムのオンラインコミュニケーションに最適化された、比較的新しい分散型のオープンソース通信プロトコルです。1対1のチャットや音声通話はもちろん、グループチャットやビデオ通話にも優れています。

このMatrixに準拠したサーバは分散型であり、異なるサーバに所属するユーザー同士でも、メールのように共通プロトコルを通じて容易に相互アクセスできるよう設計されています。

チャットや通話はクライアントアプリによってエンドツーエンドで暗号化されており、セキュリティ面でも十分な機能を備えています。エンドツーエンド暗号化が有効なルームでは、メッセージは暗号化された状態でサーバに保存され、クライアントが保持する暗号鍵または復元用のキーバックアップ情報なしでは解読できない仕組みとなっています。

導入効果

Matrixを導入することで、社内外のコミュニケーション基盤を一元化し、情報共有のスピードと正確性を大幅に向上できます。

チャット、音声通話、ビデオ通話を単一のプラットフォーム上で運用できるため、メール・電話・複数のSNSやチャットツールに分散していた連絡手段を集約し、運用コストの削減と業務効率化を実現します。

また、分散型プロトコルを採用しているため、自社サーバによる独立運用が可能であり、外部サービスへの依存を最小限に抑えられます。これにより、機密性の高い情報やプロジェクトデータを自社管理下で安全に運用できます。

エンドツーエンド暗号化により、通信内容の盗聴や漏えいリスクを低減し、セキュリティ要件の厳しい業務用途にも対応可能です。特に、設計開発部門、医療・教育機関、顧客サポート窓口など、高い情報保護が求められる環境で効果を発揮します。

さらに、プロジェクト単位でスペースやルームを柔軟に構成できるため、部門横断型のチーム運営やリモートワーク環境においても、リアルタイムかつ継続的なコミュニケーション基盤として活用できます。

既存のSNSや外部チャットサービスとのブリッジ連携を活用することで、既存ユーザー資産を活かしながら段階的な移行も可能です。

特徴

自前で用意したサーバであれば、使用するホームサーバ実装に応じて、招待制やトークンによるユーザー登録制限を設定できます。

また、管理者には専用の管理ルームや管理APIへのアクセス権限を付与し、ユーザー管理やルーム管理などの各種運用作業を行うことが可能です。

ユーザーは必要に応じてSpace(スペース)を作成し、その中に公開ルームまたは非公開ルームを複数構成できます。非公開ルームでは、他のユーザーを招待してチャットや通話を開始することが可能です。

スペースやルームは無制限に作成でき、名称も任意に設定できます。たとえば、スペースを「デザインプロジェクトチーム」、ルームを「メカ」「エレキ」「ソフト」といった形で構成することで、メンバー間のリアルタイムコミュニケーションを実現する仮想空間を構築できます。

Matrixサーバ

Matrixに準拠したサーバは以下の通りです。今回はRustでコーディングされたオープンソースのTuwunelを採用します。

continuwuity

  • Stable
  • Apache-2.0 Rust
  • Continuwuity, a community driven 2nd degree fork of Conduit focusing on user experience and new features.
  • Repository Matrix Room

Synapse Pro

  • Stable
  • Element Commercial License Python and Rust
  • Synapse for Enterprise, with a focus on performance, scalability and compliance.
  • Homepage Matrix Room

Tuwunel

  • Stable
  • Apache-2.0 Rust
  • Enterprise successor to conduwuit, the high-performance and feature-rich fork of Conduit.
  • Repository Matrix Room

Synapse

  • Stable
  • AGPL-3.0-or-later OR Element Commercial License Python
  • Synapse is a Matrix homeserver written in Python/Twisted.
  • Repository Matrix Room

Conduit

  • Beta
  • Apache-2.0 Rust
  • Conduit is a simple, fast and reliable chat server written in Rust
  • Repository Matrix Room

Dendrite

  • Beta
  • AGPL-3.0-or-later OR Element Commercial License Go
  • Dendrite is a second-generation Matrix homeserver written in Go!
  • Repository Matrix Room

Telodendria

  • Alpha
  • MIT C
  • Telodendria is an open source Matrix homeserver implementation written from scratch in ANSI C and designed to be lightweight and simple, yet functional.
  • Repository Matrix Room

Matrixクライアント

Matrix準拠の代表的なクライアントアプリは以下の通りですが、他にも多く存在します。今回はAndroid版Element XとElement WEBを使用します。 Element-Webは公式サイトで公開されていますが、自前でも用意できます。

Nheko

Cinny

Element Web / Desktop

Element X

FluffyChat

システム条件

ホストPC

QEMU/KVMで作成したVM:Debian13(2コアCPU、メモリ2GB、ストレージ60GB)

取得ドメイン

システム構成

DockerコンテナでTuwunelとElement Webのフロントエンドを構築します。

Dockerコンテナによる全体システム構成は以下の通りです。CoturnのTLS認証はCertbotコンテナが常駐して管理します。

Matrix Docker System

公開ポート

  • 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)

各コンテナ設定ファイル

各コンテナの設定ファイルは以下の通りです。TuwunelはMatrixサーバコンテナ、CaddyはTLS認証とプロキシサーバを兼ねたコンテナ、LiveKitはリアルタイム通話の際に利用するWEB-RTC SFU(Selective Forwarding Unit)コンテナ、CoturnはLiveKitがNAT越えが必要となる場合に利用されるTurnサーバコンテナです。

各種設定ファイルのルールについては公式サイトを参照願います。


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

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 
    }
  • 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]の項目は不要(全てコメントアウト)


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

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

Docker Composeファイル


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 

  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

networks:
  caddy:
    external: true

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

起動・操作

システムの起動・ユーザ登録

以下のdocker composeコマンドを実行します。

$ docker compose up -d

起動後、Element-XやウェブアプリなどのMatrixクライアントを利用してユーザ登録を行います。

ユーザ登録には、

  • Matrixサーバのドメイン:matrix.example.com
  • tuwunel.toml ファイルで設定したトークン:registration_token

が必要です。

この一番最初の登録ユーザが管理ユーザとして承認され、管理ルームへの入室を許可されます。管理ルームはデフォルトでTuwunel初回起動時に作成されます。

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

tuwunel.toml

grant_admin_to_first_user = true
create_admin_room = true

他のユーザは、管理ユーザから付与されたトークンを利用してユーザ登録するか、管理ユーザが作成したユーザ名とパスワードを使ってログインします。

管理ユーザによる操作

管理ユーザは、他ユーザの登録・作成などの各種操作を管理ルームで行うことができます。

name: matrix.example.com 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-Web sign-in

Element-Web create room

Element-Web invite

Element-Web chat

Element-Web video

Element-Web group video

クライアント:Element-X

Element-X approve

Element-X chat room list

Element-X chat

Element-X participate chat

Element-X group video