Chienomi

Slack風コラボレーションツールOSS Mattermostを建てる

Live With Linux::server

MattermostはSlack風のコラボレーションツールであり、サーバー、クライアント共にOSSとなっている。 クライアントはWindows, Mac, Linux, Android, iOSに対応する。

今回Mattermostを建てたのは、Discordのかわりに「メモ&通知ツール」として使うためだ。

Discordはチャンネルごとに独立したチャットになっている。これを利用すると、カテゴリ機能のあるメモツールとして使いやすい。 (半オープンなもので秘匿はされていないので、見られたらまずい内容はやめたほうがいい。)

これだけなら他にもいくらでもあるし、別に作るのも難しくないが、「モバイル通知」という点から言うと、効率的に作るのはちょっとむずかしい。 Discordはwebhookによって簡単に外部から投稿でき、これを使って通知システムに組み込むことができる。

これを私はものすごく多用しており、なおかつサーバーダウンなど重要な通知を送っているため、Discordが届くと特別な通知になるようにしている。 これが逆に困りもので、Discordにメッセージを送られてしまうと緊急の通知として届くため、私はDiscordを他の人に教えていない。

最近はこれが不便なので、Discordを空けるためにMattermostをセルフホストすることにした。

ちなみに、もうひとつDiscordをやめたい理由としてアプリがある。 Discordは常に最新版アプリを要求し、アプリがバージョンアップするとDiscordは開けなくなる。 ここでUbuntu用のパッケージはリンクからダウンロードできるのだが、ManjaroではPKGBUILDを書く必要がある。

別に旧バージョンのPKGBUILDを書き換えても簡単に動きはするが、面倒。 そして公式の対応を待つと数日間使えないということがある。 これがちょっと困るのだ。

Mattermostサーバー概要

MattermostはTypeScript/Goで書かれたサーバーと、TypeScriptで書かれたクライアントからなる。

サービス自体はself-hostedのためのサーバーソフトウェアであると同時に、公式でSaaSとして提供されてもいる。 割とよくあるタイプのマネタイズ方式。

サーバーソフトウェアの導入は、ホストに直接導入する方式とDockerが選べる。 メンテナンスされている度合いでいうと、直接導入のほうが手厚い。

私が導入したサーバーはかなり複雑な構成になっているため、Dockerを使った。

Mattermost Docker

Archwikiに記事があり、Dockerについても触れられているが、英語版を含め内容が古いため、全く使い物にならない。

そこで、Mattermostの公式ドキュメントを読むことになるが、公式ドキュメントもまぁまぁ古いため、一部参考にならない。

ただ、Dockerに関しては公式ドキュメント通りに進めれば良い。

ArchwikiではDockerの操作を一般ユーザーでやっているけれど、実際にArchlinuxではDocker操作を一般ユーザーでやるのはちょっと大変なので、私はrootでやった。

そうするとMattermostのソース自体がrootになるのだけど、chwon -R 2000:2000は結局のところ必須。 2000:2000が存在しなくても、である。

証明書に関してはスキップできそうに見える。 特にDockerを使ってNginxをリバースプロキシにする場合はDockerがHTTPSを受ける必要はないのだけど、実際はスキップできない。 公式ドキュメントの後半にあるような方式で、Let’s Encryptの証明書fullchain.pemprivate.keyをコピーする。

注意点はこれくらいで、あとはDockerとNginxについての一般的な知識で事足りるだろう。 だが、ここからさらにポイントがある。Mattermostのエディションとバージョンだ。

エディションはMATTERMOST_IMAGE=mattermost-enterprise-editionとなっているので、mattermost-team-editionに変えておく。

Mattermostのバージョンは.envMATTERMOST_IMAGE_TAG=7.8.0に固定されている。9.3.0が最新であり、随分長い間更新されていない。 このタグはlatestが効くので、latestにして

# docker-compose pull
# docker compose -f docker-compose.yml -f docker-compose.without-nginx.yml restart

という感じでやれば解決する。

初期状態でアカウントの登録はできないようになっている(一番最初のアカウントは別)ため、勝手に利用されるということは気にしなくて良い。 ユーザーの追加は招待リンクによって行う。

Mattermostはかなりメール志向であり、メールサーバーが有効でないとプレビューモードとして扱われ、バナーが出る。 メールで細かく通知されてしまうので、そのあたりは面倒なところではある。 なお、バナーそのものは消すことが可能。

