herdr + Tailscaleでスマホから開発環境を操作する手順

自宅のMacで動いているClaude CodeやCodexに、外出先のiPhoneから指示を出す環境を作りました。
月額はゼロで、スマホには専用アプリを入れません。ブラウザでURLを開くだけです。

なぜherdrを選んだかは前の記事に書いたので、ここでは手順を記載します。
macOSで2回つまずいたので、その原因と直し方も載せています。

構成

iPhone (Safari / ホーム画面に追加)
   ↓ Tailscale(WireGuard・自分のネットワーク内だけ)
Mac :17930 (HTTPS)
   ↓ tailscale serve
herdr-web :7930(127.0.0.1限定)
   ↓ socket API
herdrサーバー(常駐・ログイン時に自動起動)
   ↓
Claude Code / Codex / 他20種

herdrがターミナルを所有し、herdr-webがそれをWeb UIとして出し、
Tailscaleがスマホとの通信をつなぐ。役割はこの3つに分かれています。

環境

  • macOS 26.6.2 / Apple M4
  • Node v22.17.0(herdr-webの要件は18以上)
  • herdr 0.9.1(herdr-webの要件は0.7以上)

1. herdrを入れる

brew install herdr
brew services start herdr

brew servicesで起動しておくと、ログインするたびに自動で復帰します
herdrを選んだ理由の1つが常駐性なので、ここは入れておきます。

次にエージェントとの連携を入れます。

herdr integration install claude
herdr integration install codex

入るのは~/.claude/hooks/herdr-agent-state.shのようなシェルスクリプトのフックです。
エージェントの状態(working / blocked / idle)をherdrのソケットに通知するだけで、
プロンプトにトークンを足すものではありません。
スクリプトの中身を見るとHERDR_ENV=1でなければすぐexitするので、
herdrの外でclaudeを起動したときは何もしません。

サーバーが動いているか確認します。

herdr status
server:
  status: running
  socket: /Users/xxx/.config/herdr/herdr.sock

2. herdr-webを入れる

スマホから見るためのWeb UIです。herdrのプラグインとして入ります。

herdr plugin install eyalev/herdr-web --yes

依存はexpresswsの2つだけ、ライセンスはMITです。
ソースを見ると、待ち受けるアドレスはこうなっています。

const BIND = process.env.HERDR_WEB_BIND || '127.0.0.1';

localhost固定です。自分でHERDR_WEB_BIND=0.0.0.0を設定しない限り外には出ません。

起動しようとしたら、2回失敗しました。

つまずき1:setsidがない

scripts/plugin-start.sh: line 22: setsid: command not found

setsidはLinuxのutil-linuxに入っているコマンドで、macOSには存在しません。
起動スクリプトがサーバーをデタッチするのに使っていました。

macOSではnohupdisownで同じことができるので、分岐を足します。
対象は~/.config/herdr/plugins/github/eyalev.herdr-web-*/scripts/plugin-start.shです。

# macOS has no setsid; fall back to nohup + disown.
if command -v setsid >/dev/null 2>&1; then
  setsid nohup node "$ROOT/server.js" >> "$STATE_DIR/server.log" 2>&1 < /dev/null &
else
  nohup node "$ROOT/server.js" >> "$STATE_DIR/server.log" 2>&1 < /dev/null &
  disown "$!" 2>/dev/null || true
fi
echo $! > "$STATE_DIR/server.pid"

つまずき2:nodeが見つからない

直して再実行すると、別のエラーになりました。

nohup: node: No such file or directory

ターミナルではnode -vが通ります。

原因は1つ目の手順にあります。brew services start herdrでサーバーを常駐させたので、
herdrはlaunchd経由で起動しています。launchdは最小限のPATHしか持たないため、
nvm管理下のnodeを見つけられません。

同じスクリプトのPORT=の行の直前に、nodeを探す処理を足します。

