04/Documentation
Bereitstellungsvarianten B–E
Andere Zugänge als der Standard: ein Identity-Aware-Proxy, ein Reverse-Proxy ohne Tailscale, ein externer Ingress, mehrere Collies auf einem Host (einer pro Benutzer oder mehrere Instanzen für einen Benutzer) und der Standby-Zugang eines packs
Die Bridge bindet 127.0.0.1. Bereitstellungen unterscheiden sich durch Ingress und Identitätsprüfung. Variante A (einfaches tailscale serve) befindet sich in der README. Die Sicherheitsanforderungen in docs/security.md gelten für alle Varianten.
Einzelne Geräte können auch ohne Proxy über Koppeln autorisiert werden.
---
Variante B: Identity-Aware-Proxy + gerätespezifische Autorisierung
Collie liest eine opake Geräte-ID aus COLLIE_DEVICE_HEADER und prüft COLLIE_DEVICE_ALLOWLIST. Freigegebene IDs erhalten Schreibzugriff; fehlende oder nicht gelistete IDs erhalten Nur-Lese-Zugriff (Lesevorgänge, Snapshots und Sitzungslisten bleiben offen; Terminal-Eingaben, Uploads und Bereichsaktionen werden blockiert).
Collie-Konfiguration (.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=1Proxy-Anforderungen:
- Gerät authentifizieren (mTLS, SSO, Forward-Auth).
- Geräte-Header überschreiben bei jeder Upstream-Anfrage, um clientseitiges Spoofing zu verhindern.
- Proxy auf Loopback (
127.0.0.1:$COLLIE_PORT). - Öffentlichen
Hostunverändert weiterleiten oder tragen Sie den öffentlichen Origin inCOLLIE_ALLOWED_ORIGINSein, um die Same-Origin-Prüfung zu bestehen.
Beispielhafte Nginx-Konfiguration:
location / {
proxy_set_header X-Device-Id $device_id;
proxy_set_header Host $host;
proxy_pass http://127.0.0.1:8787;
}Header-Injektion und Überschreiben von einem externen Gerät aus überprüfen:
$ 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}Gibt die zweite Prüfung "device":"my-phone" zurück, hängt der Proxy den Header an, anstatt ihn zu überschreiben.
Betriebshinweise:
- Direkter Loopback-Zugriff (
http://127.0.0.1:$COLLIE_PORT) sendet keinen Header und ist schreibgeschützt. - Um Befehle lokal über curl auszuführen, übergeben Sie den Header explizit an Loopback:
curl -H 'X-Device-Id: my-laptop' http://127.0.0.1:$COLLIE_PORT/api/... - Entziehen Sie den Zugriff, indem Sie die ID aus
COLLIE_DEVICE_ALLOWLISTentfernen und neu starten (Standalone:bin/collie restart; Herdr:herdr plugin action invoke restart --plugin herdr.collie). - Aktivieren Sie
COLLIE_DEVICE_HEADERnicht bei einfachemtailscale serve; Header werden dabei nicht überschrieben.
---
Variante C: Reverse-Proxy als einziger Eingang (kein Tailscale)
Verwenden Sie dies beim Betrieb außerhalb von Tailscale oder hinter einem dedizierten TLS/SSO-Proxy. COLLIE_SKIP_SERVE=1 verhindert, dass Collie tailscale serve verwaltet.
Die vier Proxy-Anforderungen aus Variante B gelten.
Beispielkonfiguration für Caddy:
collie.example.com {
reverse_proxy 127.0.0.1:8787 {
header_up X-Device-Id {your_device_id}
header_up Host {host}
}
}Collie-Konfiguration (.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.comCOLLIE_TRUSTED_USER hat ohne Tailscale keine Wirkung. Header pro Gerät oder die eigene Authentifizierung des Proxys dienen als Zugriffskontrolle.
Routing für /auth/ und Caching
- Leiten Sie
/sw.jsundindex.htmlmit dem OriginCache-Control(no-cache) weiter. Blockieren Sie statische Assets für nicht authentifizierte Clients nicht, da Service-Worker sonst nicht aktualisiert werden können. - Leiten Sie Authentifizierungsabläufe unter
/auth/*weiter (oder/cdn-cgi/access/für Cloudflare Access). Service-Worker umgehen das Caching für/auth/*. - Forward-Auth-Weiterleitungen bei API-Anfragen werden als 401-Fehler behandelt, um die Login-Benutzeroberfläche anzuzeigen. Authentik-Setups sollten
/auth/an/outpost.goauthentik.io/startweiterleiten.
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 { ... }
}
}Überprüfen Sie den Endpunkt und die Caching-Regeln:
$ 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: Externer Identity-Proxy über das Tailnet
Verwenden Sie dies, wenn Sie Datenverkehr über einen zentralen Tailscale-Ingress-Knoten leiten.
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 CollieDie Anforderungen aus Variante B gelten, mit der Ausnahme, dass der Proxy auf den Tailscale-HTTP-Endpunkt des Hosts statt auf Loopback zielt.
Tailscale-ACLs (erforderlich)
Da tailscale serve Client-Header unverändert weiterleitet, müssen Tailscale-ACLs den direkten Portzugriff ausschließlich auf den Ingress-Knoten beschränken.
Tailscale / Headscale ≥ 0.29:
"grants": [
{ "src": ["tag:ingress"], "dst": ["tag:agent-host"], "ip": ["tcp:8787"] },
]Headscale ≤ 0.28:
acls:
- action: accept
src: ["ingress-node"]
dst: ["agent-host:8787"]
- action: accept
src: ["my-phone", "my-laptop"]
dst: ["agent-host:1-8786", "agent-host:8788-65535"]Collie-Konfiguration (.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 entspricht der Identität des Ingress-Knotens, nicht der des Endbenutzers dahinter.
Überprüfung:
Von einem Tailnet-Peer, der kein Ingress-Knoten ist:
$ 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 outAuf dem Agent-Host:
$ curl -s http://127.0.0.1:8787/api/snapshot | jq -c .device
{"enforced":true,"device":null,"authorized":false}---
Variante E: Jedes andere Mesh oder Tunnel (NetBird, ZeroTier, Cloudflare Tunnel)
Richten Sie den Tunnel/Proxy auf 127.0.0.1:$COLLIE_PORT aus.
Collie-Konfiguration (.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.comRegeln:
COLLIE_TRUSTED_USERist ohne Tailscale inaktiv. Verwenden SieCOLLIE_DEVICE_HEADERoder die Authentifizierung des Tunnels.- Verwenden Sie einen statischen Hostnamen, damit das PWA-Caching und
COLLIE_PUBLIC_HOSTSgültig bleiben.
---
Mehrere Collies auf einem Host
Um unabhängige Instanzen pro Benutzer auf einem gemeinsam genutzten System zu hosten (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 agentsURL-Prüfung für eigenständige Binärdatei: bin/collie url. URL-Prüfung für Herdr-Plugin: herdr plugin action invoke url --plugin herdr.collie.
Voraussetzungen:
- Verwenden Sie getrennte Unix-Benutzer und separate Instanzen von
herdr/Collie. - Setzen Sie
COLLIE_TRUSTED_USERimmer, um den Zugriff pro Port einzuschränken. - Initiale Tailscale-Bindungen erfordern eine Konfiguration durch den Operator: Führen Sie
bin/collie serve(oder das Herdr-Äquivalent) mit Operator-Rechten aus.
COLLIE_SERVE_PORT legt den Eingangsport für eine Instanz fest. COLLIE_INSTANCE erstellt eine isolierte Instanz mit eigener Service-Unit und Konfiguration auf demselben Host (Mehrere Collie-Instanzen auf einem Host).
---
Mehrere Collie-Instanzen auf einem Host
Verwenden Sie dieses Setup, um ein zweites, separates Collie auf derselben Maschine auszuführen. Beispiele sind der Betrieb einer stabilen Version neben einer Arbeitskopie oder die Ausführung eines zweiten Multiplexers (tmux/zellij) neben dem von Herdr verwalteten Standard. Jede Instanz erhält einen dedizierten Port, eine Konfiguration, ein Zustandsverzeichnis und eine Service-Unit. Dies unterscheidet sich von Mehrere Collies auf einem Host, wo eine Instanz pro Unix-Benutzer zugewiesen wird. Hier führt ein Benutzer mehrere Instanzen aus.
Die zweite Instanz erstellen
Setzen Sie COLLIE_INSTANCE=<name>, um die Instanz zu benennen. Der Name muss [a-z0-9-]{1,16} entsprechen. Eine benannte Instanz erfordert zudem einen expliziten COLLIE_PORT. Das CLI bricht mit einem Fehler ab, wenn dieser Port fehlt; es leitet keinen Standardwert ab.
Erstellen Sie ~/.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 und COLLIE_STATE_DIR können sich alle in dieser .env-Datei befinden. Die zusammengeführte Umgebung löst die Instanz auf. HERDR_PLUGIN_CONFIG_DIR überschreibt das Verzeichnis, in dem das CLI nach der Konfiguration sucht.
Eine Service-Unit pro Instanz ausführen
collie start schreibt die Unit, wenn die Umgebung die Instanz festlegt. Der Operator schreibt sie nicht von Hand. Unter macOS schreibt es stattdessen eine launchd-plist unter ~/Library/LaunchAgents/. Die Unit setzt COLLIE_PORT, COLLIE_INSTANCE, COLLIE_PLUGIN_ROOT, HERDR_PLUGIN_CONFIG_DIR und EnvironmentFile=-<config dir>/.env. --instance <name> auf ExecStart existiert einzig, damit zwei Instanzen, die sich ein Binary teilen, ihre Bridges unterscheiden können (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>/.envDas CLI verwaltet eine instanzspezifische tailscale serve-Handler-Datei, sodass die Ausführung von unserve auf einer Instanz das Mapping für eine andere nicht löscht.
Eine benannte Instanz über das CLI ansteuern
Jedes CLI-Verb (pair, devices, url, qr, pack …, logs, push-test, …) ermittelt seine Zielinstanz aus der Prozessumgebung. Setzen Sie COLLIE_INSTANCE, bevor Sie das Verb aufrufen:
COLLIE_INSTANCE=next bin/collie pair
COLLIE_INSTANCE=next bin/collie devices listOhne COLLIE_INSTANCE fragt das CLI Herdr ab. Herdr erfasst nur das Plugin ohne Suffix, daher wird der Befehl für die erste Instanz ausgeführt. Setzen Sie COLLIE_INSTANCE immer explizit, wenn Sie mehrere Instanzen auf einem Host ausführen, um Änderungen an der falschen Instanz zu vermeiden.
Die Verweigerungsregel
Wenn COLLIE_INSTANCE gesetzt ist, aber herdr.collie-<name>/.env fehlt, bricht das CLI mit einem Fehler ab. Es greift nicht auf die Konfiguration einer anderen Instanz zurück.
Kopplung erfolgt pro Instanz
collie pair schreibt einen Kopplungscode in das Zustandsverzeichnis dieser Instanz. Der ausgegebene QR-Code enthält die eigene URL dieser Instanz, da jede Instanz ihr eigenes Zustandsverzeichnis und über COLLIE_PUBLIC_URL oder ihren eigenen Port ihren eigenen Zugang besitzt. Öffnen Sie die spezifische URL dieser Instanz auf dem Telefon und geben Sie den Code unter Einstellungen → Gekoppelte Geräte ein. Ein für eine Instanz erstellter Code kann kein Gerät mit einer anderen Instanz koppeln (Ein Gerät koppeln).
---
Der Standby-Zugang: Failover-Pfad eines Packs
Für pack-Bereitstellungen. Konfiguriert einen vorab autorisierten deputy zur Übernahme, falls das lead unerreichbar wird (ADR 0027, ADR 0028, PACK_PROTOCOL.md §18).
Einstellungen für deputy und lead:
| Schlüssel | Standard | Funktion |
|---|---|---|
COLLIE_STANDBY_PORT | (nicht gesetzt) | Port für den Standby-Listener. Nicht gesetzt deaktiviert die Standby-Tür. Muss auf Lead und deputy übereinstimmen. |
COLLIE_STANDBY_HOST | 127.0.0.1 | Bind-Adresse (127.0.0.1 für lokale Proxys, Overlay-IP für Remote). |
COLLIE_STANDBY_ARM_MS | max(30000, 2.5 × COLLIE_POLL_IDLE_MS) | Erforderliche Dauer der Lead-Inaktivität vor dem Scharfschalten. |
Setzen Sie COLLIE_STANDBY_PORT sowohl auf dem Lead als auch auf dem deputy auf einen identischen, ungenutzten Port.
Die Voraussetzung: ein Hostname, zwei Backends
Lead und deputy müssen vom selben Origin ausgeliefert werden, um die PWA-Registrierung und die Geräte-Anmeldedaten zu teilen. Standalone-Packs ohne vereinheitlichten Ingress stellen über bin/collie promote wieder her (oder Herdr: herdr plugin action invoke promote --plugin herdr.collie; PACK_PROTOCOL §14.4).
Beispielkonfiguration für 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: 2sAntworten der Zustandsprüfung: Lead gibt 200 zurück (ungleich 200, wenn abgesetzt); deputy gibt 503 zurück, bis er scharfgeschaltet ist, danach 200.
Einmalig einrichten, solange das System fehlerfrei läuft
Standalone-Einrichtung auf dem Lead:
bin/collie pair
bin/collie pack deputy nas
bin/collie pack statusHerdr-Einrichtung auf dem 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 statusKonfigurieren Sie auf dem deputy COLLIE_STANDBY_PORT=8788 und starten Sie neu. Stellen Sie sicher, dass alle Peers neu starten, um den deputy warrant zu laden. Überprüfen Sie dies unter https://collie.example.com/standby.
⚠️ Der deputy muss überwacht werden
Bei einer Übernahme schreibt der deputy den Status und beendet sich mit dem Statuscode 75 (EX_TEMPFAIL), um einen Prozessneustart (bridge/index.ts) auszulösen.
Stellen Sie sicher, dass der Supervisor bei Beendigungscodes ungleich null neu startet (systemd: Restart=always oder Restart=on-failure).
Der Ernstfall: das Betriebshandbuch
- Öffnen Sie
https://collie.example.com. - Wenn der Lead fehlerhaft ist, leitet der Proxy zu
/standbyweiter. - Prüfen Sie den Standby-Status des deputy.
- Wählen Sie Übernehmen aus.
- Der deputy überprüft den Peer-Konsens, wendet den warrant an und beendet sich mit
75. - Der Supervisor startet den Prozess neu; die PWA lädt sich als neuer Lead neu.
Nach der Wiederherstellung:
- Aktualisieren Sie die Peer-Adresse des abgesetzten Knotens: Standalone
bin/collie pack set-address <member> <host:port>oder Herdrherdr plugin action invoke pack --plugin herdr.collie set-address <member> <host:port>. - Weisen Sie einen neuen deputy zu: Standalone
bin/collie pack deputy <member>oder Herdrherdr plugin action invoke pack --plugin herdr.collie deputy <member>. - Führen Sie
pack rotateerst aus, wenn alle Mitglieder die Verbindung wiederhergestellt haben.
Diese Seite auf GitHub bearbeiten