Discordとの違い

これはDiscordとSlackの違いでもあるのだが、メモ&通知用に使うのであれば、やはりDiscordのほうが少し便利だ。

まず、Discordはサーバーの状態は全体で共有される。 対して、Mattermostの場合はカテゴリなどはユーザーに帰属する設定なので、ユーザーごとに設定が必要になる。

同様にチャンネルへの参加自体も個別に行う必要があり、複数のアカウントを使う場合はチャンネルへの参加も難しい。 @channelを有効にするにもチャンネルに参加していないといけないため、だいぶ手間だ。

じゃあそももそ複数アカウント用意する必要があるのかということだが、通知管理という意味ではあったほうがいい。 通知の状態は同一アカウントなら全デバイスで共有されるため、複数のモバイルデバイスで使っていると通知がうまく機能しないケースがある。

Webhook

ロールの管理機能はなく、ロールごとにできることは決まっている。 Webhookのような統合機能の追加はSystem AdminまたはTeam Adminしか行えない。

また、Webhookでの投稿はデフォルトがWebhookを作成したユーザーとして行われるため、そうなるとbotアカウントを用意して、botアカウントをAdminにするというよくわからない扱いになる。

MattermostのwebhookはURLが認証を兼ねているタイプで、特に認証ヘッダーは必要ない。 最低限でいうと

curl -X POST -H "Content-Type: application/json" -d '{"text": "test message"}' ${URL}

という感じで良い。

チャンネルを指定する場合は

curl -X POST -H "Content-Type: application/json" -d '{"text": "test message", "channel": "town-square"}' ${URL}

という感じだけど、多分チャンネルは固定で使う運用のほうが良いだろう。

「誰として」投稿されるかはそもそもwebhookを作成したユーザーに固定されているが、「誰だと表示するか」はコントロールできる。 (ただし、config.jsonEnablePostUsernameOverridetrueにしている必要がある)

curl -X POST -H "Content-Type: application/json" -d '{"text": "test message", "username": "Super Harukamy"}'

ユーザーアイコンはURLで指定できる。 (ただし、config.jsonEnbalePostIconOverridetrueにしている必要がある) 公開されている画像である必要があり、外部ソースから読み込むというのはいまいちだ。

curl -X POST -H "Content-Type: application/json" -d '{"text": "test message", "username": "Super Harukamy", "icon_url": "https://example.com/superhaurkaicon.png"}'

そのわかりに絵文字を使うこともできる。絵文字名から:を抜いた形で指定する。 こちらもEnablePostIconOverrideが必要。

curl -X POST -H "Content-Type: application/json" -d '{"text": "test message", "username": "Super Harukamy", "icon_emoji": "smile"}'

Mattermostの感想

だいたいSlackと同じような感じだが、機能的には劣っている。

デスクトップアプリのズーム機能がないため、UIが非常に小さい、ということが発生したりする。

通知は控えめで、Slackほど主張が強くない。 モバイルアプリに関しては、X2にはちゃんと通知が飛ぶが、X6はアプリを落としているとなかなか通知がこない。

また、Mattermostの通知はHigh importanceとMin importanceの2種類があり、Min importanceはデフォルトでサイレント通知。 チャットウィンドウにはpriorityという機能があるけれど、Webhookには今のところないっぽいので、モバイルで通知を鳴らしたい場合はMin importanceでちゃんと鳴るように設定する必要がある。

全体的に軽めに作られてはいるから、自分用のサーバーがあるなら動かすのも悪くないとは思う。 しかし、本当にコラボレーションツールとして使う場合は少し微妙。 Slackのような他のサービスと比べて機能的に劣っているという問題もあるし、コラボレーションツールとして、特に通話を含めて本格的に利用するためのリソースコストは馬鹿にならないという問題もある。

それに、プライバシーに関しても実はちょっと微妙。 クラウドサーバーで稼働させる場合、データは潜在的にクラウドプロバイダーによってアクセス可能なものであり、ちゃんと秘匿されているとは言い難い。 機密性を求めて選択するのであれば、E2EE前提のThreema WorkやWireなどを検討したほうがいいだろう。

いずれにせよ、この手のサービスをself-hostedにするというのはそもそも割と微妙な話で、色々な大変さを考えるとライトに構築・管理できるようにしたほうがいい。 本格的に使うなら、サードパーティのサービスを先に検討したほうがいいだろう。

