ActivityPub対応ブログシステム
この記事をMisskeyやMastodonからご覧の皆様こんにちは。 ぜひ本ノート最上部のリンクもしくは「オリジナルのページを表示」をクリックしてみてください。
この記事をブラウザからご覧の皆様こんにちは。 ぜひ本記事のURLをコピーして、MisskeyやMastodonから「照会」してみてください。
概要
言いたいことは以上で伝わったはずですが、ちゃんとした説明もします。 要は、このサイトをどう作ったかという紹介です。
ActivityPub
MisskeyやMastodonなどのSNSソフトウェアが近年注目を集めています。 これらはOSSとして提供されており、さらに複数のサーバ間で投稿(以降ノート)をやり取りする機能を持っています。 これにより、Xのように中央集権的(単一のサーバに全ユーザがアクセスするという意味論)ではない分散したコミュニティを築きつつも、サーバを越えたやり取りも可能にしています。
この、サーバ間のやり取りに使われるのがActivityPubプロトコルです1。 つまりこのプロトコルに従えば、SNSでなくとも同じインターフェースで情報のやり取りが可能になります。 たとえばブログであれば、SNSにリンクを貼って自分のサイトへ誘導するのではなく内容そのものをSNSに流せるわけです(当然ある程度の制約はある)。 またユーザのフォロー機能を利用すれば、新しく書いた記事を見たい人へそのまま自動で流すこともできます。
システム要件
MisskeyやMastodonは普通にLinuxなどのサーバ上で常時動かすことを想定して設計されたシステムであり、常時最低3~4プロセス程度の起動が必要になります。 一方、ブログを配信したいだけの要件でLinuxサーバの管理はしたくありません。 シンプルにブログを配信するだけの、構築と管理が一番簡単なシステムを考えると
- GitHub上でMarkdownとしてコンテンツを管理
- AstroなどのSSGツールでビルドし、GitHub PagesもしくはCloudflare Pagesへデプロイ
という構成になるでしょう。 この構成からスタートして、ActivityPubのサポートに必要なシステム構成を考えます。
ActivityPubではAccept: application/activity+jsonなどのヘッダをつけてリクエストすると専用のjson-ld形式でレスポンスが返ってくることを前提に仕様が決められています。
ユーザビリティを考慮すると、あるURLに対してブラウザから普通にアクセスした場合と当該ヘッダ付きでアクセスした場合でレスポンス形式を変えるのがよいでしょう。
これには、AWS Lambda、Azure Functions、Cloudflare WorkersなどのFaaSを配信の前段に配置することで可能になります。
また、フォロー要求やリプライなど外部からのリクエストを受け取って処理する機構も必要です。 プログラムは同じFaaSに載ると思われますが、フォローなどの処理をするなら別途DBを用意する必要があることがわかります。
最後に、新しい記事をフォロワー全員に配信する処理やフォローリクエストの処理などにおいて負荷分散やリトライの簡略化のため、何らかのJob Queueが必要でしょう2。
ブログを配信したいという今回の要件では、必要なシステムはこれだけで済みます。 これは、コンテンツの書き込みがGitHub経由でのみ行われるという特異性により実現できているもので、Mastodonや通常のブログサービスと同様にWebブラウザからコンテンツを書き込みたい場合には認証まわりを考える必要があるでしょう。 また、管理するデータ複雑になるとDBの要件も変わってきます。
システム構成
先述の要件からシステムの大半をCloudflareに載せられそうなので、以下のような構成になりました。
- FaaS: Cloudflare Workers
- ブログ本体などの配信: Cloudflare Workersにアセットを載せる
- DB: Cloudflare D1, Cloudflare R2
- Job Queue: Cloudflare Queue
Cloudflareのサービスは無料枠があるのでなんと無料に…とは残念ながらならず、Cloudflare QueueのためにWorkers Paid Plan($5/month)を用意する必要がありました。 ドメインも必要ですが、私の場合は元々持っていたドメインのサブドメインを使うので実質無料です。
デプロイ処理
ブログ記事自体をGitHubで管理することにしたので、記事を編集してmainブランチにマージされたものをGitHub Actionsで自動デプロイすることを目指します。 また、バックエンド部分はフロントエンド(サイトのデザインなど)に依存しないので別リポジトリで管理し、IaCのように使います。 つまり、バックエンドを定義しているリポジトリに用意されているWorkflowで行うべきことは
- システムが存在しなければ作成し初期化
- 記事本体のmarkdownからActivityPub用のjson-ldをビルド
- フロントエンド側でビルドされた記事htmlと合わせてデプロイ
- 記事の差分に基づいてフォロワーにActivityPubメッセージを送る(正確にはCloudflare Workersから送るので、Cloudflare Queueにメッセージを入れるだけ)
というものです。 1.と3.はCloudflareの仕組みに乗っかってやるだけです。 2.では、Astroを利用してjson-ldをビルドしています。 このブログに限ってはフロントエンドもAstroなので、記事更新の度にAstroビルドが2回走ることになります。 4.は少し難しく、ステートレスなGitHub Actionsで記事の更新という情報を処理しなければなりません。 このためにCloudflare R2に現在デプロイ済みの記事とそのハッシュの情報を保存しておき、ビルド終了後に計算したハッシュから記事の追加更新削除を検知しています。 これにより、GitHubで管理しているMarkdownをGitHub Actionsでデプロイするというシンプルな手順から外れることなくActivityPub対応ができました。
まとめ
本記事では、シンプルな管理手順のままでブログシステムをActivityPubプロトコルに対応させる試みについて紹介しました。 本ブログのソースコードはWhite-Green/blog、バックエンド側はWhite-Green/fblog_systemにて公開しています。 またフロントエンドとバックエンドが完全に独立しており、同じバックエンドを利用して誰でも何個でもデプロイができるよう設計しているので、利用したい方はデプロイ用Workflowを参考に、自己責任で利用してください。