2023/10/01

MatrixでDiscord/Slackを所有する

先日のDiscordの障害は華金を電脳空間で過ごそうと決めていた人々を絶望の淵に叩き込んだ。きょうびオンラインゲームをやるとなったらボイスチャットはほぼ必須であり、とりわけDiscordは当該の分野で支配的な地位を占めている。ちなみに、僕は独りで黙々とCounter-Strike2のランクマッチを回していたので関係なかった。静謐。

しかし他に寄辺のない異常独身男性が集結せしDiscordサーバを運用している立場としては、一つのサービスの死がコミュニケーションの喪失を引き起こしかねない現状はまったく好ましくない。彼らは他のSNSをあまり使わない。以前から代替ツールを模索してはいたが、企業運営の類似サービスでは面白みに欠けるし、かといってセルフホスト型だと「そのためだけのアカウント」が余分に増えてしまう。なにしろ従来の形態ではサーバごとに別のアカウントを作らなければならないのだ。

そこで、ようやく手を出したのがMatrixだった。その強固なセキュア性や独立性から情報技術系のコミュニティで広く使われていたり、やたら熱心な信奉者を見かけるこのプロトコルは、分散型ネットワークを形成するためセルフホスト型でありながら一つのアカウントで他のサーバに接続することができる。将来、プロトコルの発展次第で作ったアカウントが活かせるのは説得材料になりうる。

7月に自鯖を持って以来、長らくActivityPubにかかりきりだったがそろそろ他の分散型プロトコルを触っても良い頃合いだ。さしあたり避難所的に運用してMatrixと関連エコシステムの手触りを学んでおいて損はない。本稿はMatrixのリファレンス実装サーバであるSynapseと、WebクライアントのElementを利用した構築方法について記す。

ファイルの取得と編集

dockerおよびdocker-composeは導入済みと仮定する。まず、任意のディレクトリを作成して一階層ぶん深い位置にdocker-compose.ymlファイルを作る。仮にユーザ名がmatrixの場合、/home/matrix/1/2/のような形式になる。下記の記述例ではこの1に該当するディレクトリにファイル群が展開される。

 1version: '3'
 2services:
 3
 4  redis:
 5    restart: always
 6    image: redis:4.0-alpine
 7    volumes:
 8      - ../redis:/data
 9    networks:
10      - internal_network
11
12  db:
13    restart: always
14    image: postgres:13.2-alpine
15    volumes:
16      - ../db:/var/lib/postgresql/data
17    networks:
18      - internal_network
19    environment:
20      - POSTGRES_PASSWORD=乱数生成
21      - POSTGRES_USER=synapse
22      - POSTGRES_DB=synapse
23      - POSTGRES_INITDB_ARGS=--encoding=UTF-8 --locale=C
24
25  synapse:
26    restart: always
27    image: matrixdotorg/synapse:latest
28    volumes:
29      - ../data:/data
30    environment:
31      - SYNAPSE_SERVER_NAME=あんたのドメイン
32      - SYNAPSE_REPORT_STATS=yes
33    ports:
34      - "7654:8008"
35    networks:
36      - external_network
37      - internal_network
38
39networks:
40  external_network:
41  internal_network:
42    internal: true

POSTGRES_PASSWORDopenssl rand -hex 16などで乱数生成する。portsの左側はデフォルトの8008番ポートが埋まっている際に置き換える。変更が済んだらdocker-compose run --rm synapse generateでファイル群を吐き出させる。次に、/home/matrix/1/data/homeserver.yamlの編集に進む。

 1server_name: "あんたのドメイン"
 2public_baseurl: https://あんたのドメイン/
 3allow_public_rooms_without_auth: true
 4allow_public_rooms_over_federation: true
 5admin_contact: 'mailto:あんたのメールアドレス'
 6pid_file: /data/homeserver.pid
 7listeners:
 8  - port: 8008
 9    tls: false
