本文へスキップ
ColliePWA

17/Documentation

デプロイバリアント B〜E

デフォルト以外のfront door: Identity-Aware Proxy、Tailscaleなしのリバースプロキシ、ホスト外のingress、単一ホスト上の複数Collie(ユーザーごとに1つ、または単一ユーザー向けの複数インスタンス)、crewのstandby door

ブリッジは 127.0.0.1 にバインドします。デプロイ構成は、Ingress と ID 検証の方法によって異なります。バリアント A(プレーンな tailscale serve)は README に記載されています。docs/security.md のセキュリティ要件は、すべての構成に適用されます。

個々のデバイスは、プロキシを使用せず ペアリング 経由で認可することもできます。

---

バリアント B: Identity-Aware Proxy + デバイスごとの認可

CollieはCOLLIE_DEVICE_HEADERから不透明なデバイスIDを読み取り、COLLIE_DEVICE_ALLOWLISTをチェックします。許可リストにあるIDには書き込みアクセスが付与されます。IDが存在しないかリストにない場合は、読み取り専用アクセスになります(端末入力、アップロード、ペイン操作はブロックされます)。ペアリングは2つ目の関門であり、常に有効です。すべてのデバイスは、読み取りも含め、すべての/api呼び出しにペアリングトークンも必要です。

Collie の設定(.env):

COLLIE_HOST=127.0.0.1                       # keep loopback (default)
COLLIE_DEVICE_HEADER=X-Device-Id            # the header your proxy injects
# ids allowed to drive agents; others → read-only
COLLIE_DEVICE_ALLOWLIST=my-phone,my-laptop
# only if the proxy does NOT forward the public Host
# COLLIE_ALLOWED_ORIGINS=https://collie.example.com
# REQUIRED unless the proxy forwards a Host Collie already knows: loopback, a
# discovered Tailscale host, or an allowed origin's host
# COLLIE_PUBLIC_HOSTS=collie.example.com
# opt out of Host validation entirely (re-opens DNS rebinding)
# COLLIE_ALLOW_ANY_HOST=1
# COLLIE_TRUSTED_USER still composes on top if your ingress also injects
# Tailscale-User-Login
# accept a request carrying no Tailscale-User-Login at all
# COLLIE_TRUSTED_USER_OPTIONAL=1

プロキシの要件:

  1. デバイスを認証する(mTLS、SSO、forward-auth)。
  2. クライアントによる偽装を防ぐため、すべてのアップストリームリクエストで デバイスヘッダーを上書きする。
  3. ループバックにプロキシする(127.0.0.1:$COLLIE_PORT)。
  4. 公開 Host を変更せずに転送する を行うか、同一オリジンチェックを通過させるために COLLIE_ALLOWED_ORIGINS に公開オリジンを記載します。

Nginx の設定例:

location / {
    proxy_set_header X-Device-Id $device_id;
    proxy_set_header Host        $host;
    proxy_pass http://127.0.0.1:8787;
}

外部デバイスからのヘッダー挿入とオーバーライドを検証します。/api/healthと/api/pairを除くすべての/api呼び出しにはペアリングトークンが必要なため、まず端末をペアリングしてください。ホスト上でcollie pairを実行します。8文字のコードが表示されます。テストに使用するデバイスからそのコードを申請します。Originヘッダーは公開URLと一致している必要があります。応答にトークンが含まれます。トークンはシェル変数に保持し、ファイルには貼り付けないでください。

$ collie pair                                   # on the host; note the code it prints
$ TOKEN=$(curl -s -X POST https://collie.example.com/api/pair \
    -H 'Content-Type: application/json' -H 'Origin: https://collie.example.com' \
    -d '{"code":"ABCD1234","label":"header-check"}' | jq -r .token)

次にトークンを使ってヘッダーのチェックを実行します。

$ curl -s -H "Authorization: Bearer $TOKEN" https://collie.example.com/api/snapshot | jq -c .device
{"enforced":true,"device":"header-check","authorized":true}

$ curl -s -H "Authorization: Bearer $TOKEN" -H 'X-Device-Id: not-listed' https://collie.example.com/api/snapshot | jq -c .device
{"enforced":true,"device":"header-check","authorized":true}

