17/Documentation
Variantes de implantação B–E
Portas de entrada além da padrão: um proxy com reconhecimento de identidade, um proxy reverso sem Tailscale, um ingress externo ao host, vários Collies em um único host (um por usuário ou várias instâncias para um usuário) e a porta reserva de uma crew
A ponte escuta em 127.0.0.1. As implantações diferem pelo ingresso e pela verificação de identidade. A Variante A (tailscale serve simples) está no README. Os requisitos de segurança em docs/security.md se aplicam a todos os formatos.
- Portas de entrada, um produto por vez: Tailscale, Cloudflare Tunnel, Nginx Proxy Manager, NetBird, Caddy
Dispositivos individuais também podem ser autorizados sem um proxy via pareamento.
---
Variante B: proxy ciente de identidade + autorização por dispositivo
O Collie lê um ID de dispositivo opaco de COLLIE_DEVICE_HEADER e consulta COLLIE_DEVICE_ALLOWLIST. IDs na allowlist recebem acesso de gravação; IDs ausentes ou fora da lista recebem acesso somente leitura (entrada de terminal, uploads e ações de painel são bloqueados). O pareamento é uma segunda barreira e fica sempre ativo: todo dispositivo também precisa de um token de pareamento para cada chamada /api, leituras inclusas.
Configuração do 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=1Requisitos do proxy:
- Autentique o dispositivo (mTLS, SSO, forward-auth).
- Sobrescreva o cabeçalho do dispositivo em cada requisição upstream para evitar spoofing pelo cliente.
- Faça proxy para o loopback (
127.0.0.1:$COLLIE_PORT). - Encaminhe o
Hostpúblico sem alterações, ou liste a origem pública emCOLLIE_ALLOWED_ORIGINSpara passar na verificação de mesma origem.
Exemplo de configuração do Nginx:
location / {
proxy_set_header X-Device-Id $device_id;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8787;
}Verifique a injeção e sobreposição de cabeçalho a partir de um dispositivo externo. Toda chamada /api exceto /api/health e /api/pair precisa de um token de pareamento, portanto pareie um terminal primeiro. No host, execute collie pair. Ele exibe um código de 8 caracteres. Resgate o código no dispositivo usado para o teste. O cabeçalho Origin precisa corresponder à URL pública. A resposta contém o token. Guarde-o em uma variável de shell e nunca o cole em um arquivo:
$ 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)Em seguida, execute a verificação de cabeçalho com o 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}Com um token, device informa o rótulo pareado (header-check), não o valor do cabeçalho. Nenhum campo retorna mais o valor bruto do cabeçalho, então leia authorized: ele é true somente quando o cabeçalho visto pela bridge está em COLLIE_DEVICE_ALLOWLIST e o token é válido. Se a segunda verificação retornar "authorized":false, o proxy está anexando o cabeçalho do cliente em vez de sobrescrevê-lo. Quando terminar, remova o dispositivo de teste com collie devices revoke header-check.
Notas operacionais:
- O acesso direto via loopback (
http://127.0.0.1:$COLLIE_PORT) não envia cabeçalho, é somente leitura e ainda precisa de um token de pareamento. - Para executar comandos localmente via curl, passe o token e o cabeçalho explicitamente para o loopback:
curl -H "Authorization: Bearer $TOKEN" -H 'X-Device-Id: my-laptop' http://127.0.0.1:$COLLIE_PORT/api/... - Revogue o acesso removendo o ID de
COLLIE_DEVICE_ALLOWLISTe reiniciando (Standalone:bin/collie restart; Herdr:herdr plugin action invoke restart --plugin herdr.collie). - Não ative
COLLIE_DEVICE_HEADERemtailscale servepuro; ele não sobrescreve cabeçalhos.
---
Variante C: proxy reverso como única porta de entrada (sem Tailscale)
Use isto ao rodar fora do Tailscale ou atrás de um proxy TLS/SSO dedicado. COLLIE_SKIP_SERVE=1 impede o Collie de gerenciar tailscale serve.
Os quatro requisitos de proxy de Variante B se aplicam.
Exemplo de configuração do Caddy:
collie.example.com {
reverse_proxy 127.0.0.1:8787 {
header_up X-Device-Id {your_device_id}
header_up Host {host}
}
}Configuração do 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.comCOLLIE_TRUSTED_USER não tem efeito sem o Tailscale. Cabeçalhos por dispositivo ou a própria autenticação do proxy servem como controle de acesso.
Roteamento de /auth/ e cache
- Passe
/sw.jseindex.htmlcom origemCache-Control(no-cache). Não bloqueie recursos estáticos para clientes não autenticados, caso contrário os service workers não conseguirão atualizar. - Encaminhe os fluxos de autenticação sob
/auth/*(ou/cdn-cgi/access/para Cloudflare Access). Service workers ignoram o cache para/auth/*. - Redirecionamentos de forward-auth em requisições de API são tratados como 401s para exibir a interface de login. Configurações com Authentik devem encaminhar
/auth/para/outpost.goauthentik.io/start. - Sem um proxy em
/auth/, o Collie responde a404lá. A página agora instrui um dispositivo não pareado a executarcollie paire inserir o código em Configurações. Ela nunca redireciona, porque o caminho pertence ao seu proxy.
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 { ... }
}
}Verifique as regras de endpoint e de cache. $TOKEN é um token de pareamento, resgatado como em Variante B; authorized é true quando seu cabeçalho está na allowlist:
$ 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---
Variante D: proxy de identidade fora do host pela tailnet
Use esta opção ao rotear tráfego através de um nó de ingress centralizado da Tailscale.
Os requisitos de Variante B se aplicam, exceto que o proxy tem como destino o endpoint HTTP da Tailscale no host em vez do loopback.
Tailscale ACLs (Obrigatório)
Como tailscale serve encaminha cabeçalhos de cliente sem modificação, as Tailscale ACLs devem restringir o acesso direto à porta apenas ao nó de ingress.
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"]Configuração do 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.comCOLLIE_TRUSTED_USER corresponde à identidade do nó de ingress, não ao usuário final atrás dele.
Verificação:
A partir de um peer da tailnet sem ingress. $TOKEN é um token de pareamento, resgatado como em Variante 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 outNo host do agente, uma requisição não envia cabeçalho, então authorized é false mesmo com um token válido:
$ curl -s -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8787/api/snapshot | jq -c .device
{"enforced":true,"device":"header-check","authorized":false}---
Variante E: qualquer outra malha ou túnel (NetBird, ZeroTier, Cloudflare Tunnel)
Aponte o túnel/proxy para 127.0.0.1:$COLLIE_PORT.
Configuração do 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.comRegras:
COLLIE_TRUSTED_USERfica inativo sem Tailscale. UseCOLLIE_DEVICE_HEADERou a autenticação do túnel.- Use um hostname estático para que o cache do PWA e
COLLIE_PUBLIC_HOSTSpermaneçam válidos.
---
Portas de entrada, um produto por vez
Configurações básicas para as portas de entrada mais comuns. Cada uma termina no mesmo lugar: HTTPS em um hostname fixo, encaminhado para 127.0.0.1:$COLLIE_PORT neste host.
Nota. Web Push, o microfone e a instalação na tela inicial precisam de HTTPS com um certificado em que o celular confie. Em HTTP simples, o Collie ainda lê e responde em uma aba do navegador, e esses três recursos continuam desativados (Entrada de voz e Web Push).
Toda porta de entrada, exceto o Tailscale, usa as mesmas três linhas no .env do Collie, seguido de um reinício:
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 originExecute o proxy ou túnel no mesmo host do Collie. O Collie recusa conexões que não venham de loopback, portanto um proxy em outra máquina precisa de Variante D.
Em seguida, emparelhe o celular com collie pair. Nenhuma dessas portas sabe qual dispositivo pode digitar; o emparelhamento sabe (Parear um dispositivo).
| Porta | Privado por padrão | Certificado HTTPS | Etapa extra |
|---|---|---|---|
| Tailscale | sim, apenas na tailnet | Tailscale emite | ative certificados HTTPS uma vez |
| Cloudflare Tunnel | não, público | Cloudflare emite | login do Cloudflare Access na frente |
| Nginx Proxy Manager | depende do host | Let's Encrypt | rede do host para o container |
| NetBird | sim, apenas na mesh | nenhum próprio | um proxy no host, desafio de DNS |
| Caddy | depende do host | Let's Encrypt, automático | um plugin de DNS para um nome privado |
Tailscale
O padrão, e a única porta que o Collie gerencia por conta própria: o collie start executa o tailscale serve no seu nome MagicDNS.
- No console de administração do Tailscale, abra DNS, ative o MagicDNS e depois Enable HTTPS.
- Inicie o Collie, que se publica na tailnet.
collie start
- Defina
COLLIE_TRUSTED_USERcom seu login da tailnet em.enve reinicie.collie restart
Variante A contém o restante. Não defina COLLIE_SKIP_SERVE para esta porta.
Nunca use tailscale funnel. O Funnel expõe a porta na internet pública.Cloudflare Tunnel
O cloudflared conecta ativamente à Cloudflare, portanto nenhuma porta é aberta no seu roteador. O hostname é público, então um login precisa ficar na frente dele.
- Faça login, crie um túnel e aponte um hostname para ele.
cloudflared tunnel login cloudflared tunnel create collie cloudflared tunnel route dns collie collie.example.com
- Crie
~/.cloudflared/config.ymlcom o id do túnel quecreateimprimiu.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
- Instale-o como um serviço com essa configuração, para que ele continue ativo após reiniciar.
sudo cloudflared --config ~/.cloudflared/config.yml service install
- No Cloudflare Zero Trust, adicione uma self-hosted application para
collie.example.com, com uma política que permita apenas o seu e-mail.
Um túnel criado no painel do Zero Trust também funciona: defina o serviço do seu hostname público como http://127.0.0.1:8787. O cloudflared encaminha o hostname público como Host, que é o que o COLLIE_PUBLIC_HOSTS espera.
Aviso. Sem o passo 4, qualquer pessoa que encontrar o hostname terá um shell na sua máquina. O Cloudflare Access é a tranca da porta; o pareamento decide então qual dispositivo pode digitar.
- Faça o Collie verificar o bloqueio. No aplicativo do Access, copie a tag Application Audience (AUD). Adicione ela e o nome da sua equipe ao
.env, depois reinicie.COLLIE_ACCESS_TEAM=myteam # from myteam.cloudflareaccess.com COLLIE_ACCESS_AUD=<the AUD tag>Cada requisição pelo túnel agora deve conter um token assinado pela Cloudflare para este aplicativo. Se o app do Access for excluído, receber uma regra de bypass ou ainda não tiver chegado a todos os edges, o Collie recusa a requisição em vez de exibir os painéis. A verificação de audience é essencial: todos os apps do Access em uma equipe assinam com a mesma chave, portanto um token de outro app também é recusado.
O Collie busca as chaves públicas da equipe na inicialização e a cada hora. Até que a primeira busca funcione, toda requisição remota recebe
503. Se uma busca posterior falhar, o Collie mantém as chaves atuais. Configure ambas as linhas ou nenhuma: definir apenas uma recusa qualquer requisição remota. De qualquer forma, o log da bridge descreve o problema em uma única linha; verifique o log após reiniciar.Apenas processos nesta máquina ignoram o token: uma requisição para
127.0.0.1oulocalhostsemCf-*e sem cabeçalhos de encaminhamento (X-Forwarded-For,X-Forwarded-Host,X-Forwarded-Proto,Forwarded,X-Real-Ip). Ela ainda precisa de pareamento para digitar./api/healthe os links de crew também não precisam de token. O comandocollie doctoré um desses clientes locais, portanto um doctor verde não garante que as chaves foram carregadas ou que um token é válido. Abra o hostname pelo celular para testar. ADR 0081 traz os detalhes.Mantenha estas regras com o gate ativo:
- Uma porta de entrada por vez. Com o Access ativado, uma requisição via
tailscale serve, Caddy ou Traefik também precisa de token, porque eles adicionam um cabeçalho de encaminhamento por padrão. Uma máquina que serve tanto um túnel quanto um nome da tailnet (tailscale serve) perde a porta da tailnet para navegadores: essas requisições não carregam token do Access e recebem401. - Nenhum segundo proxy entrecloudflarede o Collie que faça as requisições parecerem locais. Quem reescreveHostpara127.0.0.1oulocalhoste remove oCf-*e cabeçalhos de encaminhamento faz isso de propósito. Umproxy_pass http://127.0.0.1:8787básico do nginx faz isso por padrão: ele enviaHost: 127.0.0.1:8787e não adiciona cabeçalho de encaminhamento a menos que você configure um. Essas requisições ignoram o token. Apontecloudflaredpara o Collie diretamente. - Não adicione regras de bypass do Access para caminhos do Collie, nem para o manifesto, o service worker ou os ícones. O Access adiciona o token a cada requisição que deixa passar. Um caminho ignorado não carrega token, então o Collie responde401.
O Access redireciona para /cdn-cgi/access/ para fazer login. Mantenha sw.js e index.html fora do cache, conforme indicado por Roteamento de /auth/ e cache.
Nginx Proxy Manager
Um proxy reverso com interface web e Let's Encrypt integrados. Ele roda no Docker, e dentro de um contêiner 127.0.0.1 é o contêiner, não o seu host.
- Dê ao contêiner a rede do host, para que ele possa acessar o Collie no loopback.
services: npm: image: jc21/nginx-proxy-manager:latest network_mode: host volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt
- Abra a interface na porta 81 e adicione um Proxy Host: domínio
collie.example.com, esquemahttp, encaminhar para127.0.0.1, porta8787. - Na aba SSL, solicite um certificado Let's Encrypt e ative Force SSL.
Deixe Fazer cache de assets desativado, para que o service worker possa atualizar. O Collie faz polling via HTTP simples, portanto a opção Suporte a Websockets não faz diferença.
Para um nome que resolve apenas dentro da sua rede, selecione Usar um DNS Challenge na aba SSL. O Let's Encrypt não consegue alcançar um host privado via HTTP.
Aviso.host.docker.internalcomhost-gatewaynão funciona aqui. Ele atinge o endereço da bridge do Docker, e o Collie escuta apenas em127.0.0.1.
Se o proxy estiver aberto para a internet, adicione uma Access List ao host do proxy, ou mantenha as portas 80 e 443 fechadas no roteador e acesse o host por uma VPN.
NetBird
O NetBird é uma malha WireGuard, como uma tailnet. Ele transporta o tráfego, e um proxy reverso no host adiciona HTTPS.
- Conecte o host e o telefone à mesma rede NetBird.
netbird up
- Aponte um registro DNS de
collie.example.compara o endereço NetBird do host (100.x.y.z). - Execute o Caddy no host, vinculado a esse endereço, com um certificado via DNS challenge.
collie.example.com { bind 100.x.y.z tls { dns cloudflare {env.CF_API_TOKEN} } reverse_proxy 127.0.0.1:8787 }
O DNS challenge prova que você é dono do domínio sem que o Let's Encrypt precise alcançar o host, permitindo que o nome aponte para um endereço privado. O Caddy precisa do plugin de DNS do seu provedor, por exemplo xcaddy build --with github.com/caddy-dns/cloudflare.
Um nome de peer do NetBird como host.netbird.cloud não tem certificado publicamente confiável, então use um domínio próprio. A mesma receita serve para o ZeroTier ou qualquer outra malha.
Caddy
O Caddy obtém e renova o próprio certificado. Em um host acessível nas portas 80 e 443, esta é toda a configuração necessária:
collie.example.com {
reverse_proxy 127.0.0.1:8787
}Para um nome privado, adicione o bloco tls { dns … } de NetBird acima. Um host público também precisa de autenticação na frente, como em Variante C.
---
Servindo o Collie sob um caminho
Um único build de release atende a qualquer montagem. Defina o caminho em .env da instância (ou [serve] base_path em config.toml), reinicie, e a bridge servirá o app, seus assets e /api/* sob esse caminho:
# https://<your-node>/collie/ — leading and trailing slash both optional; the bridge adds them
COLLIE_BASE_PATH=/collieAceito: /collie, collie/, /apps/collie/. Recusado, com uma linha no log e a raiz usada no lugar: segmento . ou .., espaço em branco, ?, #. collie doctor exibe a montagem na sua linha front-door.
Com a porta de entrada padrão, collie serve (e cada collie start) publica tailscale serve --set-path=/collie/ em vez da raiz, permitindo que outro app mantenha / no mesmo nó. O Tailscale remove a montagem antes de fazer o proxy, e a bridge também aceita requisições que ainda a contenham, então o proxy reverso funciona de ambas as formas. Para verificar a sua:
curl -s https://<your-node>/collie/api/health # JSON, not HTML, means the mount is rightAtrás do seu próprio proxy (Variante C), monte a bridge no mesmo caminho. Caddy: handle_path /collie/* { reverse_proxy 127.0.0.1:8787 }. Nginx: location /collie/ { proxy_pass http://127.0.0.1:8787/; }. Se você definir COLLIE_PUBLIC_URL, inclua o caminho: https://collie.example.com/collie/.
Uma alteração de montagem equivale a um novo app no telefone. O escopo e a identidade do PWA instalado são o caminho a partir do qual ele foi instalado. Após alterar COLLIE_BASE_PATH ou voltar para a raiz, remova o ícone da tela inicial e adicione-o novamente a partir do novo endereço; o ícone antigo continuará apontando para um caminho que a bridge não serve mais. A bridge precisa apenas de um reinício, e o collie serve move a porta por conta própria: ele desfaz o mapeamento indicado no registro antes de publicar o novo.
---
Vários Collies em um único host
Para hospedar instâncias independentes por usuário em um sistema compartilhado (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 agentsVerificação de URL do binário standalone: bin/collie url. Verificação de URL do plugin Herdr: herdr plugin action invoke url --plugin herdr.collie.
Requisitos:
- Execute usuários Unix distintos e instâncias separadas de
herdr/Collie. - Sempre defina
COLLIE_TRUSTED_USERpara restringir o acesso por porta. - As associações iniciais da Tailscale exigem configuração do operador: execute
bin/collie serve(ou equivalente no Herdr) com privilégios de operador.
COLLIE_SERVE_PORT define a porta de entrada para uma instância. COLLIE_INSTANCE cria uma instância isolada com sua própria unidade de serviço e configuração no mesmo host (Várias instâncias do Collie em um único host).
---
Várias instâncias do Collie em um único host
Use esta configuração para executar um segundo Collie distinto na mesma máquina. Exemplos incluem executar uma versão estável junto com uma cópia de trabalho, ou executar um segundo multiplexador (tmux/zellij) ao lado do padrão gerenciado pelo Herdr. Cada instância recebe uma porta dedicada, configuração, diretório de estado e unidade de serviço. Isso difere de Vários Collies em um único host, que aloca uma instância por usuário Unix. Aqui, um único usuário executa múltiplas instâncias.
Crie a segunda instância
Defina COLLIE_INSTANCE=<name> para nomear a instância. O nome deve corresponder a [a-z0-9-]{1,16}. Uma instância nomeada também exige uma COLLIE_PORT explícita. A CLI encerra com um erro se essa porta estiver ausente; nenhum valor padrão é inferido.
Crie ~/.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 e COLLIE_STATE_DIR podem residir nesse arquivo .env. O ambiente mesclado resolve a instância. HERDR_PLUGIN_CONFIG_DIR substitui o diretório onde a CLI busca pela configuração.
Execute uma unidade de serviço por instância
O collie start grava a unidade quando o ambiente define a instância. O operador não a escreve manualmente. No macOS, ele grava um plist do launchd em ~/Library/LaunchAgents/. A unidade define COLLIE_PORT, COLLIE_INSTANCE, COLLIE_PLUGIN_ROOT, HERDR_PLUGIN_CONFIG_DIR e EnvironmentFile=-<config dir>/.env. --instance <name> em ExecStart existe apenas para que duas instâncias que compartilham um binário possam distinguir suas pontes (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>/.envA CLI mantém um arquivo de manipulador tailscale serve por instância, portanto, executar unserve em uma instância não exclui o mapeamento de outra.
Direcione uma instância nomeada a partir da CLI
Cada verbo da CLI (pair, devices, url, qr, crew …, logs, push-test, …) resolve sua instância de destino a partir do ambiente do processo. Defina COLLIE_INSTANCE antes de invocar o verbo:
COLLIE_INSTANCE=next bin/collie pair
COLLIE_INSTANCE=next bin/collie devices listSem COLLIE_INSTANCE, a CLI consulta o Herdr. O Herdr rastreia apenas o plugin sem sufixo, portanto o comando é executado na primeira instância. Sempre defina COLLIE_INSTANCE explicitamente ao executar várias instâncias em um único host para evitar modificar a instância errada.
A regra de recusa
Se COLLIE_INSTANCE estiver definido, mas herdr.collie-<name>/.env estiver ausente, a CLI é encerrada com um erro. Ela não usa a configuração de outra instância como fallback.
O pareamento é por instância
collie pair grava um código de pareamento no diretório de estado dessa instância. O QR gerado codifica a própria URL dessa instância, já que cada instância tem seu próprio diretório de estado e, via COLLIE_PUBLIC_URL ou por sua própria porta, sua própria porta de entrada. Abra a URL específica dessa instância no celular e insira o código em Configurações → Sistema → Dispositivos pareados. Um código gerado para uma instância não pode parear um dispositivo com nenhuma outra instância (Parear um dispositivo).
---
A porta de contingência: caminho de failover de uma equipe
Para implantações crew. Configura um deputy pré-autorizado para assumir o controle se o lead ficar inacessível (ADR 0027, ADR 0028, CREW_PROTOCOL.md §18).
Configurações do deputy e do lead:
| Chave | Padrão | O que faz |
|---|---|---|
COLLIE_STANDBY_PORT | (não definido) | Porta para o listener em standby. Deixar em branco desativa a porta de standby. Deve ser idêntica no lead e no deputy. |
COLLIE_STANDBY_HOST | 127.0.0.1 | Endereço de bind (127.0.0.1 para proxies locais, IP overlay para remotos). |
COLLIE_STANDBY_ARM_MS | max(30000, 2.5 × COLLIE_POLL_IDLE_MS) | Duração de silêncio do lead necessária antes de armar. |
Defina COLLIE_STANDBY_PORT para uma porta idêntica e não utilizada tanto no lead quanto no deputy.
O pré-requisito: um hostname, dois backends
Lead e deputy devem ser servidos a partir da mesma origem para compartilhar o registro do PWA e as credenciais do dispositivo. Uma crew independente sem ingress unificado se recupera via bin/collie promote (ou Herdr: herdr plugin action invoke promote --plugin herdr.collie; CREW_PROTOCOL §14.4).
Exemplo de configuração do 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: 2sRespostas de verificação de integridade: o lead retorna 200 (não-200 quando deposto); o deputy retorna 503 até armar, depois 200.
Configure uma vez, enquanto tudo estiver íntegro
Configuração standalone no lead:
bin/collie pair
bin/collie crew deputy nas
bin/collie crew statusConfiguração do Herdr no lead:
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 statusNo deputy, configure COLLIE_STANDBY_PORT=8788 e reinicie. Certifique-se de que todos os peers reiniciem para carregar a warrant do deputy. Verifique em https://collie.example.com/standby.
⚠️ O deputy precisa ser supervisionado
Durante a tomada de controle, o deputy grava o estado e encerra com código de saída 75 (EX_TEMPFAIL) para acionar o reinício do processo (bridge/index.ts).
Garanta que o supervisor reinicie em códigos de saída diferentes de zero (systemd: Restart=always ou Restart=on-failure).
O pior cenário: o runbook
- Abra
https://collie.example.com. - Quando o lead estiver instável, o proxy roteia para
/standby. - Revise o status de espera do deputy.
- Selecione Assumir o controle.
- O deputy verifica o consenso dos peers, aplica o warrant e encerra com
75. - O supervisor reinicia o processo; o PWA recarrega como o novo lead.
Pós-recuperação:
- Atualize o endereço de peer do nó deposto: Standalone
bin/collie crew set-address <member> <host:port>ou Herdrherdr plugin action invoke crew --plugin herdr.collie set-address <member> <host:port>. - Defina um novo deputy: Standalone
bin/collie crew deputy <member>ou Herdrherdr plugin action invoke crew --plugin herdr.collie deputy <member>. - Não execute
crew rotateaté que todos os membros tenham se reconectado.