10    type: http
11    x_forwarded: true
12    resources:
13      - names: [client, federation]
14        compress: false
15database:
16  name: psycopg2
17  args:
18    user: synapse
19    password: POSTGRES_PASSWORDと同じ
20    database: synapse
21    host: db
22    cp_min: 5
23    cp_max: 10
24enable_registration: false
25enable_registration_without_verification: false
26client_base_url: "http://あんたのドメイン/"
27enabled: true
28host: redis
29port: 6379
30log_config: "/data/あんたのドメイン.log.config"
31media_store_path: /data/media_store
32registration_shared_secret: "自動生成"
33report_stats: true
34macaroon_secret_key: "自動生成"
35form_secret: "自動生成"
36signing_key_path: "/data/あんたのドメイン.signing.key"
37trusted_key_servers:
38  - server_name: "matrix.org"
39suppress_key_server_warning: true

大抵の環境ではドメイン部分の修正のみで機能すると思われる。自動生成と記されている箇所は失うと一巻の終わりなのでバックアップをとっておく。保存後、docker-compose downで一旦終了してからdocker-compose up -dで再起動を行う。なぜかたまに起動がコケるのでdocker-compose logs -fで変なエラーが出ていないか確認する。

Webクライアントの導入

ElementはMatrix用クライアントの一つである。他にもいくつか種類があるが今のところはElementが頭ひとつ抜けている。当初はこれもdocker-compose.ymlに加えてコンテナ化するつもりでいたが、うまく動かなかったのでビルド済みのバイナリをディレクトリに直接置く形を採った。公式のドキュメントでもそのやり方が推奨されているようだ。

まずは/home/matrix/1/elementなどの形式で任意のディレクトリを作成して、そこにwget https://github.com/vector-im/element-web/releases/download/vv1.11.45/element-v1.11.45.tar.gzでファイルを置く。バージョンは2023年10月1日時点での最新。ダウンロードが済み次第、tar xvzf element-v1,11.45.tar.gzで解凍する。続いて、element-v1.11.45/config.jsonの編集を行う。

 1{
 2    "default_server_config": {
 3        "m.homeserver": {
 4            "base_url": "https://あんたのドメイン",
 5            "server_name": "あんたのドメイン"
 6        },
 7        "m.identity_server": {
 8            "base_url": "https://vector.im"
 9        }
10    },
11    "disable_custom_urls": false,
12    "disable_guests": true,
13    "disable_login_language_selector": false,
14    "disable_3pid_login": false,
15    "brand": "Element",
16    "integrations_ui_url": "https://scalar.vector.im/",
17    "integrations_rest_url": "https://scalar.vector.im/api",
18    "integrations_widgets_urls": [
19        "https://scalar.vector.im/_matrix/integrations/v1",
20        "https://scalar.vector.im/api",
21        "https://scalar-staging.vector.im/_matrix/integrations/v1",
22        "https://scalar-staging.vector.im/api",
23        "https://scalar-staging.riot.im/scalar/api"
24    ],
25    "default_country_code": "JP",
26    "show_labs_settings": false,
27    "features": {},
28    "default_federate": true,
29    "default_theme": "light",
30    "room_directory": {
31        "servers": ["matrix.org", "gitter.im", "matrix.fedibird.com"]
32    },
33    "enable_presence_by_hs_url": {
34        "https://matrix.org": false,
35        "https://matrix-client.matrix.org": false
36    },
37    "setting_defaults": {
38        "breadcrumbs": true
39    },
40    "jitsi": {
41        "preferred_domain": "meet.element.io"
42    },
43    "element_call": {
44        "url": "https://call.element.io",
45        "participant_limit": 8,
46        "brand": "Element Call"
47    },
48    "map_style_url": "自動生成"
49}

ここもドメイン部分以外にあえていじる箇所はそう多くない。serversの欄には利用者が多そうなサーバをあらかじめ列挙している。

リバースプロキシの設定

