跳转至正文
ColliePWA

17/Documentation

部署变体 B–E

默认配置之外的入口:身份感知代理、无 Tailscale 的反向代理、主机外 ingress、单台主机上的多个 Collie(每用户一个,或单用户多个实例),以及 crew 的备用入口

Bridge 绑定到 127.0.0.1。不同部署方式的区别在于入口接入和身份验证。方案 A(基础 tailscale serve)请见 README。docs/security.md 中的安全要求适用于所有部署形式。

单台设备也可以不通过代理直接经由 配对 完成授权。

---

方案 B:身份感知代理 + 单设备授权

Collie 从 COLLIE_DEVICE_HEADER 读取不透明设备 ID 并检查 COLLIE_DEVICE_ALLOWLIST。白名单内的 ID 获得写入权限;缺失或未列出的 ID 获得只读权限(阻止终端输入、上传和面板操作)。配对是第二道关卡且始终开启:每台设备在进行每次 /api 调用时也都需要 pairing token,包含读取操作。

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. 代理到 loopback(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 调用都需要 pairing token,因此请先配对终端。在宿主机上运行 collie pair。它会输出一个 8 位代码。从用于测试的设备申领该代码。Origin 标头必须与公共 URL 匹配。响应中包含该 token。将其保存在 shell 变量中,切勿粘贴到文件中:

$ 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)

然后使用该 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}

有了 token 后,device 会指定配对的标签(header-check),而不是标头值。不再有字段返回原始标头值,因此请读取 authorized:仅当网桥看到的标头位于 COLLIE_DEVICE_ALLOWLIST 中且 token 有效时,它才为 true。如果第二次检查返回 "authorized":false,说明代理是在追加客户端标头而不是覆盖它。完成后,使用 collie devices revoke header-check 移除测试设备。

