Aller au contenu
ColliePWA

17/Documentation

Variantes de déploiement B–E

Points d'entrée autres que celui par défaut : un proxy avec gestion des identités, un reverse proxy sans Tailscale, un ingress hors hôte, plusieurs Collies sur un seul hôte (un par utilisateur, ou plusieurs instances pour un seul utilisateur), et le point d'entrée de secours d'une crew

Le pont écoute sur 127.0.0.1. Les déploiements diffèrent par leur point d'entrée et la vérification des identités. La Variante A (tailscale serve brut) se trouve dans le README. Les exigences de sécurité de docs/security.md s'appliquent à toutes les configurations.

Des appareils individuels peuvent aussi être autorisés sans proxy via association.

---

Variante B : proxy avec gestion des identités + autorisation par appareil

Collie lit un identifiant d'appareil opaque depuis COLLIE_DEVICE_HEADER et vérifie COLLIE_DEVICE_ALLOWLIST. Les identifiants autorisés reçoivent un accès en écriture ; les identifiants manquants ou absents de la liste reçoivent un accès en lecture seule (la saisie dans le terminal, les téléversements et les actions de panneau sont bloqués). L'association constitue un second filtre toujours actif : chaque appareil requiert également un jeton d'association pour chaque appel /api, lectures comprises.

Configuration 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

Exigences pour le proxy :

  1. Authentifiez l'appareil (mTLS, SSO, forward-auth).
  2. Écrasez l'en-tête d'appareil sur chaque requête vers l'amont pour empêcher l'usurpation par le client.
  3. Transférez vers la boucle locale (127.0.0.1:$COLLIE_PORT).
  4. Transférez le Host public sans modification, ou listez l'origine publique dans COLLIE_ALLOWED_ORIGINS pour passer la vérification d'origine identique (same-origin).

Exemple de configuration Nginx :

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

Vérifiez l'injection et la substitution d'en-tête depuis un appareil externe. Chaque appel /api sauf /api/health et /api/pair requiert un jeton d'association : associez d'abord un terminal. Sur l'hôte, exécutez collie pair. La commande affiche un code à 8 caractères. Réclamez le code depuis l'appareil de test. L'en-tête Origin doit correspondre à l'URL publique. La réponse contient le jeton. Conservez-le dans une variable de shell et ne le collez jamais dans un fichier :

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

Exécutez ensuite la vérification d'en-tête avec le jeton :

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

Avec un jeton, device indique le libellé associé (header-check), pas la valeur de l'en-tête. Aucun champ ne renvoie plus la valeur brute de l'en-tête, lisez donc authorized : sa valeur est vraie uniquement si l'en-tête vu par le pont figure sur COLLIE_DEVICE_ALLOWLIST et que le jeton est valide. Si la seconde vérification renvoie "authorized":false, le proxy ajoute l'en-tête du client au lieu de le remplacer. Lorsque vous avez terminé, supprimez l'appareil de test avec collie devices revoke header-check.

Notes d'exploitation :

  • L'accès local direct (http://127.0.0.1:$COLLIE_PORT) n'envoie aucun en-tête et s'effectue en lecture seule, tout en exigeant toujours un jeton d'association.
  • Pour exécuter des commandes localement via curl, transmettez explicitement le jeton et l'en-tête à l'adresse de bouclage : curl -H "Authorization: Bearer $TOKEN" -H 'X-Device-Id: my-laptop' http://127.0.0.1:$COLLIE_PORT/api/...
  • Révoquez l'accès en supprimant l'identifiant de COLLIE_DEVICE_ALLOWLIST et en redémarrant (Standalone : bin/collie restart ; Herdr : herdr plugin action invoke restart --plugin herdr.collie).
  • N'activez pas COLLIE_DEVICE_HEADER sur un simple tailscale serve ; il n'écrase pas les en-têtes.
  • Si le proxy s'exécute sur un autre hôte, consultez Variante D.

