Pular para o conteúdo
ColliePWA

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.

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

Requisitos do proxy:

  1. Autentique o dispositivo (mTLS, SSO, forward-auth).
  2. Sobrescreva o cabeçalho do dispositivo em cada requisição upstream para evitar spoofing pelo cliente.
  3. Faça proxy para o loopback (127.0.0.1:$COLLIE_PORT).
  4. Encaminhe o Host público sem alterações, ou liste a origem pública em COLLIE_ALLOWED_ORIGINS para 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_ALLOWLIST e reiniciando (Standalone: bin/collie restart; Herdr: herdr plugin action invoke restart --plugin herdr.collie).
  • Não ative COLLIE_DEVICE_HEADER em tailscale serve puro; ele não sobrescreve cabeçalhos.
  • Se o proxy rodar em outro host, veja Variante D.

---

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.com

COLLIE_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.js e index.html com origem Cache-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 a 404 lá. A página agora instrui um dispositivo não pareado a executar collie pair e 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.

Um nó de ingress autentica o telefone na borda da tailnet, e o próprio Collie responde apenas em loopback.phoneingress nodeagent hostCollie127.0.0.1:8787httpsTLS + forward-auth,sets the device headerhttp, tailnet only —WireGuard already encrypts ittailscale serve, tailnet-onlyloopback
Um nó de ingress autentica o telefone na borda da tailnet, e o próprio Collie responde apenas em loopback.

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.com

COLLIE_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 out

No 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.com

Regras:

  1. Aplique as regras de proxy Variante B.
  2. COLLIE_TRUSTED_USER fica inativo sem Tailscale. Use COLLIE_DEVICE_HEADER ou a autenticação do túnel.
  3. Use um hostname estático para que o cache do PWA e COLLIE_PUBLIC_HOSTS permaneç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 origin

Execute 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).

PortaPrivado por padrãoCertificado HTTPSEtapa extra
Tailscalesim, apenas na tailnetTailscale emiteative certificados HTTPS uma vez
Cloudflare Tunnelnão, públicoCloudflare emitelogin do Cloudflare Access na frente
Nginx Proxy Managerdepende do hostLet's Encryptrede do host para o container
NetBirdsim, apenas na meshnenhum próprioum proxy no host, desafio de DNS
Caddydepende do hostLet's Encrypt, automáticoum 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.

  1. No console de administração do Tailscale, abra DNS, ative o MagicDNS e depois Enable HTTPS.
  2. Inicie o Collie, que se publica na tailnet.
    collie start
  1. Defina COLLIE_TRUSTED_USER com seu login da tailnet em .env e 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.

  1. 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
  1. Crie ~/.cloudflared/config.yml com o id do túnel que create imprimiu.
    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. 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
  1. 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.
  1. 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.1 ou localhost sem Cf-* 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/health e os links de crew também não precisam de token. O comando collie 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 recebem 401. - Nenhum segundo proxy entre cloudflared e o Collie que faça as requisições parecerem locais. Quem reescreve Host para 127.0.0.1 ou localhost e remove o Cf-* e cabeçalhos de encaminhamento faz isso de propósito. Um proxy_pass http://127.0.0.1:8787 básico do nginx faz isso por padrão: ele envia Host: 127.0.0.1:8787 e não adiciona cabeçalho de encaminhamento a menos que você configure um. Essas requisições ignoram o token. Aponte cloudflared para 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 responde 401.

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.

  1. 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
  1. Abra a interface na porta 81 e adicione um Proxy Host: domínio collie.example.com, esquema http, encaminhar para 127.0.0.1, porta 8787.
  2. 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.internal com host-gateway não funciona aqui. Ele atinge o endereço da bridge do Docker, e o Collie escuta apenas em 127.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.

  1. Conecte o host e o telefone à mesma rede NetBird.
    netbird up
  1. Aponte um registro DNS de collie.example.com para o endereço NetBird do host (100.x.y.z).
  2. 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=/collie

Aceito: /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 right

Atrá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 agents

Verificaçã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_USER para 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>/.env

A 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 list

Sem 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:

ChavePadrãoO 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_HOST127.0.0.1Endereço de bind (127.0.0.1 para proxies locais, IP overlay para remotos).
COLLIE_STANDBY_ARM_MSmax(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: 2s

Respostas 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 status

Configuraçã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 status

No 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

  1. Abra https://collie.example.com.
  2. Quando o lead estiver instável, o proxy roteia para /standby.
  3. Revise o status de espera do deputy.
  4. Selecione Assumir o controle.
  5. O deputy verifica o consenso dos peers, aplica o warrant e encerra com 75.
  6. O supervisor reinicia o processo; o PWA recarrega como o novo lead.

Pós-recuperação:

  • O lead anterior se depõe ao reconectar (CREW_PROTOCOL.md §8.4).
  • Atualize o endereço de peer do nó deposto: Standalone bin/collie crew set-address <member> <host:port> ou Herdr herdr plugin action invoke crew --plugin herdr.collie set-address <member> <host:port>.
  • Defina um novo deputy: Standalone bin/collie crew deputy <member> ou Herdr herdr plugin action invoke crew --plugin herdr.collie deputy <member>.
  • Não execute crew rotate até que todos os membros tenham se reconectado.

Editar esta página no GitHub

The rest of the documentation