0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

OpenClawが返してくる「ファイルパス」を Discord・スマホからすぐ開けるようにする — Tailscale + Caddy で解決

0
Posted at

痛み: 「/home/yousan/.claude/usage-data/report-...html です」と言われても開けない

OpenClaw で AI エージェントを動かしていると、エージェントは仕事の成果物をホスト上にファイルとして生成する。HTML のレポート、PDF、動画、スクショ、CSV。生成した結果としてエージェントが返してくるのは 「ホスト上の絶対パス」 だ。

できました: /home/yousan/.claude/usage-data/report-2026-06-12-111053.html

これを Discord や LINE 越しに受け取った私は、開けない。

  • ファイルパスは URL ではない。Discord でクリックしても何も起きない
  • 同じマシンに居ても、Finder で ~/Library/... を掘るなり open <path> を打つなりが要る
  • 別マシンに居ると SCP するか SSH してブラウザを開くしかない
  • iPhone から見ようとした瞬間に詰む。SCP も SSH も実用的じゃない

私の環境では OpenClaw が動いてるのは Windows 上の WSL Ubuntu (tachikoma)、私が触ってるのは Mac、外では iPhone、というクロスホスト・クロスネットワーク構成なので、 エージェントが何かファイルを作るたびに「見るための儀式」が発生 していた。これがしんどい。

エージェントの成果物の大半は HTML/画像/PDF/動画なので、本来は HTTP(S) で URL になっていれば、デバイス問わずブラウザで一発 のはずだ。これをどうにかしたい、というのが今回の話。

結論: tailnet 限定の Caddy 配信 + 日付別 share/ + エージェントへの規約

