1
2

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 で解決

1
Last updated at Posted at 2026-06-16

痛み: 「/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、ダークモード)

当日のディレクトリに潜ると、その日に生成された成果物がトピック単位で並びます。Caddy の file_server browse には Grid 表示と画像サムネが付いてくるので、生成画像を渡すときはこちらのほうが圧倒的に見やすいです。実際の成果物(HTML レポートなど)も、ディレクトリ URL を開けばそのままブラウザに表示されます。

なぜ 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 でまぶしくない
  • 検索・ソート・パンくず が全部入り。ロード時に 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 に流れてきません。

エージェントがホストパスではなく tailnet URL(http://tachikoma:8801/...)を返してくる。これがゴールです

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

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

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 から開けるようになったこと でした。

iPhone から Caddy の一覧をそのまま開いたところ。外出先でも URL タップで成果物に届きます(フッタの "Served with Caddy" が目印)

デバイス 以前
同じホストの 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 も通りません。

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?