---

Variante C : reverse proxy comme unique point d'entrée (sans Tailscale)

Utilisez cette option si vous travaillez hors de Tailscale ou derrière un proxy TLS/SSO dédié. COLLIE_SKIP_SERVE=1 empêche Collie de gérer tailscale serve.

Les quatre exigences de proxy issues de Variante B s'appliquent.

Exemple de configuration Caddy :

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

Configuration 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 (crew)
# COLLIE_PUBLIC_URL=https://collie.example.com

COLLIE_TRUSTED_USER n'a aucun effet sans Tailscale. Les en-têtes par appareil ou l'authentification propre au proxy servent de contrôle d'accès.

Routage de /auth/ et mise en cache

  • Transmettez /sw.js et index.html avec l'origine Cache-Control (no-cache). Ne bloquez pas les ressources statiques pour les clients non authentifiés, sinon les service workers ne pourront pas se mettre à jour.
  • Acheminez les flux d'authentification sous /auth/* (ou /cdn-cgi/access/ pour Cloudflare Access). Les service workers contournent le cache pour /auth/*.
  • Les redirections forward-auth sur les requêtes d'API sont traitées comme des erreurs 401 pour afficher l'interface de connexion. Les configurations Authentik doivent acheminer /auth/ vers /outpost.goauthentik.io/start.
  • Sans proxy sur /auth/, Collie y répond 404. La page indique désormais à un appareil non associé d'exécuter collie pair et de saisir le code dans Settings. Elle ne redirige jamais, car le chemin appartient à votre 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 { ... }
    }
}

Vérifiez le point de terminaison et les règles de mise en cache. $TOKEN est un jeton d'association, réclamé comme dans Variante B ; authorized est vrai quand votre en-tête figure sur la liste d'autorisations :

$ 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 d'identité externe sur le tailnet

Utilisez cette méthode pour router le trafic via un nœud d'entrée Tailscale centralisé.

Un nœud d'entrée authentifie le téléphone à la périphérie du tailnet, et Collie lui-même ne répond que sur loopback.phoneingress nodeagent hostCollie127.0.0.1:8787httpsTLS + forward-auth,sets the device headerhttp, tailnet only —WireGuard already encrypts ittailscale serve, tailnet-onlyloopback
Un nœud d'entrée authentifie le téléphone à la périphérie du tailnet, et Collie lui-même ne répond que sur loopback.

Les exigences de Variante B s'appliquent, sauf que le proxy cible le point de terminaison HTTP Tailscale de l'hôte au lieu du loopback.

ACLs Tailscale (obligatoire)

Comme tailscale serve transmet les en-têtes du client sans modification, les ACLs Tailscale doivent restreindre l'accès direct aux ports au seul nœud d'entrée.

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

Configuration 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 correspond à l'identité du nœud d'entrée, et non à celle de l'utilisateur final qui l'utilise.

Vérification :

Depuis un pair du tailnet non configuré en ingress. $TOKEN est un jeton d'association, réclamé comme dans 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

Sur l'hôte de l'agent, une requête n'envoie aucun en-tête, authorized est donc faux même avec un jeton valide :

$ 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 : tout autre maillage ou tunnel (NetBird, ZeroTier, Cloudflare Tunnel)

Pointez le tunnel/proxy vers 127.0.0.1:$COLLIE_PORT.

Configuration 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

Règles :

  1. Appliquez les règles de proxy de Variante B.
  2. COLLIE_TRUSTED_USER est inactif sans Tailscale. Utilisez COLLIE_DEVICE_HEADER ou l'authentification du tunnel.
  3. Utilisez un nom d'hôte statique pour que le cache de la PWA et COLLIE_PUBLIC_HOSTS restent valides.

---

Points d'entrée, un produit à la fois

Configurations de base pour les points d'entrée fréquemment demandés. Chacune aboutit au même résultat : HTTPS sur un nom d'hôte fixe, redirigé vers 127.0.0.1:$COLLIE_PORT sur cet hôte.

