🔔

MattermostのスマホPush通知をWebhook+ntfy(Basic認証付き)で代用する

2024/02/13に公開

TL;DR

  • server.ymlでauth-default-access: deny-all
  • ntfy user add posterntfy access poster \"*\" wo && ntfy token add posterで投稿用トークン取得
  • ntfy user add ユーザー名ntfy access ユーザー名 "*" roで購読権限付与
  • Webhookサーバーを建て、Mattermostからのメッセージをカスタマイズしてトークン認証でntfyサーバーにPOST

背景

自前の通知サーバーを用いたMattermost公式のAndroidへのプッシュ通知方法は、アプリのソースコードに認証情報を含めたうえでビルドする必要があり、アプリ側のメンテナンスの手間を考えると使い勝手があまり良いとは言えない。

一方で、自前の通知サーバーを用いてモバイル端末に様々な通知を行えるntfyがある。

設定次第でBasic認証によるアクセス制限をかけることができるので、これで代用する。

Webhook+ntfyのメリット・デメリット

メリット

  • アプリを自前でビルドする必要がない
  • 通知内容を自由にカスタマイズできる

デメリット

  • Webhookをチャンネルごとに設定 or メッセージの条件指定しなければならない
  • 内向きWebhookの投稿は検知不可(外向きWebhookの動作依存、Botは検知可)
  • ダイレクトメッセージ検知不可
  • ユーザー単位での通知切り替え方法が面倒
  • 離席中判定不可

向いている・向いていないシーン

向いているシーン

  • 特定のチャンネルの単純な投稿通知だけしたい
  • ダイレクトメッセージの通知は無しでいい

向いていないシーン

  • 離席中でないときには通知しない、ダイレクトメッセージも通知する等、Mattermost標準通知機能と同等な通知にしたい

内容

システムイメージ

手順

1. 外向きのウェブフックの設定

WebhookサーバーをMattermostと同一ローカルネットワーク上に置く場合は、システムコンソールから以下を設定

  • ウェブサーバー→「安全でない外向きの接続を有効にする」を有効
  • 開発者→「信頼されていない内部接続を許可する」にCIDRあるいはローカルで名前解決可能なホスト名を追加

統合機能→外向きのウェブフックのコールバックURLにWebhookサーバーのURLを設定

2. ntfyサーバーの構築

本手順ではDocker版を想定

  • server.ymlファイルの作成
    サンプルを参考に設定
    server.yml
    listen-https: ":443" # SSLにしないとBasic認証や通知内容が丸見えになる アカウントを作らずnginxでのみSSL+認証させる方法もあり
    key-file: /certs/key.pem
    cert-file: /certs/server.pem
    cache-file: /var/cache/ntfy/cache.db
    cache-duration: "12h"
    behind-proxy: false # nginx等のリバースプロキシを挟む場合はtrue
    enable-signup: false # falseにしておかないと、外部からntfyサーバーに誰でもブラウザから登録できてしまうので、明示的に無効化
    enable-login: false # ブラウザからログインしたければtrue
    auth-file: /var/cache/ntfy/user.db
    auth-default-access: deny-all # 基本はアカウント無しでアクセスさせない
    
  • サーバー起動
    あらかじめ./certs/以下にSSL証明書(server.pem)と秘密鍵(key.pem)を配置しておく
    command
    docker run -d --restart=always \
      -p 36444:443 \
      -v ./server.yml:/etc/ntfy/server.yml:rw \
      -v ./cache:/var/cache/ntfy:rw \
      -v ./certs:/certs:ro \
      -e TZ=Asia/Tokyo \
      --name ntfy \
      binwiederhier/ntfy:v2.8.0-amd64 \
      serve
    

2. ntfyユーザーの作成

  • 投稿用アカウントの作成
    command
    docker exec -it ntfy \
      sh -c "NTFY_PASSWORD=パスワード ntfy user add poster && \
      ntfy access poster \"*\" wo && \
      ntfy token add poster"
    
    生成されたトークンをメモしておく
  • 購読用アカウントの作成
    command
    docker exec -it ntfy \
      sh -c "NTFY_PASSWORD=パスワード ntfy user add testuser && \
        ntfy access testuser \"*\" ro"
    

3. スマホ側での購読設定

Androidの例
ntfyアプリから、上記ntfyサーバーのトピックを追加
トピック名は本手順ではMattermostチャンネル名とする

ログインを求められるので、購読用アカウントでログイン

トピックを受信できる状態

4. Webhookサーバーの構築

server.py
import requests
from flask import Flask, jsonify, request


def main() -> None:
    app: Flask = Flask(__name__)
    headers: dict[str, str] = dict(
        Authorization=f"Bearer ntfyの投稿用アカウントのトークン"
    )
    mattermost_host: str = "Mattermostのホスト:ポート番号"

    @app.post("/")
    def webhook():
        team, channel_name, user, id, text = [
            request.json[key]
            for key in ("team_id", "channel_name", "user_name", "post_id", "text")
        ]
        # 通知内容やタイトル、クリック時の挙動はここでカスタマイズできる
        print(
            requests.post(
                "https://ntfyサーバーアドレス",
                headers=headers,
                json=dict(
                    topic=channel_name,
                    message=text,
                    title=f"{user} posted on {channel_name}",
                    click=f"mattermost://{mattermost_host}/{team}/pl/{id}",
                ),
            ).json()
        )
        return jsonify({})

    app.run(
        host="0.0.0.0", port=Webhookサーバーのポート番号, ssl_context=("server.pem", "key.pem"), threaded=True
    )


if __name__ == "__main__":
    main()
command
python server.py

5. メッセージ送信

Botで投稿の例

command
curl \
    -H 'Authorization: Bearer MattermostのBotのトークン' \
    -H 'Content-Type: application/json' \
    -d "{\"channel_id\": \"チャンネルID\", \"message\": \"test message\"}" \
    https://MattermostのURL/api/v4/posts


うまくいくとWebhook側に以下のようなメッセージが出る

{'id': '***', 'time': ***, 'expires': ***, 'event': 'message', 'topic': 'example', 'title': 'chatbot posted on example', 'message': 'test message', 'click': 'mattermost://MattermostのURL/チャンネルID/pl/メッセージID'}
***.***.***.*** - - [**/***/**** **:**:**] "POST / HTTP/1.1" 200 -

スマホ側に通知が行く

通知をタップ

Mattermostアプリが立ち上がり、メッセージに飛ぶ

補足

mattermost://について

クリックのアクションにURLを指定する際、httpやhttpsで始めると既定のブラウザが開いてしまい、Mattermostアプリは立ち上がらない。
直接Mattermostを立ち上げる仕様はどこにも記載がなかったが、メッセージ仕様を見ると例に「twitter://」でtwitter(現X)が開くと記載がある。
おそらくntfyのアプリの仕様というより、OS側でカスタムプロトコルを指定するとアプリを推測するような仕組みがあると思われるが、偶々動作を確認しただけなので確証はない。
また、iOSでの動作は未確認。

ユーザー単位での通知動作切り替えについて

Mattermost標準の通知設定では自分にメンションされた時のみにすることができるが、ntfyで実現するためにはメッセージを解析したうえでユーザーごとにトピックを切り替える必要があるので、あまり現実的ではない。

参考

ntfyメッセージ仕様

Docker

Discussion