Saltar al contenido
ColliePWA

04/Documentation

Variantes de despliegue B–E

Puertas de entrada alternativas a la predeterminada: un proxy con reconocimiento de identidad, un proxy inverso sin Tailscale, un ingress externo al host, varios Collies en un mismo host (uno por usuario o varias instancias para un solo usuario) y la puerta de reserva de un pack

El puente se vincula a 127.0.0.1. Los despliegues varían según el ingreso y la verificación de identidad. Variante A (tailscale serve sin cifrar) se encuentra en el README. Los requisitos de seguridad de docs/security.md se aplican a todas las configuraciones.

Los dispositivos individuales también se pueden autorizar sin proxy mediante emparejamiento.

---

Variante B: proxy consciente de la identidad + autorización por dispositivo

Collie lee un ID de dispositivo opaco desde COLLIE_DEVICE_HEADER y comprueba COLLIE_DEVICE_ALLOWLIST. Los ID en la lista de permitidos reciben acceso de escritura; los ID ausentes o no listados reciben acceso de solo lectura (las lecturas, instantáneas y listas de sesiones permanecen abiertas; la entrada de terminal, las cargas y las acciones de paneles se bloquean).

Configuración de 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 del proxy:

  1. Autenticar el dispositivo (mTLS, SSO, forward-auth).
  2. Sobrescribir el encabezado del dispositivo en cada solicitud ascendente para evitar la suplantación de clientes.
  3. Hacer proxy a loopback (127.0.0.1:$COLLIE_PORT).
  4. Reenviar el Host público sin modificaciones, o listar el origen público en COLLIE_ALLOWED_ORIGINS para superar la comprobación de mismo origen.

Ejemplo de configuración de Nginx:

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

Verifique la inyección y sobrescritura de encabezados desde un dispositivo externo:

$ curl -s https://collie.example.com/api/snapshot | jq -c .device
{"enforced":true,"device":"my-laptop","authorized":true}

$ curl -s -H 'X-Device-Id: my-phone' https://collie.example.com/api/snapshot | jq -c .device
{"enforced":true,"device":"my-laptop","authorized":true}

Si la segunda comprobación devuelve "device":"my-phone", el proxy está añadiendo en lugar de sobrescribir.

