前回までの続き。MulmoClaudeはローカルのMacBookで動かしていたので、閉じたり持ち歩いたりすると使えなくなる。外出先や自宅以外の場所からも、ちゃんとしたWeb UIでアクセスしたい場面が増えていた。Telegram botは複数スレッドの並行やMarkdown表示が弱く、そろそろ限界を感じていた。
選択肢を比べてみた
ローカルを24時間つけっぱなしにする案は電気代と持ち運びの都合で却下。自分のマシンから直接SSHで繋ぐ案も、企業ネットワークは outbound の port 22(SSH)をブロックしていることが多いので早々に除外した。代わりに、HTTPS越しにドメイン経由でアクセスし、手前に認証ゲート(Cloudflare Access)を置く構成にした。ログインはGoogleアカウント、加えてアプリ自体の共有シークレット(MULMOCLAUDE_AUTH_TOKEN)による二重の認証にした。
クラウド事業者はさくらのクラウドと迷ったが、Singaporeリージョンのレイテンシを取ってAWS Lightsailを選択。最初は $7/mo(1GB RAM、2 vCPU、40GB SSD)のバンドルから始め、足りなければ後から上げる方針にした。
CSRFのtrusted originで送信が無音失敗
移行作業自体は完了したが、実際にブラウザからメッセージを送ると失敗する。エラーメッセージも出ず、ただ無反応になるだけだった。
原因は、アプリ内蔵のCSRFガードが Origin ヘッダーをチェックしていて、新しいドメインが .env の MULMOCLAUDE_TRUSTED_ORIGINS に追加されていなかったこと。ここに https://mulmoclaude-taiki.com を追加して systemctl restart mulmoclaude.service したら直った。MulmoClaudeを自分でセルフホストしていて、公開ドメインからの送信だけ無言で失敗する場合は、まずここを疑うといい。
原因不明の “Network error calling …” を追いかけた
数日後、presentForm や manageCollection など内部のMCPブリッジ呼び出しが断続的に “Network error calling …: fetch failed” で落ちるようになった。
最初は /proc/meminfo を見てメモリ不足を疑った。実際 MemAvailable は少なめで、swapも埋まっていた。zram-tools を入れて圧縮swap(/dev/zram0)を作り、vm.swappiness=10 に下げてみたが、症状は改善しなかった。念のためLightsailのプランも $7/mo(1GB)から $12/mo(2GB)に上げてみたが、それでも同じエラーが100%の確率で再現した。
結局、アプリのソース(server/index.ts)を読んで根本原因が分かった。アプリは app.listen(port, "127.0.0.1", ...) で loopbackにしかbindしない 設計になっている。サンドボックス内のエージェントは host.docker.internal 経由でホストにアクセスするが、Docker Desktop(Mac)ではこれがhypervisor越しにloopbackへ届くのに対し、Docker Engine(Linux)では単に docker0 ブリッジの実IP(このケースでは 172.17.0.1)に解決される。127.0.0.1にしかbindしていないアプリは、この別インターフェースからの接続を受け付けない。Macでは気づかなかった潜在バグが、Linux移行で初めて表に出た形だ。
修正は、socat でブリッジ側からloopbackへのTCPリレーを1本立てるだけだった。
ExecStart=/usr/bin/socat TCP-LISTEN:3001,bind=172.17.0.1,fork,reuseaddr TCP:127.0.0.1:3001
これをsystemdサービスとして登録したら、curl http://host.docker.internal:3001/ が200を返すようになり、presentForm/manageCollection も即座に正常化した。メモリ・zramの対応は完全に的外れだったわけではないが(実際に空きは増えた)、このエラーの直接原因ではなかった。
$12/mo(2GB)に戻した理由
原因が判明した後、節約のために元の $7/mo(1GB)へのダウングレードを試みた。ところがLightsailはスナップショット経由でのプラン縮小に対応しておらず、1GBインスタンスをゼロから作り直す必要があった。作り直したインスタンスは、Dockerのサンドボックスイメージビルドなど負荷がかかるタイミングで繰り返しハングし、SSHもLightsailのブラウザコンソールも反応しなくなる、ということが複数回起きた。
1GBではこのワークロード(MulmoClaude本体 + Dockerサンドボックス + Telegram bridge)を安定して支えきれないと判断し、$12/mo(2GB)を最終プランとして確定した。安さより安定性を優先した形だ。
常時稼働にしてみて
いくつかのつまずきを経て、今はこのサーバーが24時間動き続けている。自分のパソコンの状態に関係なく、いつでもどこからでも使える。
つづく。