トークンがある場合、deviceにはヘッダー値ではなくペアリングされたラベル(header-check)が設定されます。生ヘッダー値を返すフィールドはなくなったため、authorizedを確認してください。これは、ブリッジが確認したヘッダーがCOLLIE_DEVICE_ALLOWLISTに存在し、かつトークンが有効な場合にのみtrueになります。2回目のチェックで"authorized":falseが返された場合、プロキシはクライアントのヘッダーを上書きせずに追加しています。完了したら、collie devices revoke header-checkでテストデバイスを削除してください。

運用上の注意:

  • 直接のループバックアクセス(http://127.0.0.1:$COLLIE_PORT)はヘッダーを送信しないため読み取り専用であり、この場合もペアリングトークンが必要です。
  • curl経由でローカルでコマンドを実行するには、トークンとヘッダーをループバックに明示的に渡します: curl -H "Authorization: Bearer $TOKEN" -H 'X-Device-Id: my-laptop' http://127.0.0.1:$COLLIE_PORT/api/...
  • アクセスを取り消すには、COLLIE_DEVICE_ALLOWLIST から ID を削除して再起動します(Standalone: bin/collie restart、Herdr: herdr plugin action invoke restart --plugin herdr.collie)。
  • プレーンな tailscale serve で COLLIE_DEVICE_HEADER を有効にしないでください。ヘッダーは上書きされません。
  • プロキシを別のホストで実行する場合は、バリアント D を参照してください。

---

バリアント C: リバースプロキシのみをフロントドアとする構成(Tailscale なし)

Tailscale の外部で実行する場合や、専用の TLS/SSO プロキシの背後で実行する場合に使用します。COLLIE_SKIP_SERVE=1 を指定すると、Collie は tailscale serve を管理しなくなります。

バリアント B の 4 つのプロキシ要件が適用されます。

Caddy の設定例:

collie.example.com {
    reverse_proxy 127.0.0.1:8787 {
        header_up X-Device-Id {your_device_id}
        header_up Host {host}
    }
}

Collie の設定(.env):

# proxy is ingress; never run tailscale serve
COLLIE_SKIP_SERVE=1
# REQUIRED — Host validation fails closed, and `collie start` discovers no
# tailnet name here
COLLIE_PUBLIC_HOSTS=collie.example.com
# opt out of Host validation (re-opens DNS rebinding)
# COLLIE_ALLOW_ANY_HOST=1
# exact public origin for the same-origin gate
COLLIE_ALLOWED_ORIGINS=https://collie.example.com
COLLIE_DEVICE_HEADER=X-Device-Id                    # the header your proxy injects…
# …and the ids allowed to drive; others → read-only
COLLIE_DEVICE_ALLOWLIST=my-phone,my-laptop
# optional — status banner, `collie qr`, and the address a lead hands
# joining machines (crew)
# COLLIE_PUBLIC_URL=https://collie.example.com

COLLIE_TRUSTED_USER は Tailscale がないと機能しません。デバイスごとのヘッダー、またはプロキシ自体の認証をアクセスポータルとして使用します。

/auth/ のルーティングとキャッシュ

  • オリジン Cache-Control (no-cache) で /sw.js と index.html を渡します。未認証クライアントに対する静的アセットのブロックは避けてください。ブロックすると Service Worker が更新できなくなります。
  • 認証フローは /auth/* (または Cloudflare Access の場合は /cdn-cgi/access/) 配下にルーティングします。Service Worker は /auth/* のキャッシュをバイパスします。
  • API リクエスト時のフォワード認証リダイレクトは、ログイン UI を表示するために 401 として扱われます。Authentik を使用する場合は /auth/ を /outpost.goauthentik.io/start にルーティングしてください。
  • /auth/ にプロキシがない場合、Collie はそこで 404 を返します。未ペアリングのデバイスには、collie pair を実行して「設定」にコードを入力するようページ上に表示されます。パスはプロキシに属しているため、リダイレクトは行われません。
collie.example.com {
    handle /auth/* {
        reverse_proxy 127.0.0.1:9091
    }
    handle {
        forward_auth 127.0.0.1:9091 { ... }
        reverse_proxy 127.0.0.1:8787 { ... }
    }
}

エンドポイントとキャッシュ規則を検証します。$TOKENはバリアント Bと同様に申請したペアリングトークンです。ヘッダーが許可リストに登録されている場合、authorizedはtrueになります。

$ curl -s -H "Authorization: Bearer $TOKEN" https://collie.example.com/api/snapshot | jq -c .device
{"enforced":true,"device":"header-check","authorized":true}

$ curl -sI https://collie.example.com/sw.js | grep -i '^cache-control'
cache-control: no-cache

---

バリアント D: tailnet 経由のホスト外 ID プロキシ

トラフィックを集約された Tailscale イングレスノード経由でルーティングする場合に使用します。

Ingressノードがtailnetのエッジでスマートフォンを認証し、Collie自体はループバックでのみ応答します。phoneingress nodeagent hostCollie127.0.0.1:8787httpsTLS + forward-auth,sets the device headerhttp, tailnet only —WireGuard already encrypts ittailscale serve, tailnet-onlyloopback
Ingressノードがtailnetのエッジでスマートフォンを認証し、Collie自体はループバックでのみ応答します。

バリアント B の要件が適用されますが、プロキシの接続先はループバックではなくホストの Tailscale HTTP エンドポイントになります。

Tailscale ACL (必須)

tailscale serve はクライアントヘッダーを変更せずに転送するため、Tailscale ACL を使用してポートへの直接アクセスをイングレスノードのみに制限する必要があります。

Tailscale / Headscale ≥ 0.29:

"grants": [
  { "src": ["tag:ingress"], "dst": ["tag:agent-host"], "ip": ["tcp:8787"] },
]

Headscale ≤ 0.28:

acls:
  - action: accept
    src: ["ingress-node"]
    dst: ["agent-host:8787"]
  - action: accept
    src: ["my-phone", "my-laptop"]
    dst: ["agent-host:1-8786", "agent-host:8788-65535"]

Collie の設定(.env):

# proxy terminates TLS; this hop is tailnet-internal
COLLIE_SERVE_MODE=http
COLLIE_HOST=127.0.0.1                                 # keep loopback (default)
# header your forward-auth injects — REQUIRED here
COLLIE_DEVICE_HEADER=X-Tailnet-Device
# ids allowed to drive; others + header-less → read-only
COLLIE_DEVICE_ALLOWLIST=my-phone,my-laptop
# REQUIRED — the Host the proxy forwards. COLLIE_TAILSCALE_HOSTS carries the bare
# tailnet name `collie start` found; a rewritten Host is yours to list.
# COLLIE_ALLOW_ANY_HOST=1 opts out.
COLLIE_PUBLIC_HOSTS=host:8787,host.your-tailnet.ts.net:8787
# the public origin the browser actually uses
COLLIE_ALLOWED_ORIGINS=https://collie.example.com

COLLIE_TRUSTED_USER は背後のエンドユーザーではなく、イングレスノードの ID に一致します。

検証:

インプレスではないtailnetピアからです。$TOKENはバリアント Bと同様に申請したペアリングトークンです。

$ curl -s -H "Authorization: Bearer $TOKEN" https://collie.example.com/api/snapshot | jq -c .device
{"enforced":true,"device":"header-check","authorized":true}

$ curl -s --max-time 10 -H 'X-Tailnet-Device: my-phone' http://host.your-tailnet.ts.net:8787/api/snapshot
curl: (28) Connection timed out

エージェントホスト上では、リクエストがヘッダーを送信しないため、有効なトークンがあってもauthorizedはfalseになります。

$ curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8787/api/snapshot | jq -c .device
{"enforced":true,"device":"header-check","authorized":false}

---

バリアント E: その他のメッシュまたはトンネル(NetBird、ZeroTier、Cloudflare Tunnel)

トンネルまたはプロキシの接続先を 127.0.0.1:$COLLIE_PORT に設定します。

Collie の設定(.env):

COLLIE_SKIP_SERVE=1                                 # never run tailscale serve
# REQUIRED — exact public host; Host validation fails closed and finds no
# tailnet name here
COLLIE_PUBLIC_HOSTS=collie.example.com
# exact public origin for the same-origin gate
COLLIE_ALLOWED_ORIGINS=https://collie.example.com

ルール:

  1. バリアント B プロキシルールを適用します。
  2. COLLIE_TRUSTED_USER は Tailscale がないと機能しません。COLLIE_DEVICE_HEADER またはトンネルの認証を使用してください。
  3. PWA のキャッシュと COLLIE_PUBLIC_HOSTS が有効なまま維持されるよう、静的なホスト名を使用してください。

---

各製品のフロントドア設定

よく問い合わせのあるフロントドアの簡易セットアップです。いずれも最終構成は同じで、単一の固定ホスト名でのHTTPSを受け付け、このホスト上の 127.0.0.1:$COLLIE_PORT に転送します。

注。 Web Push、マイク、ホーム画面へのインストールには、スマートフォンが信頼する証明書によるHTTPSが必要です。平文のHTTPの場合でもブラウザのタブ内でCollieの読み書きは可能ですが、これら3つの機能は無効のままになります(音声入力とWeb Push)。

Tailscale以外のすべてのフロントドアでは、Collieの .env に同じ3行を追加して再起動します:

COLLIE_SKIP_SERVE=1                                 # you own the door, not Collie
COLLIE_PUBLIC_HOSTS=collie.example.com              # the hostname the phone opens
COLLIE_ALLOWED_ORIGINS=https://collie.example.com   # the same, as an origin

プロキシまたはトンネルはCollieと同じホストで実行してください。Collieはループバック以外からの接続を拒否するため、別マシン上のプロキシを使用する場合は代わりに バリアント D が必要です。

次に、スマートフォンを collie pair とペアリングします。これらのフロントドアはいずれも入力可能なデバイスを識別しないため、ペアリングで制御します(デバイスをペアリングする)。

フロントドアデフォルトでプライベートHTTPS証明書追加の手順
Tailscaleはい(tailnetのみ)Tailscaleが発行HTTPS証明書を一度有効化
Cloudflare Tunnelいいえ(パブリック)Cloudflareが発行手前にCloudflare Accessログインを配置
Nginx Proxy ManagerホストによるLet's Encryptコンテナにホストネットワークを使用
NetBirdはい(メッシュのみ)独自発行なしホスト上のプロキシ、DNS チャレンジ
CaddyホストによるLet's Encrypt(自動)プライベート名用の DNS プラグイン

Tailscale

デフォルトであり、Collie 自身が管理する唯一のエントリポイントです。collie start が MagicDNS 名で tailscale serve を実行します。

  1. Tailscale 管理コンソールで DNS を開き、MagicDNS を有効にしてから Enable HTTPS をオンにします。
  2. Collie を起動すると、tailnet 上に自身を公開します。
    collie start
  1. .env で COLLIE_TRUSTED_USER に tailnet のログイン名を設定し、再起動します。
    collie restart

残りの設定は バリアント A にあります。このエントリポイントでは COLLIE_SKIP_SERVE を設定しないでください。

絶対に には tailscale funnel を使用します。Funnel はポートをパブリックなインターネット上に公開します。

Cloudflare Tunnel

cloudflared は Cloudflare へアウトバウンド接続するため、ルーターのポートを開放しません。ホスト名は公開されるため、手前にログイン認証を配置する必要があります。

  1. ログインし、トンネルを作成して、ホスト名をそのトンネルに向けます。
    cloudflared tunnel login
    cloudflared tunnel create collie
    cloudflared tunnel route dns collie collie.example.com
  1. create に出力されたトンネル ID を使って ~/.cloudflared/config.yml を記述します。
    tunnel: <tunnel-id>
    credentials-file: /home/you/.cloudflared/<tunnel-id>.json
    ingress:
      - hostname: collie.example.com
        service: http://127.0.0.1:8787
      - service: http_status:404
  1. 再起動後も維持されるよう、その設定を使ってサービスとしてインストールします。
    sudo cloudflared --config ~/.cloudflared/config.yml service install
  1. Cloudflare Zero Trust で collie.example.com 用の セルフホストアプリケーション を追加し、自身のメールアドレスのみを許可するポリシーを設定します。

Zero Trust ダッシュボードで作成したトンネルも利用できます。パブリックホスト名のサービスを http://127.0.0.1:8787 に設定してください。cloudflared はパブリックホスト名を Host として転送するため、COLLIE_PUBLIC_HOSTS の想定どおりに動作します。

警告。 ステップ4を省略すると、ホスト名を知る誰もがマシン上のシェルを取得できてしまいます。Cloudflare Access はドアの鍵であり、その後のペアリングによって操作可能なデバイスが決定されます。
  1. Collieにロックを検証させます。AccessアプリケーションでApplication Audience (AUD) タグをコピーしてください。それとチーム名を.envに追加し、再起動します。
    COLLIE_ACCESS_TEAM=myteam          # from myteam.cloudflareaccess.com
    COLLIE_ACCESS_AUD=<the AUD tag>

    トンネル経由のすべてのリクエストには、Cloudflareがこのアプリケーション用に署名したトークンが必要になります。Accessアプリが削除された場合、バイパスルールが設定された場合、または設定がすべてのエッジに行き届いていない場合、Collieはペインを表示せずにリクエストを拒否します。AUDの検証は不可欠です。チーム内の全Accessアプリは同じ鍵で署名するため、別アプリ用のトークンも拒否されます。

    Collieは起動時と1時間ごとにチームの公開鍵を取得します。最初の取得が成功するまで、すべてのリモートリクエストは503を受け取ります。その後の取得に失敗した場合、Collieは保持している鍵を維持します。両方の行を設定するか、どちらも設定しないでください。片方のみを設定すると、すべてのリモートリクエストが拒否されます。いずれの場合もbridgeログに問題が1行で出力されるため、再起動後にログを確認してください。

    トークンが免除されるのは、このマシン上のプロセスのみです。Cf-*や転送ヘッダー(X-Forwarded-For、X-Forwarded-Host、X-Forwarded-Proto、Forwarded、X-Real-Ip)を含まない127.0.0.1またはlocalhostへのリクエストが該当します。入力には引き続きペアリングが必要です。/api/healthおよびcrewのリンクもトークンを必要としません。collie doctorもこのようなローカル呼び出し元であるため、doctorが正常を示していても、鍵が読み込まれたことやトークンが検証されることは確認できません。確認するにはスマートフォンからホスト名を開いてください。理由はADR 0081にあります。

    ゲートを有効にした状態では、次のルールを維持してください:

    - 入り口は1つに制限してください。 Accessが有効な場合、tailscale serve、Caddy、またはTraefik経由のリクエストにもトークンが必要です。これらはデフォルトで転送ヘッダーを追加するためです。トンネルとtailnet名(tailscale serve)の両方を提供するマシンでは、ブラウザからのtailnet経由のアクセスができなくなります。これらのリクエストにはAccessトークンが付与されず、401が返されます。- cloudflaredとCollieの間に、リクエストをローカルに見せかけるような2つ目のプロキシを配置しないでください。 Hostを127.0.0.1やlocalhostに書き換え、Cf-*および転送ヘッダーを削除する設定は、意図的な構成です。デフォルトのプレーンなnginx proxy_pass http://127.0.0.1:8787はこの動作になり、Host: 127.0.0.1:8787を送信し、設定しない限り転送ヘッダーを追加しません。これらのリクエストはトークン検証をスキップします。cloudflaredはCollieに直接向けてください。- Collieのパスに対してAccessのバイパスルールを追加しないでください。マニフェスト、サービスワーカー、アイコンに対しても追加しないでください。Accessは通過させるすべてのリクエストにトークンを付与します。バイパスされたパスにはトークンが付与されないため、Collieは401を返します。

Access はログインのために /cdn-cgi/access/ へリダイレクトします。/auth/ のルーティングとキャッシュ の記載どおり、sw.js と index.html はキャッシュしないようにしてください。

Nginx Proxy Manager

Web UI と Let's Encrypt を内蔵したリバースプロキシです。Docker 内で動作し、コンテナ内での 127.0.0.1 はホストではなくコンテナ自身を指します。

  1. ループバック経由で Collie にアクセスできるよう、コンテナにホストのネットワークを割り当てます。
    services:
      npm:
        image: jc21/nginx-proxy-manager:latest
        network_mode: host
        volumes:
          - ./data:/data
          - ./letsencrypt:/etc/letsencrypt
  1. ポート 81 で UI を開き、Proxy Host を追加します: ドメイン collie.example.com、スキーム http、転送先 127.0.0.1、ポート 8787。
  2. SSL タブで Let's Encrypt 証明書をリクエストし、Force SSL を有効にします。

Service Worker が更新できるよう、アセットのキャッシュ はオフのままにしてください。Collie はプレーンな HTTP 経由でポーリングするため、WebSocketのサポート スイッチの設定は影響しません。

内部ネットワークでのみ解決される名前の場合は、SSLタブでDNSチャレンジを使用するを選択してください。Let's EncryptはHTTP経由でプライベートホストに到達できません。

host-gatewayを使用した警告。 host.docker.internalはここでは機能しません。Dockerブリッジのアドレスに到達しますが、Collieは127.0.0.1でのみリッスンします。

プロキシをインターネットに公開する場合は、プロキシホストにアクセスリストを追加するか、ルーターのポート80と443を閉じたままにしてVPN経由でホストにアクセスしてください。

NetBird

NetBirdはtailnetのようなWireGuardメッシュです。トラフィックを伝送し、ホスト上のリバースプロキシがHTTPSを追加します。

  1. ホストとスマートフォンを同じNetBirdネットワークに参加させます。
    netbird up
  1. collie.example.comのDNSレコードをホストのNetBirdアドレス(100.x.y.z)に向けます。
  2. ホスト上でCaddyを実行し、そのアドレスにバインドして、DNSチャレンジ証明書を使用します。
    collie.example.com {
        bind 100.x.y.z
        tls {
            dns cloudflare {env.CF_API_TOKEN}
        }
        reverse_proxy 127.0.0.1:8787
    }

DNSチャレンジを使用すると、Let's Encryptがホストに直接アクセスすることなくドメインの所有権を証明できるため、名前をプライベートアドレスに向けることができます。Caddyにはxcaddy build --with github.com/caddy-dns/cloudflareなど、使用しているプロバイダー用のDNSプラグインが必要です。

host.netbird.cloudのようなNetBirdのピア名は公的に信頼された証明書を持たないため、自身が所有するドメインを使用してください。同じ手順はZeroTierやその他のメッシュネットワークにも適用できます。

Caddy

Caddyは自身の証明書を自動で取得および更新します。ポート80と443で到達可能なホストでは、設定はこれだけで完結します:

collie.example.com {
    reverse_proxy 127.0.0.1:8787
}

プライベート名の場合は、上記のNetBirdからtls { dns … }ブロックを追加します。公開ホストの場合は、バリアント Cのように手前にログインも必要です。

---

パス配下でのCollieの配信

1つのリリースビルドで任意のパスへのマウントに対応します。インスタンスの.env(またはconfig.toml内の[serve] base_path)にパスを設定して再起動すると、ブリッジはそのパスの配下でアプリ、アセット、/api/*を配信します:

# https://<your-node>/collie/ — leading and trailing slash both optional; the bridge adds them
COLLIE_BASE_PATH=/collie

使用可能: /collie、collie/、/apps/collie/。拒否され、ログに1行出力されて代わりにルートが使用されるもの: .または..セグメント、空白文字、?、#。collie doctorはfront-door行にマウントパスを出力します。

デフォルトのエントランスでは、collie serve(および各collie start)はルートの代わりにtailscale serve --set-path=/collie/を公開するため、同じノード上の/を別のアプリが維持できます。Tailscaleはプロキシ前にマウントパスを除去しますが、ブリッジはパスが付いたままのリクエストも受け付けるため、リバースプロキシはどちらの構成でも機能します。現在の設定を確認するには:

curl -s https://<your-node>/collie/api/health   # JSON, not HTML, means the mount is right

独自のプロキシ(バリアント C)の背後では、同じパスにブリッジをマウントしてください。Caddy: handle_path /collie/* { reverse_proxy 127.0.0.1:8787 }。Nginx: location /collie/ { proxy_pass http://127.0.0.1:8787/; }。COLLIE_PUBLIC_URLを設定する場合は、パスを含めてください: https://collie.example.com/collie/。

マウントの変更はスマートフォン上では新しいアプリ扱いになります。 インストールされたPWAのスコープと識別情報は、インストール元のパスに基づきます。COLLIE_BASE_PATHを変更した後、またはルートに戻した後は、ホーム画面のアイコンを削除し、新しいアドレスから再度追加してください。古いアイコンはブリッジが配信しなくなったパスを参照し続けます。ブリッジ自体は再起動するだけでよく、collie serveは独自にエントランスを移行します。新しいマッピングを公開する前に、レコード名が指定するマッピングを破棄します。

---

1 台のホストで複数の Collie を実行する

共有システム (ADR 0001) 上でユーザーごとに独立したインスタンスをホストする場合:

# ~/.config/herdr/plugins/config/herdr.collie/.env — one per Unix user
# this user's loopback bridge port — unique per user
COLLIE_PORT=8801
# this user's tailnet https listener — unique per user
COLLIE_SERVE_PORT=8443
COLLIE_TRUSTED_USER=dev-a@example.com  # only this tailnet login may drive these agents

スタンドアロンバイナリ URL チェック: bin/collie url。Herdr プラグイン URL チェック: herdr plugin action invoke url --plugin herdr.collie。

要件:

  • 異なる Unix ユーザーと個別の herdr/Collie インスタンスを実行します。
  • ポートごとにアクセスを制限するため、常に COLLIE_TRUSTED_USER を設定してください。
  • 初期 Tailscale バインドにはオペレーター権限での設定が必要です。オペレーター権限で bin/collie serve (または同等の Herdr コマンド) を実行してください。

COLLIE_SERVE_PORT はインスタンスのエントリポートを設定します。COLLIE_INSTANCE は同じホスト上に専用のサービスユニットと設定を持つ分離されたインスタンスを作成します (単一ホスト上での複数 Collie インスタンスの実行)。

---

単一ホスト上での複数 Collie インスタンスの実行

同じマシン上で2つ目の独立した Collie を実行する場合はこの設定を使用します。例えば、安定版と作業用コピーを並行して実行する場合や、Herdr が管理するデフォルトの構成の横で2つ目のマルチプレクサー (tmux/zellij) を実行する場合などです。各インスタンスには専用のポート、設定、状態ディレクトリ、サービスユニットが割り当てられます。これは Unix ユーザーごとに1つのインスタンスを割り当てる 1 台のホストで複数の Collie を実行する とは異なり、1人のユーザーが複数のインスタンスを実行します。

2つ目のインスタンスを作成する

COLLIE_INSTANCE=<name> を設定してインスタンス名を指定します。名前は [a-z0-9-]{1,16} に一致する必要があります。名前付きインスタンスには明示的な COLLIE_PORT の指定も必要です。このポートが指定されていない場合、CLI はエラーを出して終了します。デフォルト値は推論されません。

~/.config/herdr/plugins/config/herdr.collie-<name>/.env を作成:

COLLIE_INSTANCE=<name>        # required — [a-z0-9-], max 16 chars
COLLIE_PORT=8788              # required for a named instance — no default is inferred
# Unset, this defaults to ~/.local/state/collie, with no instance suffix
COLLIE_STATE_DIR=/home/you/.local/state/collie-<name>

COLLIE_INSTANCE、COLLIE_PORT、COLLIE_STATE_DIR はすべてその .env ファイルに記述できます。マージされた環境変数からインスタンスが解決されます。HERDR_PLUGIN_CONFIG_DIR は CLI が設定を検索するディレクトリを上書きします。

インスタンスごとにサービスユニットを実行する

環境変数でインスタンスが設定されている場合、collie start がユニットを書き込みます。オペレーターが手動で作成する必要はありません。macOS では代わりに ~/Library/LaunchAgents/ の下に launchd plist を書き込みます。ユニットは COLLIE_PORT、COLLIE_INSTANCE、COLLIE_PLUGIN_ROOT、HERDR_PLUGIN_CONFIG_DIR、EnvironmentFile=-<config dir>/.env を設定します。ExecStart の --instance <name> は、単一のバイナリを共有する2つのインスタンスがそれぞれのブリッジを識別できるようにするためだけに存在します (cli/unit.ts systemdUnit()):

ExecStart=<root>/bin/collie _exec-bridge --instance <name>
Environment=COLLIE_PORT=8788
Environment=COLLIE_INSTANCE=<name>
Environment=COLLIE_PLUGIN_ROOT=<root>
Environment=HERDR_PLUGIN_CONFIG_DIR=<config dir>
EnvironmentFile=-<config dir>/.env

CLI はインスタンスごとに tailscale serve ハンドラーファイルを保持するため、1つのインスタンスで unserve を実行しても別のインスタンスのマッピングが削除されることはありません。

CLI から名前付きインスタンスを対象にする

すべての CLI サブコマンド (pair、devices、url、qr、crew …、logs、push-test、…) は、プロセス環境変数から対象のインスタンスを解決します。サブコマンドを実行する前に COLLIE_INSTANCE を設定してください:

COLLIE_INSTANCE=next bin/collie pair
COLLIE_INSTANCE=next bin/collie devices list

COLLIE_INSTANCE を指定しない場合、CLI は Herdr に問い合わせます。Herdr はサフィックスのないプラグインのみを追跡するため、コマンドは最初のインスタンスに対して実行されます。誤ったインスタンスを変更しないよう、1台のホストで複数のインスタンスを実行する場合は常に明示的に COLLIE_INSTANCE を設定してください。

拒否ルール

COLLIE_INSTANCE が設定されているにもかかわらず herdr.collie-<name>/.env がない場合、CLI はエラーを出して終了します。他のインスタンスの設定にフォールバックすることはありません。

ペアリングはインスタンス単位

collie pair は、そのインスタンスの状態ディレクトリにペアリングコードを書き込みます。各インスタンスは独自の状態ディレクトリを持ち、COLLIE_PUBLIC_URL や専用ポートを介して独自の入口を持つため、出力されるQRコードにはそのインスタンス固有のURLがエンコードされます。スマートフォンでそのインスタンスのURLを開き、「設定」→「システム」→「ペアリング済みデバイス」でコードを入力してください。あるインスタンス用に発行されたコードを使って、他のインスタンスにデバイスをペアリングすることはできません(デバイスをペアリングする)。

---

standby door: crewのフェイルオーバー経路

crew デプロイ用。lead が到達不能になった場合に引き継ぐ事前承認済みの deputy を設定します (ADR 0027、ADR 0028、CREW_PROTOCOL.md §18)。

Deputy と lead の設定:

キーデフォルト説明
COLLIE_STANDBY_PORT(未設定)standbyリスナー用のポート。未設定の場合はstandby doorが無効化される。leadとdeputyで一致させる必要がある。
COLLIE_STANDBY_HOST127.0.0.1バインドアドレス(ローカルプロキシの場合は127.0.0.1、リモートの場合はオーバーレイIP)。
COLLIE_STANDBY_ARM_MSmax(30000, 2.5 × COLLIE_POLL_IDLE_MS)arm状態に移行するまでに必要なleadの無応答時間。

leadとdeputyの両方で、COLLIE_STANDBY_PORTに同一の未使用ポートを設定する。

前提条件: 1つのホスト名と2つのバックエンド

PWA登録とデバイス資格情報を共有するには、leadとdeputyを同じオリジンから配信する必要があります。統一ingressのないスタンドアロンcrewはbin/collie promote経由で復旧します(またはHerdr: herdr plugin action invoke promote --plugin herdr.collie; CREW_PROTOCOL §14.4)。

Traefikの設定例:

http:
  routers:
    collie:
      rule: "Host(`collie.example.com`)"
      service: collie-crew
      tls: {}

  services:
    collie-crew:
      failover:
        service: collie-lead
        fallback: collie-deputy

    collie-lead:
      loadBalancer:
        servers:
          - url: "http://lead.internal:8787"
        healthCheck:
          path: /standby/health
          interval: 5s
          timeout: 2s

    collie-deputy:
      loadBalancer:
        servers:
          - url: "http://deputy.internal:8788"   # COLLIE_STANDBY_PORT
        healthCheck:
          path: /standby/health
          interval: 5s
          timeout: 2s

ヘルスチェックの応答: leadは200を返す(deposed時は200以外)。deputyはarm状態になるまで503を返し、その後200を返す。

システムが正常なうちに一度だけ設定を行う

leadでのスタンドアロン設定:

bin/collie pair
bin/collie crew deputy nas
bin/collie crew status

leadでのHerdr設定:

herdr plugin action invoke pair --plugin herdr.collie
herdr plugin action invoke crew --plugin herdr.collie deputy nas
herdr plugin action invoke crew --plugin herdr.collie status

deputyでCOLLIE_STANDBY_PORT=8788を設定して再起動する。deputy warrantを読み込むため、すべてのピアを再起動すること。https://collie.example.com/standbyで確認する。

⚠️ deputyはスーパーバイザーで管理する必要がある

テイクオーバー中、deputyは状態を書き出し、プロセスの再起動(bridge/index.ts)をトリガーするためにステータスコード75(EX_TEMPFAIL)で終了する。

非ゼロの終了コードでスーパーバイザーが再起動するように設定する(systemdの場合: Restart=alwaysまたはRestart=on-failure)。

障害発生時 — ランブック

  1. https://collie.example.comを開く。
  2. leadが異常な場合、プロキシは/standbyにルーティングする。
  3. deputyのstandby状態を確認する。
  4. テイクオーバーを選択する。
  5. deputyはピアの合意を検証し、warrantを適用して75で終了する。
  6. スーパーバイザーがプロセスを再起動し、PWAが新しいleadとしてリロードされる。

復旧後:

  • 以前のleadは再接続時に自身をdeposeする(CREW_PROTOCOL.md §8.4)。
  • deposedノードのピアアドレスを更新する: スタンドアロンはbin/collie crew set-address <member> <host:port>、Herdrはherdr plugin action invoke crew --plugin herdr.collie set-address <member> <host:port>。
  • 新しいdeputyを割り当てる: スタンドアロンはbin/collie crew deputy <member>、Herdrはherdr plugin action invoke crew --plugin herdr.collie deputy <member>。
  • すべてのメンバーが再接続するまでcrew rotateを実行しないこと。

GitHub でこのページを編集

The rest of the documentation