Remarque. Web Push, le microphone et l'installation sur l'écran d'accueil nécessitent HTTPS avec un certificat approuvé par le téléphone. En HTTP simple, Collie lit et répond toujours dans un onglet de navigateur, et ces trois fonctionnalités restent désactivées (Saisie vocale et Web Push).

Chaque point d'entrée sauf Tailscale utilise les mêmes trois lignes dans le fichier .env de Collie, suivi d'un redémarrage :

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

Exécutez le proxy ou le tunnel sur le même hôte que Collie. Collie refuse toute connexion qui ne provient pas de l'interface de bouclage ; un proxy sur une autre machine nécessite donc Variante D à la place.

Associez ensuite le téléphone avec collie pair. Aucun de ces points d'entrée ne sait quel appareil a le droit d'écrire ; l'association s'en charge (Associer un appareil).

Point d'entréePrivé par défautCertificat HTTPSÉtape supplémentaire
Tailscaleoui, tailnet uniquementTailscale le délivreactiver les certificats HTTPS une seule fois
Cloudflare Tunnelnon, publicCloudflare le délivreconnexion Cloudflare Access en amont
Nginx Proxy Managerdépend de l'hôteLet's Encryptmise en réseau hôte pour le conteneur
NetBirdoui, maillage uniquementaucun certificat propreun proxy sur l'hôte, défi DNS
Caddydépend de l'hôteLet's Encrypt, automatiqueun plugin DNS pour un nom privé

Tailscale

L'option par défaut, et la seule porte que Collie gère lui-même : collie start exécute tailscale serve sur votre nom MagicDNS.

  1. Dans la console d'administration Tailscale, ouvrez DNS, activez MagicDNS, puis Activer HTTPS.
  2. Démarrez Collie, qui se publie sur le tailnet.
    collie start
  1. Définissez COLLIE_TRUSTED_USER avec votre identifiant tailnet dans .env, puis redémarrez.
    collie restart

Variante A contient le reste. Ne définissez pas COLLIE_SKIP_SERVE pour cette porte.

Ne jamais utilise tailscale funnel. Funnel expose le port sur l'internet public.

Cloudflare Tunnel

cloudflared se connecte à Cloudflare en sortie, aucun port ne s'ouvre donc sur votre routeur. Le nom d'hôte est public, une connexion doit donc le protéger.

  1. Connectez-vous, créez un tunnel et faites pointer un nom d'hôte dessus.
    cloudflared tunnel login
    cloudflared tunnel create collie
    cloudflared tunnel route dns collie collie.example.com
  1. Rédigez ~/.cloudflared/config.yml avec l'identifiant de tunnel affiché par create.
    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. Installez-le en tant que service avec cette configuration, pour qu'il survive à un redémarrage.
    sudo cloudflared --config ~/.cloudflared/config.yml service install
  1. Dans Cloudflare Zero Trust, ajoutez une application auto-hébergée pour collie.example.com, avec une règle qui n'autorise que votre adresse e-mail.

Un tunnel créé dans le tableau de bord Zero Trust fonctionne aussi : définissez le service de son nom d'hôte public sur http://127.0.0.1:8787. cloudflared transmet le nom d'hôte public sous forme de Host, ce qu'attend COLLIE_PUBLIC_HOSTS.