採用した形はこれ。

  • ホスト: OpenClaw が動いているマシン(私の場合は tachikoma
  • bind: Tailscale のノード IP に固定(100.x.y.z)。0.0.0.0 にはしない
  • 配信: Caddyfile_server browsesystemd の user unit として常駐
  • ルート: ~/.openclaw/workspace/share/
  • 構造: share/YYYY-MM-DD/<topic-slug>/
  • 置き方: 元ファイルは触らず symlink を置くだけ
  • アクセス: Tailscale MagicDNS でホスト名解決。Mac でも iPhone でも tailnet に入っていればそのままブラウザで開ける

これで「ファイルパス問題」は次のように形が変わる。

  • エージェントは生のホストパスを返す代わりに、share/YYYY-MM-DD/<topic-slug>/ に symlink を作って URL を返す
  • 私は Discord で URL を受け取り、Mac でも iPhone でもタップ一発で開く
  • SCP も SSH も Finder も登場しない

そして地味に効くのが Tailscale で、これが2つの問題を同時に潰してくれる。

  1. ネットワーク問題: WSL Ubuntu と Mac と iPhone は通常別ネットワークだが、tailnet 内なら直接到達できる
  2. ホスト名問題: MagicDNS のおかげで IP を覚えなくていい(http://tachikoma:8801/

さらに share/YYYY-MM-DD/ で日付別に切ってあるので、Caddy の autoindex を辿るだけで「今日エージェントが何を作ったか」を一覧で見渡せる。

share/ のルートは日付ごとに並ぶ。これがエントリポイントになる

当日に潜るとトピック単位で並ぶ。

当日ディレクトリ — その日にエージェントが生成した成果物がトピック単位で並ぶ

Caddy の file_server browse には Grid 表示と画像サムネが付いてくる。生成画像を渡すときはこっちのほうが圧倒的に見やすい。

Grid 表示は画像サムネが効く。生成画像のレビューに強い

実際の成果物を開くとこう(ヘッドレス Chrome で撮った)。Claude Code の利用状況レポートをエージェントに生成させて、ホスティングだけお願いした例。

ブラウザから開いた実際の配信ページ

なぜ Caddy なのか — 候補3つを試した結果

最初は python3 -m http.server で立てていた。これは確かに動くんだが、autoindex の見た目が <ul><li> の素の HTML で、ファイルが増えてくると一覧性が落ちる。スマホから見ると文字も小さくてつらい。

そこでファイラ系UIを持つ候補をいくつか試した。

候補 symlink 追従 UI systemd 化 認証
python3 -m http.server する 素のテキスト一覧 容易 無し
filebrowser しない1 リッチなファイラ 容易 あり
dufs する テーブル型 容易 あり
Caddy file_server browse する List/Grid/サムネ/検索/ソート 容易 リバプロで足せる

結局 Caddy に落とした。理由は単純で、

  • symlink を素直に追従する。share の外を指す symlink でも何の設定もなく見える(これは大きい)
  • Grid 表示と画像サムネ がデフォルトで効く。生成画像が多い私のユースケースに合う
  • ダークモード が OS の prefers-color-scheme に追従する。夜の iPhone でまぶしくない
  • 検索・ソート・パンくず が全部入り。File ロード時に JS が走る系ではなく、サーバ側 HTML なので軽い
  • シングルバイナリ。Go 製で 50MB 弱、依存ゼロ
  • 設定は Caddyfile 8行。実質ワンライナーみたいなもの

filebrowser が外れたのは認証 UI のせいではなく純粋に symlink の挙動。 ユーザが share の中だけにファイルを置く運用に変えれば filebrowser でもいけるが、本記事の「元ファイルは触らない」原則と相性が悪い。dufs は問題なく動くがテーブル型 UI で Grid・サムネが無い、というだけ。

構築

1. ディレクトリを切る

mkdir -p ~/.openclaw/workspace/share

2. Caddy を入れる

公式のシングルバイナリを取ってくるのが一番速い。

mkdir -p ~/bin
curl -sL "https://caddyserver.com/api/download?os=linux&arch=amd64" -o ~/bin/caddy
chmod +x ~/bin/caddy
~/bin/caddy version

apt のパッケージ(caddy)でも構わないが、share 配信用途では root 権限を要求してまで /etc/caddy/Caddyfile を使う必要はない。

3. Caddyfile を書く

~/.config/caddy/Caddyfile:

{
    admin off
    auto_https off
}

:8801 {
    bind 100.100.26.90
    root * /home/yousan/.openclaw/workspace/share
    file_server browse
    encode gzip
}

ポイントは2つ。

  • bind で listen インターフェースを tailnet IP に絞る。Caddy のリスナーは、サイトアドレスに IP を書いてもデフォルトで全インターフェース (0.0.0.0) に bind される。tailnet 限定にするには bind 100.100.26.90 を明示するのが必須で2、これを忘れると家 LAN 全体にポートが開く。そしてサイトアドレスは ホストを書かず :8801 にする — ここにホスト名や IP を書くと Host マッチが効いて、http://tachikoma:8801/(MagicDNS 名)でのアクセスが弾かれるからだ3
  • auto_https off。tailnet 内通信なので TLS 要らず。これを書かないと Caddy が証明書を取りにいこうとする

4. systemd user unit を書く

~/.config/systemd/user/share-http.service:

[Unit]
Description=Workspace share static HTTP server (Caddy, tailnet only)
After=network-online.target tailscaled.service
Wants=network-online.target

[Service]
Type=simple
ExecStart=%h/bin/caddy run --config %h/.config/caddy/Caddyfile --adapter caddyfile
Restart=on-failure
RestartSec=3s

[Install]
WantedBy=default.target

tailnet IP は Caddyfile 側で持つので unit はシンプル。

5. 起動して enable

systemctl --user daemon-reload
systemctl --user enable --now share-http.service
systemctl --user status share-http

systemctl --user を使うので、サーバマシンでは loginctl enable-linger $USER4 を一度叩いておくとログアウト後も生き続ける。

6. エージェントへの規約として書き起こす(ここが本題)

サーバが立っているだけでは何も解決しない。エージェントが 「成果物のパスを返す代わりに symlink を置いて URL を返す」 ようになって初めて、痛みが消える。

OpenClaw には memory/ という長期メモリ機構があって、エージェントは起動時にこれを参照する5。ここに「ファイルを渡すときのルール」を書いておけば、すべてのエージェントが同じ規約に従って成果物を出してくれるようになる。

私が memory/tailscale-static-hosting.md に書いているのはこれ。

---
name: tailscale-static-hosting
type: feedback
---

yousan にファイル/動画/レポート等をブラウザで渡すときは、毎回新ポートを立てずに統合 share 領域に置く。

**Why:** 過去にエージェントが各自でポート立てて入口が散乱した。
yousan が「どこ見ればいい?」を毎回確認しないと辿れなかった。

**How to apply:**

- ホスト: tachikoma, tailscale IP 100.100.26.90
- 配信プロセス: share-http.service (systemd user unit, Caddy)
- ルート: /home/yousan/.openclaw/workspace/share/
- 構造: share/YYYY-MM-DD/<topic-slug>/
- 実体: 元ファイルは触らず symlink を置く
- URL: http://tachikoma:8801/YYYY-MM-DD/<topic-slug>/
- 新規依頼が来たら: share/YYYY-MM-DD/<slug>/ を作る → symlink → URLを渡す。
  サービス常駐済みなので再起動不要
- 禁止: 別ポートで HTTP サーバを新規に立てる /
       Caddyfile の bind から tailnet IP を消す(全インターフェース公開になる)

ポイントは 「Why」と「How to apply」を両方書く こと。Why だけだと「で、どうすればいいんだ」になるし、How だけだと「これ何のためのルール?」になって、エージェントがエッジケースで判断を誤る。

これさえ書いておけば、「このファイルをホスティングしてほしい」と言うだけで、エージェントは自動的に symlink を置いて URL を返してくる。/home/yousan/.claude/usage-data/... というパスは二度と Discord に流れてこない。

エージェントが実際にやってること

参考までに、依頼を受けたエージェントが内部的に叩いてるコマンドはこんな感じ。

mkdir -p ~/.openclaw/workspace/share/2026-06-12/usage-report
ln -sf /home/yousan/.claude/usage-data/report-2026-06-12-111053.html \
       ~/.openclaw/workspace/share/2026-06-12/usage-report/index.html

index.html という名前にすると、ディレクトリ URL を叩いたときに Caddy の file_server がそれを返す6。autoindex のディレクトリリストが欲しいときは index.html を置かなければいい(browse を有効にしているのでリストが生成される)。

返ってくる URL はこう。

http://tachikoma:8801/2026-06-12/usage-report/

MagicDNS のおかげで tachikoma というショートホスト名で到達できる7。Mac でも iPhone でも、tailnet に入っていればそのままタップで開ける。

MagicDNS が無いノードからは FQDN (tachikoma.tail087bcf.ts.net) で行ける。

クロスデバイスで効くポイント

これを入れたあとで一番ありがたかったのは iPhone から開けるようになったこと だった。

デバイス 以前
同じホストの Linux xdg-open <path> で開く URL タップ
Mac (別ホスト) SCP / sshfs / SSH 経由でブラウザ URL タップ
iPhone 事実上見られない URL タップ

「事実上見られない」が「URL タップ」になるだけで、外出中にエージェントから来た成果物を確認できるようになった。これが副次的だけど一番大きい。

加えて、

  • 過去成果物が消えない: ポートが死なないので、3日前の URL を共有しても今でも開ける
  • 露出範囲がブレない: 全エージェントが tailnet IP bind を使う規約なので、0.0.0.0 事故が起きる隙間がない
  • 元ファイルが汚れない: symlink なので、エージェントが「ホスティング用にコピー作ります」をやらない
  • Grid 表示が画像レビューに強い: 生成画像をブラウザで一気にレビューできる
  • autoindex が地味に効く: 何があったか日付単位で振り返れる

まとめ — 「こう書いておけば楽」

ここまでの話を OpenClaw に与えるルールに圧縮するとこうなる。

  1. OpenClaw が動くホストに Caddytailnet IP に bind して systemd user unit として常駐させる
  2. クライアント (Mac, iPhone) を Tailscale で同じ tailnet に入れる
  3. memory/tailscale-static-hosting.md のような形で エージェントに「成果物は share/YYYY-MM-DD// に symlink、URL を返す」と書く
  4. 元ファイルは触らない、ポートは増やさない、tailnet IP bind は外さない、と明記する

これだけで「エージェントが返すホストパスをどう開くか問題」がほぼ消える。ファイル単位の SCP も SSH も Finder も要らなくなり、Mac でも iPhone でも URL タップだけで成果物にアクセスできるようになる。

参考リンク

  1. Caddy のリスナーはデフォルトでワイルドカード(全インターフェース)に bind する。サイトアドレスにホストや IP を書いてもそれは受け付ける Host ヘッダのマッチに使われるだけで、listen するインターフェースは変わらない。特定 IP のインターフェースだけで listen させるには bind ディレクティブ が必須。

  2. Caddy はサイトアドレスのホスト部分を Host マッチャとして扱う。100.100.26.90:8801 と書くと Host: 100.100.26.90:8801 のリクエストしか通らず、http://tachikoma:8801/ のようなホスト名アクセスは弾かれる。ホスト部分を省いて :8801 とすれば任意の Host にマッチするので、IP でもホスト名でも到達できる。

  3. systemd の linger を有効にすると、ユーザがログインしていなくても user manager が起動し続ける。常駐 user unit に必須の設定。

  4. OpenClaw のエージェントワークスペース(~/.openclaw/workspace/)には AGENTS.md MEMORY.md memory/ といったファイルがあり、セッション開始時に読み込まれる仕組みになっている。詳細は OpenClaw のドキュメント を参照。

  5. Caddy の file_server は要求パスがディレクトリの場合、既定で index.html / index.txt を順に探して見つかればそれを返す。なければ browse 設定が有効なときに限ってディレクトリリストを HTML で生成する。

  6. Tailscale MagicDNS は tailnet 内のホスト名を自動で名前解決する仕組み。各ノードの hostname で他ノードからアクセスできる。tailnet に参加していない端末からは名前も解決できないし TCP も通らない。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?