また、Mattermostをself-hostedで通知用に使うと、Mattermostを動かしているサーバーが落ちたときの通知には使えないことにも注意。

対して、SlackやDiscordと比べて良い点を述べよう。

まず、メッセージがMarkdownサポートになっていること。 Extensions込みのGFMをサポートしてるっぽくて、試した限りだと

  • リスト
  • ネストリスト
  • 順序付きリスト (マーカーフォーマットもサポート)
  • タスクリスト
  • コードブロック (シンタックスハイライトもサポート)
  • インラインコード
  • em
  • strong
  • ヘッダー
  • hr
  • strike through
  • リンク ([title](url))
  • オートリンク (<url>)
  • リンクリファレンス
  • テーブル

が利用できた。

次に、チャンネル単位でモバイル通知設定ができること。 そもそも@channelなどを無視する設定ができるけれど、どういう場合に通知するか(全メッセージ/メンション/通知しない)という設定を、グローバル設定をオーバーライドする形でチャンネルでも設定できる。 それとは別に(Slack同様に)ミュート設定もある。

サーバーが強ければ「Slackよりもこっちのほうがいい」という人がいても全然おかしくない。

Revolt

RevoltはオープンソースのDiscordクローンである。 Slackに対するMattermostと似たような感じだとも言えるが、Revoltはself-hostedに対してあまり力を注いでいない、という点で大きく異なる。 Mattermostが「クラウドサービスとしても展開されているSlackクローン」なのに対して、Revoltは「self-hostedもできなくはないDiscordクローン」くらい違う。

明らかにドキュメントが不足しているRevoltは、Dockerで建てるのが基本のようだ。 しかし、Revoltそのものが外部サービスへの依存度が非常に高く、「self-hostedできる」という言葉から想像するのとはだいぶ違う形になる。 「ユーザーがホストするサーバーを自分たちでも使っている」ではなく、「自分たちが使っているサーバーソフトウェアを公開している」という感じ。

メモ用アプリにそこまでする気には到底なれず、諦めたが、一応revolt.chatのほうは試してみた。

結論としては、機能が決定的に不足している。

webhookもAPIとしては存在しているが、設定から作ることはできない。 APIを叩くにしてもbotを作る画面もないので、ウェブブラウザのセッショントークンを横取りするしかない。 ついでに、webhooks APIを叩くにしても、targetが意味するところがドキュメントからはわからない。 そして、webhookが作れたところで、webhookを叩く方法はドキュメントにない。

Revoltは自分たちについてまるで語らないので、信用できるかどうかの判定も困難。

基本的な機能だけなら使えるが、使うのが基本的な機能だけで、revolt.chatを使うつもりでいるのならば、それは「Discordでいい」になる。

私はその意義を感じられなかった。

通知の難しさとTelegram通知

しかしこうして見ると、「モバイル通知」というものが非常に難しい、ということが感じられる。

様々なメッセンジャーアプリやコラボレーションツール、あるいは各種Fediverse SNSのPWAやアプリも通知機能は持っているが、実際の通知は安定しない。 基本的に新しい端末ほど厳しい傾向で、X2はまだなんとかなるが、X6に関してはアプリを終了してしばらく経ってスリープになっていると、ほとんどのアプリが通知できない。 Huskyのような起きっぱなしでポーリングするようなアプリならできるが、それは電池消費が激しすぎる。

色々と試しはしたものの、通知に確実性があるのは唯一Telegramだった。 だが、TelegramはDiscordのようなwebhookはない。 ちょっと邪道なようだが、Botを使えば実現可能ではある。

Telegramのbotは@BotFatherというbotアカウントを使って作る。 メニューから簡単に作成・設定が可能だが、/newbotコマンドでボット作成が可能だ。

追加したら、ボットアカウントを検索してチャットに加え、任意のコマンドを送信する。 コマンドは/で始まるメッセージだ。

コマンドを送ったら、https://api.telegram.org/bot${token}/getUpdatesへGETリクエストして、送信されたコマンドを確認する。ここで重要になるのは、Chat ID (data.result[0].message.from.idもしくはdata.result[0].chat.id). これは、グループであれ、ユーザーであれ、チャットの対象、つまりルームのIDが入っている。