Avertissement. Sans l'étape 4, toute personne trouvant le nom d'hôte obtient un shell sur votre machine. Cloudflare Access constitue le verrou de la porte ; l'appairage décide ensuite quel appareil peut taper.
  1. Faites vérifier le verrouillage par Collie. Dans l'application Access, copiez le balise Application Audience (AUD). Ajoutez-le ainsi que le nom de votre équipe dans .env, puis redémarrez.
    COLLIE_ACCESS_TEAM=myteam          # from myteam.cloudflareaccess.com
    COLLIE_ACCESS_AUD=<the AUD tag>

    Chaque requête traversant le tunnel doit désormais comporter un jeton signé par Cloudflare pour cette application. Si l'application Access est supprimée, reçoit une règle de contournement ou n'a pas encore atteint tous les serveurs périphériques, Collie refuse la requête au lieu d'afficher les panneaux. La vérification de l'audience est essentielle : toutes les applications Access d'une équipe signent avec la même clé, un jeton destiné à une autre application est donc également refusé.

    Collie récupère les clés publiques de l'équipe au démarrage et toutes les heures. Tant que la première récupération n'a pas abouti, chaque requête distante reçoit 503. Si une récupération ultérieure échoue, Collie conserve les clés disponibles. Définissez les deux lignes ou aucune : en définir une seule entraîne le refus de chaque requête distante. Dans tous les cas, le journal du pont décrit le problème en une ligne ; consultez donc le journal après le redémarrage.

    Seul un processus local contourne le jeton : une requête vers 127.0.0.1 ou localhost ne comportant aucun en-tête Cf-* ni d'en-tête de redirection (X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto, Forwarded, X-Real-Ip). L'appairage reste obligatoire pour saisir du texte. /api/health et les liens crew ne requièrent pas non plus de jeton. collie doctor étant un appelant local de ce type, un rapport d'intégrité vert n'indique pas que les clés sont chargées ou qu'un jeton est validé. Ouvrez le nom d'hôte depuis le téléphone pour vérifier. ADR 0081 en explique les raisons.

    Conservez ces règles avec le verrouillage activé :

    - Une seule porte d'entrée à la fois. Quand Access est activé, une requête passant par tailscale serve, Caddy ou Traefik nécessite aussi un jeton, car ces outils ajoutent par défaut un en-tête de redirection. Une machine qui dessert à la fois un tunnel et un nom de tailnet (tailscale serve) perd l'accès par tailnet pour les navigateurs : ces requêtes ne portent aucun jeton Access et reçoivent 401. - Aucun second proxy entre cloudflared et Collie qui fait paraître les requêtes locales. Une machine qui réécrit Host en 127.0.0.1 ou localhost et supprime l'en-tête Cf-* ainsi que les en-têtes de redirection le fait volontairement. Un proxy_pass http://127.0.0.1:8787 nginx brut le fait par défaut : il envoie Host: 127.0.0.1:8787 et n'ajoute aucun en-tête de redirection à moins d'en configurer un. Ces requêtes ignorent le jeton. Pointez cloudflared directement vers Collie. - N'ajoutez pas de règles de contournement Access pour les chemins Collie, pas pour le manifeste, le service worker ou les icônes. Access ajoute le jeton à chaque requête qu'il laisse passer. Un chemin contourné ne porte aucun jeton, Collie répond donc 401.

Access redirige vers /cdn-cgi/access/ pour la connexion. Ne mettez pas en cache sw.js et index.html, comme l'indique Routage de /auth/ et mise en cache.

Nginx Proxy Manager

Un reverse proxy avec interface web et Let's Encrypt intégré. Il tourne dans Docker, et à l'intérieur d'un conteneur, 127.0.0.1 désigne le conteneur, pas votre hôte.

  1. Attribuez au conteneur le réseau de l'hôte, pour qu'il puisse joindre Collie sur le loopback.
    services:
      npm:
        image: jc21/nginx-proxy-manager:latest
        network_mode: host
        volumes:
          - ./data:/data
          - ./letsencrypt:/etc/letsencrypt
  1. Ouvrez l'interface sur le port 81 et ajoutez un Proxy Host : domaine collie.example.com, protocole http, transférer vers 127.0.0.1, port 8787.
  2. Dans l'onglet SSL, demandez un certificat Let's Encrypt et activez Forcer le SSL.

Laissez Mettre les assets en cache désactivé, afin que le service worker puisse se mettre à jour. Collie effectue ses sondages en HTTP simple, le commutateur Prise en charge des websockets n'a donc pas d'importance.