运维说明:

  • 直接本地回环访问(http://127.0.0.1:$COLLIE_PORT)不发送标头且为只读,并且它仍然需要 pairing token。
  • 要通过 curl 在本地执行命令,请显式将 token 和标头传递给本地回环: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 中的四项代理要求同样适用。

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

没有 Tailscale 时 COLLIE_TRUSTED_USER 不生效。按设备请求头或代理自身的认证将作为访问入口控制。

路由 /auth/ 和缓存

  • 透传 /sw.js 和 index.html 并携带源 Cache-Control(no-cache)。不要对未认证客户端拦截静态资源,否则 service worker 无法更新。
  • 将认证流程路由到 /auth/* 下(Cloudflare Access 则路由到 /cdn-cgi/access/)。Service worker 会对 /auth/* 绕过缓存。
  • API 请求上的 Forward-auth 重定向会被视为 401,以显示登录 UI。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 是 pairing 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 接入的主机外身份代理

通过集中式 Tailscale 入口节点路由流量时使用此配置。

入口节点在 tailnet 边缘对手机进行身份验证,而 Collie 本身仅在回环地址(loopback)上响应。phoneingress nodeagent hostCollie127.0.0.1:8787httpsTLS + forward-auth,sets the device headerhttp, tailnet only —WireGuard already encrypts ittailscale serve, tailnet-onlyloopback
入口节点在 tailnet 边缘对手机进行身份验证,而 Collie 本身仅在回环地址(loopback)上响应。

变体 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 匹配的是入口节点的身份,而不是其背后的最终用户。

验证:

来自非入口 tailnet 对等节点。$TOKEN 是 pairing 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

在 agent 宿主机上,请求不发送标头,因此即使 token 有效,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. 没有 Tailscale 时 COLLIE_TRUSTED_USER 处于停用状态。请使用 COLLIE_DEVICE_HEADER 或隧道自身的认证。
  3. 使用静态主机名,以保证 PWA 缓存和 COLLIE_PUBLIC_HOSTS 保持有效。

---

各种入口方案分别配置说明

常见入口方案的简要配置。每种方案最终都指向相同的结果:在单个固定主机名上启用 HTTPS,并转发至本机的 127.0.0.1:$COLLIE_PORT。

注意。 Web Push、麦克风和主屏幕安装需要手机信任的 HTTPS 证书。如果走纯 HTTP,Collie 仍可在浏览器标签页中读取和回复,但上述三项功能会保持关闭(语音输入与 Web Push)。

除 Tailscale 外,每个入口都在 Collie 的 .env 中使用相同的这三行配置,然后重启:

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容器使用 host 网络
NetBird是,仅限 mesh本身无证书主机上的代理,DNS 质询
Caddy取决于主机Let's Encrypt,自动用于私有域名的 DNS 插件

Tailscale

默认选项,也是 Collie 自行管理的唯一入口:collie start 在你的 MagicDNS 域名上运行 tailscale serve。

  1. 在 Tailscale 管理控制台中,打开 DNS,启用 MagicDNS,然后启用 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 步,任何找到该主机名的人都能在你的机器上获取 shell。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 会拒绝请求而不会显示窗格。Audience 验证至关重要:团队中的所有 Access 应用都使用相同的密钥签名,因此其他应用的令牌也会被拒绝。

    Collie 在启动时以及之后每小时获取一次团队的公钥。在首次获取成功前,所有远程请求都会返回 503。如果后续获取失败,Collie 会保留现有密钥。这两行配置必须同时设置或同时留空:仅设置一行会拒绝所有远程请求。无论哪种情况,bridge 日志都会单行指出问题,因此重启后请检查日志。

    只有本机上的进程才能跳过令牌验证:发往 127.0.0.1 或 localhost 且不携带 Cf-* 和转发标头(X-Forwarded-For、X-Forwarded-Host、X-Forwarded-Proto、Forwarded、X-Real-Ip)的请求。此类请求仍需配对才能输入内容。/api/health 和 crew 链接同样不需要令牌。collie doctor 就属于这类本地调用方,因此 doctor 显示绿色并不代表密钥已加载或令牌验证成功。请从手机打开该主机名进行验证。具体原因见 ADR 0081。

    开启门禁时保留这些规则:

    - 一次只使用一个入口。 启用 Access 时,经由 tailscale serve、Caddy 或 Traefik 发出的请求也需要令牌,因为它们默认会添加转发请求头。同时提供隧道和 tailnet 名称(tailscale serve)的机器对浏览器而言会失去 tailnet 入口:这些请求不携带 Access 令牌,会收到 401。- cloudflared 与 Collie 之间不得有使请求看起来像本地请求的二级代理。 若有代理将 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 绕过规则,不要对清单、Service Worker 或图标绕过。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 进行轮询,因此 Websockets 支持 开关并不重要。

如果域名仅在你的网络内部解析,请在 SSL 标签页中选择 使用 DNS Challenge。Let's Encrypt 无法通过 HTTP 访问私网主机。

此处无法配合 host-gateway 使用 警告。 host.docker.internal。它访问的是 Docker 网桥地址,而 Collie 仅监听 127.0.0.1。

如果代理暴露在公网上,请为代理主机添加 Access List,或者在路由器上保持 80 和 443 端口关闭并通过 VPN 访问主机。

NetBird

NetBird 是一套 WireGuard mesh,类似于 tailnet。它负责传输流量,由主机上的反向代理添加 HTTPS。

  1. 将主机和手机加入同一个 NetBird 网络。
    netbird up
  1. 将 collie.example.com 的 DNS 记录指向主机的 NetBird 地址(100.x.y.z)。
  2. 在主机上运行 Caddy,绑定到该地址,并使用 DNS challenge 证书。
    collie.example.com {
        bind 100.x.y.z
        tls {
            dns cloudflare {env.CF_API_TOKEN}
        }
        reverse_proxy 127.0.0.1:8787
    }

DNS challenge 可以在 Let's Encrypt 不访问主机的情况下证明你拥有该域名,因此该域名可以指向私网地址。Caddy 需要对应服务商的 DNS 插件,例如 xcaddy build --with github.com/caddy-dns/cloudflare。

像 host.netbird.cloud 这样的 NetBird 节点名没有公共受信任的证书,因此请使用你自己的域名。同样的操作也适用于 ZeroTier 或其他任何 mesh。

Caddy

Caddy 会自行获取并续签证书。在 80 和 443 端口可达的主机上,完整配置如下:

collie.example.com {
    reverse_proxy 127.0.0.1:8787
}

对于私网域名,添加上面 NetBird 中的 tls { dns … } 块。公网主机还需要在前端配置登录,如 变体 C 所示。

---

在指定路径下提供 Collie 服务

同一个 release 构建支持挂载到任意路径。在实例的 .env(或 config.toml 中的 [serve] base_path)中设置该路径并重启,bridge 即会在该路径下提供应用、静态资源和 /api/*:

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

支持:/collie、collie/、/apps/collie/。拒绝并在日志中输出一行并回退到根路径:. 或 .. 片段、空白字符、?、#。collie doctor 会在其 front-door 行打印挂载路径。

使用默认前置入口时,collie serve(以及每个 collie start)会发布 tailscale serve --set-path=/collie/ 而不是根路径,以便另一应用可以在同一节点上保留 /。Tailscale 会在代理前剥离挂载路径,而 bridge 也接收仍携带该路径的请求,因此反向代理无论哪种方式都能正常工作。检查配置:

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

在自定义代理(变体 C)后,将 bridge 挂载在相同的路径下。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 或迁回根路径后,请删除主屏幕图标,并从新地址重新添加;旧图标会继续指向 bridge 不再提供服务的路径。bridge 本身只需重启,collie serve 会自动迁移入口:它会在发布新映射之前拆除其记录中旧名称的映射。

---

单主机运行多个 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 实例

使用此配置在同一台机器上运行第二个独立的 Collie。例如,在工作副本旁运行稳定版本,或在 Herdr 管理的默认多路复用器旁运行第二个多路复用器(tmux/zellij)。每个实例都会获得专用的端口、配置、状态目录和服务单元。这与 单主机运行多个 Collie 不同,后者为每个 Unix 用户分配一个实例。在这里,单个用户即可运行多个实例。

创建第二个实例

设置 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> 存在的唯一目的是让共享同一个二进制文件的两个实例能够区分它们各自的桥接(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 处理程序文件,因此在一个实例上运行 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 仅跟踪无后缀的插件,因此该命令会针对第一个实例执行。在同一台主机上运行多个实例时,务必显式设置 COLLIE_INSTANCE,以避免修改错误的实例。

拒绝规则

如果设置了 COLLIE_INSTANCE 但缺少 herdr.collie-<name>/.env,CLI 将报错退出。它不会回退到另一个实例的配置。

配对基于单个实例

collie pair 会将配对码写入该实例的状态目录。打印的二维码编码了该实例自身的 URL,因为每个实例都有自己的状态目录,并通过 COLLIE_PUBLIC_URL 或其自身端口拥有独立的入口。在手机上打开该实例的具体 URL,并在“Settings → System → Paired devices”下输入配对码。为一个实例生成的配对码无法用于将设备配对到任何其他实例(配对设备)。

---

备用入口:crew 的故障转移路径

适用于 crew 部署。配置一个预授权的 deputy,以便在 lead 无法访问时接管(ADR 0027、ADR 0028、CREW_PROTOCOL.md §18)。

Deputy 和 lead 设置:

键默认值作用
COLLIE_STANDBY_PORT(未设置)备用监听器的端口。未设置时禁用备用入口。lead 和 deputy 上的端口必须一致。
COLLIE_STANDBY_HOST127.0.0.1绑定地址(本地代理为 127.0.0.1,远程为覆盖网络 IP)。
COLLIE_STANDBY_ARM_MSmax(30000, 2.5 × COLLIE_POLL_IDLE_MS)激活前要求的 lead 静默时长。

在 lead 和 deputy 上将 COLLIE_STANDBY_PORT 设置为相同的未占用端口。

前置条件:一个主机名,两个后端

Lead 和 deputy 必须由同一源提供服务,以共享 PWA 注册和设备凭据。没有统一 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(被废黜时返回非 200);deputy 在激活前返回 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 并重启。确保所有 peer 重启以加载 deputy warrant。在 https://collie.example.com/standby 验证。

⚠️ deputy 必须处于受控管理下

接管期间,deputy 写入状态并以状态码 75(EX_TEMPFAIL)退出,以触发进程重启(bridge/index.ts)。

确保进程管理器在非零退出码时重启(systemd:Restart=always 或 Restart=on-failure)。

故障场景:应急预案

  1. 打开 https://collie.example.com。
  2. 当 lead 处于不健康状态时,代理路由至 /standby。
  3. 检查 deputy 备用状态。
  4. 选择 接管。
  5. deputy 验证 peer 共识,应用 warrant,并退出 75。
  6. 进程管理器重启进程;PWA 重新加载为新 lead。

恢复后:

  • 原 lead 在重新连接后自我废黜(CREW_PROTOCOL.md §8.4)。
  • 更新被废黜节点的 peer 地址:独立 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