Notas operativas:

  • El acceso directo por loopback (http://127.0.0.1:$COLLIE_PORT) no envía encabezados y es de solo lectura.
  • Para ejecutar comandos localmente mediante curl, pase el encabezado explícitamente a loopback: curl -H 'X-Device-Id: my-laptop' http://127.0.0.1:$COLLIE_PORT/api/...
  • Revoque el acceso eliminando el ID de COLLIE_DEVICE_ALLOWLIST y reiniciando (Standalone: bin/collie restart; Herdr: herdr plugin action invoke restart --plugin herdr.collie).
  • No active COLLIE_DEVICE_HEADER en tailscale serve básico; no sobrescribe encabezados.
  • Si el proxy se ejecuta en otro host, consulte Variante D.

---

Variante C: proxy inverso como única puerta de entrada (sin Tailscale)

Utilice esta opción al ejecutar fuera de Tailscale o detrás de un proxy TLS/SSO dedicado. COLLIE_SKIP_SERVE=1 evita que Collie administre tailscale serve.

Se aplican los cuatro requisitos de proxy de Variante B.

Configuración de ejemplo de Caddy:

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

Configuración de 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 (pack)
# COLLIE_PUBLIC_URL=https://collie.example.com

COLLIE_TRUSTED_USER no tiene efecto sin Tailscale. Los encabezados por dispositivo o la propia autenticación del proxy actúan como control de acceso.

Enrutamiento de /auth/ y almacenamiento en caché

  • Pase /sw.js y index.html con el origen Cache-Control (no-cache). No bloquee los recursos estáticos para clientes no autenticados, o los service workers no podrán actualizarse.
  • Enrute los flujos de autenticación bajo /auth/* (o /cdn-cgi/access/ para Cloudflare Access). Los service workers omiten el almacenamiento en caché para /auth/*.
  • Las redirecciones de forward-auth en solicitudes a la API se tratan como 401 para mostrar la interfaz de inicio de sesión. Las configuraciones con Authentik deben enrutar /auth/ a /outpost.goauthentik.io/start.
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 el endpoint y las reglas de almacenamiento en caché:

$ curl -s https://collie.example.com/api/snapshot | jq -c .device
{"enforced":true,"device":"my-phone","authorized":true}

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

---

Variante D: proxy de identidad fuera del host a través de la tailnet

Utilice esta opción al enrutar tráfico a través de un nodo de entrada centralizado de Tailscale.

  phone ──── https ────► ingress node          TLS + forward-auth; SETS the device header

                            │  http, never leaves the tailnet (WireGuard encrypts it)

                        host.your-tailnet.ts.net:8787     tailscale serve --http, tailnet-only


                        127.0.0.1:8787                    Collie

Se aplican los requisitos de Variante B, excepto que el proxy apunta al endpoint HTTP de Tailscale del host en lugar de loopback.

ACL de Tailscale (obligatorio)

Dado que tailscale serve reenvía los encabezados del cliente sin modificaciones, las ACL de Tailscale deben restringir el acceso directo al puerto únicamente al nodo de entrada.

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"]

Configuración de 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 coincide con la identidad del nodo de entrada, no con la del usuario final detrás de él.

Verificación:

Desde un par de tailnet que no sea de entrada:

$ curl -s https://collie.example.com/api/snapshot | jq -c .device
{"enforced":true,"device":"my-phone","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

En el host del agente:

$ curl -s http://127.0.0.1:8787/api/snapshot | jq -c .device
{"enforced":true,"device":null,"authorized":false}

---

Variante E: cualquier otra red en malla o túnel (NetBird, ZeroTier, Cloudflare Tunnel)

Apunte el túnel/proxy a 127.0.0.1:$COLLIE_PORT.

Configuración de 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

Reglas:

  1. Aplique las reglas de proxy de Variante B.
  2. COLLIE_TRUSTED_USER está inactivo sin Tailscale. Utilice COLLIE_DEVICE_HEADER o la autenticación del túnel.
  3. Utilice un nombre de host estático para que el almacenamiento en caché de la PWA y COLLIE_PUBLIC_HOSTS sigan siendo válidos.

---

Varios Collie en un mismo host

Para alojar instancias independientes por usuario en un sistema compartido (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

Verificación de URL de binario independiente: bin/collie url. Verificación de URL de plugin de Herdr: herdr plugin action invoke url --plugin herdr.collie.

Requisitos:

  • Ejecutar usuarios de Unix distintos e instancias separadas de herdr/Collie.
  • Definir siempre COLLIE_TRUSTED_USER para restringir el acceso por puerto.
  • Las vinculaciones iniciales de Tailscale requieren configuración de operador: ejecute bin/collie serve (o el equivalente de Herdr) con privilegios de operador.

COLLIE_SERVE_PORT define el puerto de entrada para una instancia. COLLIE_INSTANCE crea una instancia aislada con su propia unidad de servicio y configuración en el mismo host (Múltiples instancias de Collie en un mismo host).

---

Múltiples instancias de Collie en un mismo host

Use esta configuración para ejecutar un segundo Collie independiente en la misma máquina. Los ejemplos incluyen ejecutar una versión estable junto a una copia de trabajo, o ejecutar un segundo multiplexor (tmux/zellij) junto al valor predeterminado administrado por Herdr. Cada instancia obtiene un puerto dedicado, configuración, directorio de estado y unidad de servicio. Esto difiere de Varios Collie en un mismo host, que asigna una instancia por usuario de Unix. En este caso, un solo usuario ejecuta múltiples instancias.

Crear la segunda instancia

Defina COLLIE_INSTANCE=<name> para nombrar la instancia. El nombre debe coincidir con [a-z0-9-]{1,16}. Una instancia con nombre también requiere un COLLIE_PORT explícito. La CLI finaliza con un error si falta este puerto; no infiere ningún valor predeterminado.

Crear ~/.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 y COLLIE_STATE_DIR pueden residir en ese archivo .env. El entorno fusionado resuelve la instancia. HERDR_PLUGIN_CONFIG_DIR anula el directorio donde la CLI busca la configuración.

Ejecutar una unidad de servicio por instancia

collie start escribe la unidad cuando el entorno define la instancia. El operador no la escribe manualmente. En macOS escribe un plist de launchd en ~/Library/LaunchAgents/ en su lugar. La unidad define COLLIE_PORT, COLLIE_INSTANCE, COLLIE_PLUGIN_ROOT, HERDR_PLUGIN_CONFIG_DIR y EnvironmentFile=-<config dir>/.env. --instance <name> en ExecStart existe únicamente para que dos instancias que comparten un binario puedan distinguir sus bridges (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

La CLI mantiene un archivo de controlador tailscale serve por instancia, por lo que ejecutar unserve en una instancia no elimina la asignación de otra.

Apuntar a una instancia con nombre desde la CLI

Cada verbo de la CLI (pair, devices, url, qr, pack …, logs, push-test, …) resuelve su instancia de destino a partir del entorno del proceso. Defina COLLIE_INSTANCE antes de invocar el verbo:

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

Sin COLLIE_INSTANCE, la CLI consulta a Herdr. Herdr solo rastrea el plugin sin sufijo, por lo que el comando se ejecuta en la primera instancia. Defina siempre COLLIE_INSTANCE explícitamente al ejecutar múltiples instancias en un host para evitar modificar la instancia incorrecta.

La regla de rechazo

Si COLLIE_INSTANCE está definido pero falta herdr.collie-<name>/.env, la CLI finaliza con un error. No recurre a la configuración de otra instancia.

El emparejamiento es por instancia

collie pair escribe un código de emparejamiento en el directorio de estado de esa instancia. El QR que imprime codifica la URL propia de esa instancia, ya que cada instancia tiene su propio directorio de estado y, mediante COLLIE_PUBLIC_URL o su propio puerto, su propia puerta de acceso. Abra la URL específica de esa instancia en el teléfono e introduzca el código en Settings → Paired devices. Un código generado para una instancia no puede emparejar un dispositivo con ninguna otra instancia (Emparejar un dispositivo).

---

La puerta de reserva: la ruta de conmutación por error de un pack

Para despliegues de pack. Configura un deputy preautorizado para asumir el control si el líder queda inaccesible (ADR 0027, ADR 0028, PACK_PROTOCOL.md §18).

Configuración de deputy y líder:

ClavePredeterminadoQué hace
COLLIE_STANDBY_PORT(no definido)Puerto para el listener en standby. Si no se define, se desactiva la puerta de standby. Debe coincidir en el lead y en el deputy.
COLLIE_STANDBY_HOST127.0.0.1Dirección de enlace (127.0.0.1 para proxys locales, IP de overlay para remotos).
COLLIE_STANDBY_ARM_MSmax(30000, 2.5 × COLLIE_POLL_IDLE_MS)Duración del silencio del lead requerida antes del armado.

Defina COLLIE_STANDBY_PORT con un puerto idéntico y sin usar tanto en el lead como en el deputy.

Requisito previo: un hostname, dos backends

El lead y el deputy deben servirse desde el mismo origen para compartir el registro de la PWA y las credenciales del dispositivo. Los packs independientes sin un ingress unificado se recuperan mediante bin/collie promote (o Herdr: herdr plugin action invoke promote --plugin herdr.collie; PACK_PROTOCOL §14.4).

Configuración de ejemplo para Traefik:

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

  services:
    collie-pack:
      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

Respuestas del health check: el lead devuelve 200 (distinto de 200 cuando está depuesto); el deputy devuelve 503 hasta que se arma, y luego 200.

Configúrelo una vez, mientras todo funcione correctamente

Configuración independiente en el lead:

bin/collie pair
bin/collie pack deputy nas
bin/collie pack status

Configuración con Herdr en el lead:

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

En el deputy, configure COLLIE_STANDBY_PORT=8788 y reinicie. Asegúrese de reiniciar todos los peers para cargar el deputy warrant. Verifique en https://collie.example.com/standby.

⚠️ El deputy debe estar supervisado

Durante la toma de control, el deputy guarda el estado y finaliza con el código de salida 75 (EX_TEMPFAIL) para activar el reinicio del proceso (bridge/index.ts).

Asegúrese de que el supervisor se reinicie con códigos de salida distintos de cero (systemd: Restart=always o Restart=on-failure).

En caso de fallo: el runbook

  1. Abra https://collie.example.com.
  2. Cuando el lead no responde correctamente, el proxy enruta hacia /standby.
  3. Revise el estado de standby del deputy.
  4. Seleccione Tomar el control.
  5. El deputy verifica el consenso de los peers, aplica el warrant y finaliza con 75.
  6. El supervisor reinicia el proceso; la PWA se recarga como el nuevo lead.

Tras la recuperación:

  • El lead anterior se depone a sí mismo al reconectarse (PACK_PROTOCOL.md §8.4).
  • Actualice la dirección del peer del nodo depuesto: independiente bin/collie pack set-address <member> <host:port> o Herdr herdr plugin action invoke pack --plugin herdr.collie set-address <member> <host:port>.
  • Asigne un nuevo deputy: independiente bin/collie pack deputy <member> o Herdr herdr plugin action invoke pack --plugin herdr.collie deputy <member>.
  • No ejecute pack rotate hasta que todos los miembros se hayan reconectado.

Editar esta página en GitHub