Pour un nom qui ne se résout qu'à l'intérieur de votre réseau, choisissez Utiliser un challenge DNS dans l'onglet SSL. Let's Encrypt ne peut pas joindre un hôte privé en HTTP.

Avertissement. host.docker.internal avec host-gateway ne fonctionne pas ici. Il joint l'adresse du pont Docker, et Collie écoute uniquement sur 127.0.0.1.

Si le proxy est ouvert sur Internet, ajoutez une Access List à l'hôte proxy, ou laissez les ports 80 et 443 fermés sur le routeur et joignez l'hôte via un VPN.

NetBird

NetBird est un maillage WireGuard, comme un tailnet. Il achemine le trafic, et un reverse proxy sur l'hôte ajoute le HTTPS.

  1. Connectez l'hôte et le téléphone au même réseau NetBird.
    netbird up
  1. Faites pointer un enregistrement DNS pour collie.example.com vers l'adresse NetBird de l'hôte (100.x.y.z).
  2. Lancez Caddy sur l'hôte, lié à cette adresse, avec un certificat obtenu par challenge DNS.
    collie.example.com {
        bind 100.x.y.z
        tls {
            dns cloudflare {env.CF_API_TOKEN}
        }
        reverse_proxy 127.0.0.1:8787
    }

Le challenge DNS prouve que vous possédez le domaine sans que Let's Encrypt ait besoin de joindre l'hôte ; le nom peut ainsi pointer vers une adresse privée. Caddy a besoin du plugin DNS pour votre fournisseur, par exemple xcaddy build --with github.com/caddy-dns/cloudflare.

Un nom de pair NetBird tel que host.netbird.cloud n'a pas de certificat publiquement approuvé ; utilisez donc un domaine que vous possédez. La même recette s'applique à ZeroTier ou à tout autre maillage.

Caddy

Caddy obtient et renouvelle son propre certificat. Sur un hôte joignable sur les ports 80 et 443, voici toute la configuration nécessaire :

collie.example.com {
    reverse_proxy 127.0.0.1:8787
}

Pour un nom privé, ajoutez le bloc tls { dns … } de NetBird ci-dessus. Un hôte public nécessite également une authentification en amont, comme dans Variante C.

---

Servir Collie sous un chemin

Un seul build de release dessert n'importe quel montage. Définissez le chemin dans l'instance .env (ou [serve] base_path dans config.toml), redémarrez, et le pont servira l'application, ses assets et /api/* sous ce chemin :

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

Accepté : /collie, collie/, /apps/collie/. Refusé, avec une ligne dans le journal et la racine utilisée à la place : un segment . ou .., des espaces, ?, #. collie doctor affiche le montage sur sa ligne front-door.

Avec la porte d'entrée par défaut, collie serve (et chaque collie start) publie tailscale serve --set-path=/collie/ au lieu de la racine, pour qu'une autre application puisse conserver / sur le même nœud. Tailscale retire le montage avant de relayer, et le pont accepte aussi une requête qui le conserve, si bien qu'un reverse proxy fonctionne dans les deux cas. Pour vérifier le vôtre :

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