/etc/nginx/sites-enabled/に任意の名前でconfファイルを作成する。

 1server {
 2  listen 443 ssl http2;
 3  listen 8448 ssl http2;
 4
 5  server_name あんたのドメイン;
 6
 7  ssl_certificate     /etc/ssl/certs/あんたのドメイン.pem;
 8  ssl_certificate_key /etc/ssl/private/あんたのドメイン.key;
 9
10  server_tokens off;
11
12  gzip on;
13  gzip_types text/css application/javascript image/svg+xml;
14  gzip_vary on;
15
16  add_header Strict-Transport-Security "max-age=63072000";
17
18  add_header X-Frame-Options SAMEORIGIN;
19  add_header X-Content-Type-Options nosniff;
20  add_header X-XSS-Protection "1; mode=block";
21  add_header Content-Security-Policy "frame-ancestors 'none'";
22
23  location /.well-known/matrix/client {
24    return 200 '{"m.homeserver": {"base_url": "https://あんたのドメイン/"}}';
25    default_type application/json;
26    add_header Access-Control-Allow-Origin *;
27  }
28
29  location /.well-known/matrix/server {
30    add_header 'Content-Type' 'application/json';
31    return 200 '{ "m.server": "あんたのドメイン:443" }';
32  }
33
34  location ~ ^(/_matrix|/_synapse/client) {
35    proxy_pass http://localhost:7654;
36    proxy_set_header X-Forwarded-For $remote_addr;
37    proxy_set_header X-Forwarded-Proto $scheme;
38    proxy_set_header Host $host;
39    client_max_body_size 50M;
40  }
41 
42  root /home/matrix/matrix/static/element-v1.11.45;
43
44}

Cloudflareユーザでオリジンサーバ証明書を設定していない人はこの記事の冒頭を参考に取得することを強くおすすめしたい。他に特筆すべき点はHTTPSポートの443番以外に8448番ポートをlistenしているところで、これは他のサーバと通信する際に用いられている。

同様に、.well-known/*の記述は連合機能を正常に動作させる認証として働く。すべての作業が終わった後にMatrix Federation Testerにドメインを入力すると前もってサーバの状態を調査できる。必須の作業工程ではないものの、障害発生時のトラブルシューティングにはなにかと役立つ。

nginx -tで構文エラーの有無を確認して、systemctl restart nginxで再起動を行う。以上の手順に誤りがなければ設定したドメインからElementのスタートページにアクセスできるはずだ。ただし、homeserver.yamlで登録を無効化しているため、最初のユーザ登録はCLIで実施する。

1docker-compose exec synapse register_new_matrix_user -c /data/homeserver.yaml http://localhost:8008 -u あんたのユーザ名 -p あんたのパスワード -a

なお、ここでパスワードに記号類を含めると正常にハッシュ化されずログインが不可能になる。これを避けるには英数字のみで作成してからWeb上で変更するか、またはhomeserver.yamlenable_registrationenable_registration_without_verificationtrueに書き換えて登録もWeb上で行う。無事にログインに成功したら構築はおおむね成功と見てよい。

機能の確認

最後に、Matrixプロトコルの醍醐味である連合機能を検証する。「ルーム」の横の+ボタンから前述のserversに列挙したサーバに属するルームを検索できる。ルームへの初回参加には仕様上かなりの時間がかかるが、参加後はDiscordやSlackとほとんど同じようにE2EE対応のテキストチャット、VoIP、ビデオ通話などが使える。

(大規模Mastodonインスタンスで知られるFedibirdの雑談ルームにお邪魔したところ、さっそく鯖缶に捕捉された。)

設計思想の違いとして、上記二つはサーバと各ルームが強力に紐付けられているのに対して、Matrixはルーム単位での参加が可能な点が挙げられる。これによりユーザは単一のサイドパネル上で個別のサーバのルームを一覧化できる。分散型ネットワークで構成されている割にサーバ全体を意識せず横断が行えるのは素直に使い勝手に優れていると感じた。

DiscordやSlackでは特定のサーバの一つのルームが目当てでも、UIの都合上、サーバ単位での参加が前提なので中央集権型にもかかわらず管理が手間と感じることが少なくない。一方、Matrixは分散型の利点を維持しつつも高度な透過性をもたらすエコシステムを実現している。

営利企業にしては良心的な運営方針を持つDiscordやSlackの牙城を崩すのは難しいかもしれないが、何者にも支配されない自由な私的空間として、あるいは単純に緊急時の避難所としてひとまずホームサーバを所有する意義は大いにあると思われる。なんにせよ、無意識に受け入れていた様々な仕様を見直す上でも競合の存在は必要不可欠に違いない。

©2011 Rikuoh Tsujitani | Fediverse | Bluesky | Keyoxide | RSS | 小説