# launchd gives the herdr server a minimal PATH; make sure node is findable.
PATH="/usr/local/bin:/opt/homebrew/bin:$PATH"
if ! command -v node >/dev/null 2>&1; then
  for d in "$HOME"/.nvm/versions/node/*/bin; do
    [ -x "$d/node" ] && PATH="$d:$PATH" && break
  done
fi
export PATH

これで起動しました。

{"level":"info","event":"listening","port":7930,"bind":"127.0.0.1"}

この2つのパッチはherdr plugin installでプラグインを更新すると上書きされます。
元のファイルを.origで残しておくと、次に当て直すときに比較できます。

3. Tailscaleを入れる

スマホとMacをつなぐ部分です。

brew install --cask tailscale-app

Macのパスワードを聞かれます。システム機能拡張がブロックされたと出たら、
システム設定のプライバシーとセキュリティで許可します。

起動してサインインします。ここで引っかかりやすい設定が2つあります。

セットアップ中に出る「Start at login」は、初期状態がOFFです。
OFFのままだとMacを再起動したときにTailscaleが立ち上がらず、
外出先でスマホを開くまで気づけません。
herdr側はログイン時に自動起動する設定にしたので、ここを揃えないと片方だけ復帰します。

もう1つ、スマホにも同じアカウントでサインインします。
別のアカウントにすると別のネットワークになり、Macが見えません。

HTTPSを有効にする

管理画面 https://login.tailscale.com/admin/dns を開いて、2つ有効にします。

  • MagicDNS
  • HTTPS Certificates

HTTPS CertificatesはDNSページの一番下にあります。Add nameserverボタンのさらに下です。

有効化すると、マシン名とテイルネットのDNS名が証明書の公開台帳(証明書トランスペアレンシーログ)に
載ります。マシン名に個人が特定できる情報を入れている場合は、先に変えておきます。

有効にしないままtailscale serveを打つと、コマンドが返ってこなくなります。
原因を確かめるには証明書を直接取りに行くと分かりやすいです。

tailscale cert <マシン名>.tailXXXXXX.ts.net
500 Internal Server Error: your Tailscale account does not support getting TLS certs

このメッセージが出たら、HTTPSがまだ有効になっていません。

なお、このコマンドはカレントディレクトリに証明書と秘密鍵を書き出します。
Gitリポジトリの中で実行すると鍵がリポジトリに残るので、確認後に消しておきます。
tailscale serveは自分で証明書を管理するので、手動で作ったものは要りません。

スマホ側

App StoreからTailscaleを入れて、Macと同じアカウントでサインインし、Connectを押します。
iOSからVPN構成の追加を求められるので許可します。

一般的なVPNサービスと違い、通信がどこかのサーバーを経由するわけではありません。
iOSの仕組み上VPN構成として登録が必要なだけで、実際にはMacと直接つながります。

2台が乗ったか、Macから確認できます。

tailscale status
100.x.x.x  macbook-xxx  xxx@  macOS  -
100.x.x.x  iphonexxx    xxx@  iOS    -

4. 公開する

tailscale serve --bg --https=17930 http://127.0.0.1:7930
Available within your tailnet:
https://<マシン名>.tailXXXXXX.ts.net:17930/
|-- proxy http://127.0.0.1:7930

Available within your tailnet と出ていれば、自分のデバイスからしか見えません。

herdr-webには認証がない

herdr-webには認証がありません。READMEにもこう書かれています。

全ペインのターミナルを無制限に操作できる。認証なし。localhost専用の設計

安全性はTailscaleのネットワーク層に依存しています。ルールは1つです。

  • tailscale serve … 自分のネットワーク内のデバイスにしか見えない ← これだけを使う
  • tailscale funnelインターネット全体に公開される ← 使わない

確認はこれでできます。

tailscale funnel status

(tailnet only) 以外が出たら、すぐtailscale serve --https=17930 offで止めます。

設定そのものを見たい場合はこちらです。AllowFunnelが存在しなければ大丈夫です。

tailscale serve status --json

5. セッションを作る

ここまでで接続はできましたが、herdrにセッションがないとスマホ側は空です。

ターミナルで、作業したいディレクトリに移動して起動します。

cd ~/Documents/myproject
herdr

herdrの画面が立ち上がります。その中でclaudecodexを起動すると、
herdrが自動で検出してタブに状態が出ます。

操作キー
右に分割Ctrl+BV
下に分割Ctrl+B-
新しいタブCtrl+BC
抜けるCtrl+BQ

マウスでも操作できます。ペインをクリックしてフォーカス、境界をドラッグでリサイズ、
右クリックでメニュー。tmuxのようにキーを暗記しなくても使えます。

Ctrl+BQ で抜けても、プロセスは動き続けます。
ターミナルのウィンドウを閉じても同じです。戻るときはまたherdrと打つだけ。

Claude CodeとCodexを並べた状態で、herdrが認識しているものを見てみます。

herdr pane list
agent="claude"  status=idle  ✳ Claude Code
agent="codex"   status=idle

2つが別々のエージェントとして並びました。これがスマホからも見えます。

6. スマホで開く

Safariでhttps://<マシン名>.tailXXXXXX.ts.net:17930を開き、
共有ボタンからホーム画面に追加します。

PWAとして登録されるので、アプリのように起動でき、
エージェントが入力待ち(blocked)になったときにシステム通知が届きます。

herdr-webの画面には、タブ・ターミナル表示のほかに、
Esc Tab Ctrl ↑↓←→ Enter といったソフトキーと、
Type to the agent... の入力欄があります。
過去の会話に戻りたいときは、ペインでclaude --resumeを打てば一覧が出るので、
矢印キーで選べます。

つまずきの一覧

同じことをやる人向けにまとめておきます。

症状原因
プラグインが起動しないmacOSにsetsidがない
同上(2回目)launchdの最小PATHでnodeが見つからない
tailscale serveが返ってこないHTTPS Certificatesが未有効
HTTPSの設定場所が分からないDNSページの最下部にある
証明書がリポジトリに残ったtailscale certはカレントディレクトリに書く
Codexが起動時に警告を出す・完了音が2回鳴る後述

最後の1つは、しばらく使ってから気づきました。

herdr integration install codex~/.codex/hooks.json~/.codex/config.toml
両方に書き込みます。config.toml側に自分で書いたフックが既にあると、
二重読み込みになってCodexが起動時に警告を出します。

loading hooks from both ~/.codex/hooks.json and ~/.codex/config.toml;
prefer a single representation for this layer

私の場合は両方にStopフックがあったので、完了音が2回鳴っていました。

herdrが管理するのはhooks.json側なので、config.tomlから[[hooks.*]]を消して
hooks.jsonに寄せます。逆向きに寄せると、herdr integration installを実行するたびに再発します。

コメント

タイトルとURLをコピーしました