Derrière votre propre proxy (Variante C), montez le pont au même chemin. Caddy : handle_path /collie/* { reverse_proxy 127.0.0.1:8787 }. Nginx : location /collie/ { proxy_pass http://127.0.0.1:8787/; }. Si vous définissez COLLIE_PUBLIC_URL, incluez le chemin : https://collie.example.com/collie/.

Un changement de montage équivaut à une nouvelle application sur le téléphone. La portée et l'identité de la PWA installée correspondent au chemin depuis lequel elle a été installée. Après avoir modifié COLLIE_BASE_PATH, ou être revenu à la racine, supprimez l'icône de l'écran d'accueil et ajoutez-la de nouveau depuis la nouvelle adresse ; l'ancienne continue de pointer vers un chemin que le pont ne sert plus. Le pont lui-même n'a besoin que d'un redémarrage, et collie serve déplace la porte de lui-même : il détruit le mappage nommé par son enregistrement avant de publier le nouveau.

---

Plusieurs instances de Collie sur un même hôte

Pour héberger des instances indépendantes par utilisateur sur un système partagé (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

Vérification de l'URL du binaire autonome : bin/collie url. Vérification de l'URL du plugin Herdr : herdr plugin action invoke url --plugin herdr.collie.

Prérequis :

  • Utilisez des utilisateurs Unix distincts et des instances séparées de herdr/Collie.
  • Définissez toujours COLLIE_TRUSTED_USER pour restreindre l'accès par port.
  • Les liaisons Tailscale initiales nécessitent une configuration opérateur : exécutez bin/collie serve (ou l'équivalent Herdr) avec les privilèges d'opérateur.

COLLIE_SERVE_PORT définit le port d'entrée d'une instance. COLLIE_INSTANCE crée une instance isolée avec sa propre unité de service et sa configuration sur le même hôte (Plusieurs instances de Collie sur un même hôte).

---

Plusieurs instances de Collie sur un même hôte

Utilisez cette configuration pour exécuter un second Collie distinct sur la même machine. Cela permet par exemple d'exécuter une version stable aux côtés d'une copie de travail, ou d'exécuter un second multiplexeur (tmux/zellij) à côté de celui géré par défaut par Herdr. Chaque instance dispose d'un port dédié, d'une configuration, d'un répertoire d'état et d'une unité de service. Cela diffère de Plusieurs instances de Collie sur un même hôte, qui attribue une instance par utilisateur Unix. Ici, un seul utilisateur exécute plusieurs instances.

Créez la seconde instance

Définissez COLLIE_INSTANCE=<name> pour nommer l'instance. Le nom doit correspondre à [a-z0-9-]{1,16}. Une instance nommée nécessite également un COLLIE_PORT explicite. Le CLI s'arrête avec une erreur si ce port est manquant ; il ne déduit aucune valeur par défaut.

Créez ~/.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 et COLLIE_STATE_DIR peuvent tous résider dans ce fichier .env. L'environnement fusionné résout l'instance. HERDR_PLUGIN_CONFIG_DIR remplace le répertoire dans lequel le CLI recherche la configuration.

Exécuter une unité de service par instance

collie start écrit l'unité lorsque l'environnement définit l'instance. L'opérateur ne l'écrit pas à la main. Sur macOS, il écrit à la place un fichier plist launchd sous ~/Library/LaunchAgents/. L'unité définit COLLIE_PORT, COLLIE_INSTANCE, COLLIE_PLUGIN_ROOT, HERDR_PLUGIN_CONFIG_DIR et EnvironmentFile=-<config dir>/.env. --instance <name> sur ExecStart existe uniquement pour que deux instances partageant un même binaire puissent distinguer leurs ponts (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

Le CLI conserve un fichier gestionnaire tailscale serve par instance, de sorte que l'exécution de unserve sur une instance ne supprime pas le mappage d'une autre.

Cibler une instance nommée depuis le CLI

Chaque verbe du CLI (pair, devices, url, qr, crew …, logs, push-test, …) résout son instance cible à partir de l'environnement du processus. Définissez COLLIE_INSTANCE avant d'invoquer le verbe :

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

Sans COLLIE_INSTANCE, le CLI interroge Herdr. Herdr ne suit que le plugin sans suffixe, la commande s'exécute donc sur la première instance. Définissez toujours COLLIE_INSTANCE explicitement lors de l'exécution de plusieurs instances sur un même hôte pour éviter de modifier la mauvaise instance.

La règle de refus

Si COLLIE_INSTANCE est défini mais que herdr.collie-<name>/.env est manquant, le CLI se termine avec une erreur. Il ne bascule pas sur la configuration d'une autre instance.

L'appairage s'effectue par instance

collie pair écrit un code d'association dans le répertoire d'état de cette instance. Le QR code affiché encode la propre URL de cette instance, puisque chaque instance possède son propre répertoire d'état et, via COLLIE_PUBLIC_URL ou son propre port, son propre point d'entrée. Ouvrez l'URL spécifique de cette instance sur le téléphone et saisissez le code sous Paramètres → Système → Appareils associés. Un code généré pour une instance ne peut pas associer un appareil à une autre instance (Associer un appareil).

---

L'accès de secours : chemin de basculement d'une équipe

Pour les déploiements crew. Configure un deputy préautorisé pour prendre le relais si le lead devient injoignable (ADR 0027, ADR 0028, CREW_PROTOCOL.md §18).

Paramètres du deputy et du lead :

CléValeur par défautDescription
COLLIE_STANDBY_PORT(non défini)Port pour l'écouteur standby. Laisser non défini désactive l'accès standby. Doit être identique sur le lead et le deputy.
COLLIE_STANDBY_HOST127.0.0.1Adresse de liaison (127.0.0.1 pour les proxys locaux, IP d'overlay pour les accès distants).
COLLIE_STANDBY_ARM_MSmax(30000, 2.5 × COLLIE_POLL_IDLE_MS)Durée de silence du lead requise avant armement.

Définissez COLLIE_STANDBY_PORT sur un port identique et inutilisé sur le lead et le deputy.

Le prérequis : un nom d'hôte, deux backends

Le lead et le deputy doivent être servis depuis la même origine pour partager l'enregistrement PWA et les identifiants des appareils. Un crew autonome sans ingress unifié récupère via bin/collie promote (ou Herdr : herdr plugin action invoke promote --plugin herdr.collie ; CREW_PROTOCOL §14.4).

Exemple de configuration 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

Réponses du health check : le lead renvoie 200 (non-200 lorsqu'il est déposé) ; le deputy renvoie 503 jusqu'à son armement, puis 200.

Configurez-le une seule fois, pendant que tout fonctionne correctement

Configuration autonome sur le lead :

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

Configuration Herdr sur le 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

Sur le deputy, configurez COLLIE_STANDBY_PORT=8788 et redémarrez. Assurez-vous que tous les pairs redémarrent pour charger le warrant du deputy. Vérifiez sur https://collie.example.com/standby.

⚠️ Le deputy doit être supervisé

Pendant le basculement, le deputy écrit l'état et quitte avec le code de sortie 75 (EX_TEMPFAIL) pour déclencher un redémarrage du processus (bridge/index.ts).

Assurez-vous que le superviseur redémarre sur les codes de sortie non nuls (systemd : Restart=always ou Restart=on-failure).

Jour de panne : le runbook

  1. Ouvrez https://collie.example.com.
  2. Lorsque le lead est défaillant, le proxy redirige vers /standby.
  3. Vérifiez le statut de veille du deputy.
  4. Sélectionnez Prendre le relais.
  5. Le deputy vérifie le consensus des pairs, applique le warrant et quitte avec 75.
  6. Le superviseur redémarre le processus ; la PWA se recharge en tant que nouveau lead.

Après récupération :

  • L'ancien lead se destitue lui-même lors de sa reconnexion (CREW_PROTOCOL.md §8.4).
  • Mettez à jour l'adresse de pair du nœud destitué : Standalone bin/collie crew set-address <member> <host:port> ou Herdr herdr plugin action invoke crew --plugin herdr.collie set-address <member> <host:port>.
  • Attribuez un nouveau deputy : Standalone bin/collie crew deputy <member> ou Herdr herdr plugin action invoke crew --plugin herdr.collie deputy <member>.
  • N'exécutez pas crew rotate tant que tous les membres ne se sont pas reconnectés.

Modifier cette page sur GitHub

The rest of the documentation