そして、Botは常にそのChat IDにメッセージを送信するようにすれば、単に自分にメッセージを送るだけのbotができる。

conn.post("/bot#{TOKEN}/sendMessage") do |req|
  req.body = JSON.dump({
    "chat_id" => CHAT_ID,
    "text" => STDIN.read
  })
end

ただし、Botアカウントは誰でも追加できるものなので、そのようなBotがTelegramにおいて許されるのかは不明だ。

このような使い方であれば、LINEのほうがいいだろう。

LINEのほうが簡単だ。 というのも、そもそもLINEにはLINE Notifyというそういう目的で使うための機能があり、話が早い。

以前自分が書いた資料によれば、LINE Notifyのマイページから登録を行ってtokenを発行して準備完了、あとは

curl -X POST -H 'Authorization: Bearer [token]' -F 'message=[message]' https://notify-api.line.me/api/notify

の形でPOSTするだけで良いようだ。

URLで分けるのとAuthirozationトークンで分けるのどっちが良いかは好みの問題だと思うけれど、個人的にはAuthorizationのトークンは認証情報であり、認証情報にはユーザーも含むものと考えられるから、ヘッダーに含める形のほうが少しだけ私は好み。

ただ、仮にLINEへの通知が確実だとして、LINEを使うのが良いかどうかは良し悪しという気がする。

通知用ツールとしてLINEが有効な人であれば、基本的にLINEをそれなりの頻度で使っているだろうと思うのだけど、それは「LINEの通知が埋もれてしまう」可能性が気になってくる。 もちろんどのようなツールであれ、通知が多すぎると同じ問題が発生するものではあるものの、LINEはとかく「教えるのを避けがたい」という状況が発生しやすく、やたらと “友だち” が多いという人が多いのではないだろうか。 もちろん、 “友だち” が多いからといって直ちに多くのメッセージを受け取っているとは限らないわけだが。

また、私が知る限りではX2でもLINEはあまり安定していなかったので、結局微妙なのかもしれない。

いずれにせよ、メッセージはMattermostに送るにしても、Mattermost以外で通知を受け取るという方法を考えも良い。 「通知があるよ」というメッセージを飛ばすだけなら、機密性が気になるチャンネルも使えるはずだ。

有力な候補はDiscord, Slack, LINEだろう。 Telegramはbotにする必要があり、やや微妙。 また、各種Fediverseアプリに通知するためにFediverseアカウントにメッセージを送るのもそこそこ現実的。

TwitterやMETA Messengerに送るのはだいぶめんどくさい。

もうちょっと特殊な例として、メールクライアントで普段使っているのとは異なるものをインストールし、専用のメールアドレスを用意してそこにメールを送って通知するという方法もある。 これは、ちょっと昔ながらの方法っぽい。

ただ私が試した限り、X6でもうまく通知できるのはDiscor

なお、X6では(つまり、おそらくは最近のAndroidスマートフォンでは)通知はほとんどの場合デフォルトでサイレント通知になるようになっている、もしくは着信音はオフになっている状態で、通知の設定からわざわざ「着信音」をオンにしないと音が鳴らないようになっている。

X2(Android11ベース)は「通知音を鳴らさない=サイレント通知」だったが、X6(Android13ベース)では「ドロワーを開いたときだけ見えるのがサイレント通知」という扱い。

だが、このサイレント通知問題に関係なく、X6はプッシュ通知がこない。 SMSですら、ロック解除してないと通知が来ないレベルだ。

ただ、Mattermostに関してはちゃんと通知が来るように(端末側の通知設定も、アプリの通知設定も)しておけば、遅くとも最大20分遅れくらいで届きはする印象。遅すぎるけれど。 SessionなんかはX6だと全く通知がこないので、それよりはマシだ。

昔はモバイルなんかはリアルタイムで通知できることは絶対の要求だった気がするけれど、最近はむしろ可能な限り通知を隠そうとしている印象。 通知が多すぎてユーザーが通知を嫌うようになった結果かもしれないし、単にGoogleの上層部に通知が嫌いな人がいるのかもしれない。 いずれにせよ、「プッシュ通知」と言いながらポーリング程度の感覚でしかない通知になりつつあり、「ケータイ持ってるんだから、なにかあったら知らせてほしい」という古来からある「普通の」希望を叶えるのがとても難しくなっていることを感じる。