본문 바로가기
ColliePWA

04/Documentation

배포 변형 B–E

기본 구성 외의 프론트 도어: Identity-Aware Proxy, Tailscale이 없는 역방향 프록시, 호스트 외부 인그레스, 단일 호스트 내 다중 Collie(사용자당 하나 또는 단일 사용자 대상 여러 인스턴스), pack의 대기 도어

브릿지는 127.0.0.1에 바인딩됩니다. 배포 방식은 인그레스 및 신원 확인 방식에 따라 다릅니다. 변형 A(일반 tailscale serve)는 README에 있습니다. docs/security.md의 보안 요구사항은 모든 형태에 적용됩니다.

프록시 없이 페어링을 통해 개별 기기를 인가할 수도 있습니다.

---

변형 B: 신원 인식 프록시 + 기기별 인가

Collie는 COLLIE_DEVICE_HEADER에서 불투명한 기기 ID를 읽고 COLLIE_DEVICE_ALLOWLIST을 확인합니다. 허용 목록에 있는 ID는 쓰기 권한을 받습니다. 누락되었거나 목록에 없는 ID는 읽기 전용 권한을 받습니다(읽기, 스냅샷, 세션 목록은 열려 있고, 터미널 입력, 업로드, 창 작업은 차단됩니다).

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

프록시 요구사항:

  1. 기기 인증(mTLS, SSO, forward-auth).
  2. 클라이언트 스푸핑을 방지하기 위해 모든 업스트림 요청에 기기 헤더 재정의을 적용합니다.
  3. 루프백으로 프록시 전달(127.0.0.1:$COLLIE_PORT).
  4. 동일 출처 확인을 통과하려면 공개 Host을 변경 없이 전달을 전달하거나 COLLIE_ALLOWED_ORIGINS에 공개 오리진을 등록합니다.

Nginx 설정 예시:

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

외부 기기에서 헤더 주입 및 재정의를 확인합니다.

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

두 번째 확인에서 "device":"my-phone"이 반환되면 프록시가 헤더를 덮어쓰지 않고 추가하고 있는 것입니다.

운영 참고 사항:

  • 직접 루프백 접근(http://127.0.0.1:$COLLIE_PORT)은 헤더를 보내지 않으며 읽기 전용입니다.
  • curl을 통해 로컬에서 명령을 실행하려면 루프백에 헤더를 명시적으로 전달합니다: curl -H 'X-Device-Id: my-laptop' http://127.0.0.1:$COLLIE_PORT/api/...
  • COLLIE_DEVICE_ALLOWLIST에서 ID를 제거하고 다시 시작하여 접근 권한을 취소합니다(Standalone: bin/collie restart, Herdr: herdr plugin action invoke restart --plugin herdr.collie).
  • 일반 tailscale serve에서는 COLLIE_DEVICE_HEADER을 활성화하지 마십시오. 헤더를 재정의하지 않습니다.
  • 프록시가 다른 호스트에서 실행 중인 경우 변형 D을(를) 참조하십시오.

---

변형 C: 유일한 프론트 도어 역할을 하는 리버스 프록시(Tailscale 미사용)

Tailscale 외부에서 실행하거나 전용 TLS/SSO 프록시 뒤에서 실행할 때 사용합니다. COLLIE_SKIP_SERVE=1은(는) Collie가 tailscale serve을(를) 관리하지 못하도록 합니다.

변형 B의 네 가지 프록시 요구사항이 적용됩니다.

Caddy 구성 예시:

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

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

Tailscale이 없으면 COLLIE_TRUSTED_USER은(는) 적용되지 않습니다. 기기별 헤더 또는 프록시 자체 인증이 접근 게이트 역할을 합니다.

/auth/ 라우팅 및 캐싱

  • 오리진 Cache-Control(no-cache)과(와) 함께 /sw.jsindex.html을(를) 전달하십시오. 인증되지 않은 클라이언트에 대해 정적 에셋을 차단하지 마십시오. 그렇지 않으면 서비스 워커가 업데이트되지 않습니다.
  • /auth/*(또는 Cloudflare Access의 경우 /cdn-cgi/access/) 아래로 인증 흐름을 라우팅하십시오. 서비스 워커는 /auth/*에 대한 캐싱을 우회합니다.
  • API 요청의 포워드 인증 리디렉션은 로그인 UI를 노출하기 위해 401로 처리됩니다. Authentik 설정에서는 /auth/을(를) /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 { ... }
    }
}

엔드포인트 및 캐싱 규칙 확인:

$ 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

---

변형 D: tailnet 기반의 외부 호스트 신원 프록시

중앙 집중식 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

프록시가 루프백 대신 호스트의 Tailscale HTTP 엔드포인트를 대상으로 한다는 점을 제외하고 변형 B의 요구사항이 적용됩니다.

Tailscale ACL(필수)

tailscale serve은(는) 클라이언트 헤더를 수정 없이 전달하므로, Tailscale ACL은 직접 포트 접근을 인그레스 노드로만 제한해야 합니다.

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 설정(.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은(는) 인그레스 노드 뒤의 최종 사용자가 아닌 인그레스 노드의 ID와 일치합니다.

검증:

인그레스가 아닌 tailnet 피어에서:

$ 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

에이전트 호스트에서:

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

---

변형 E: 기타 모든 메시 또는 터널(NetBird, ZeroTier, Cloudflare Tunnel)

터널/프록시가 127.0.0.1:$COLLIE_PORT을(를) 가리키도록 설정하십시오.

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

규칙:

  1. 변형 B 프록시 규칙을 적용하십시오.
  2. Tailscale이 없으면 COLLIE_TRUSTED_USER은(는) 비활성화됩니다. COLLIE_DEVICE_HEADER 또는 터널 인증을 사용하십시오.
  3. PWA 캐싱 및 COLLIE_PUBLIC_HOSTS이(가) 유효하게 유지되도록 고정 호스트 이름을 사용하십시오.

---

단일 호스트의 여러 Collie 인스턴스

공유 시스템(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

독립형 바이너리 URL 확인: bin/collie url. Herdr 플러그인 URL 확인: herdr plugin action invoke url --plugin herdr.collie.

요구 사항:

  • 서로 다른 Unix 사용자와 분리된 herdr/Collie 인스턴스를 실행하십시오.
  • 포트별 접근을 제한하려면 항상 COLLIE_TRUSTED_USER을 설정하십시오.
  • 초기 Tailscale 바인딩에는 운영자 설정이 필요합니다. 운영자 권한으로 bin/collie serve(또는 이에 상응하는 Herdr 명령)을 실행하십시오.

COLLIE_SERVE_PORT은 인스턴스의 진입 포트를 설정합니다. COLLIE_INSTANCE은 동일한 호스트에 자체 서비스 유닛과 구성을 갖춘 격리된 인스턴스를 생성합니다(단일 호스트에서의 다중 Collie 인스턴스).

---

단일 호스트에서의 다중 Collie 인스턴스

동일한 머신에서 별도의 두 번째 Collie를 실행하려면 이 설정을 사용하십시오. 예로는 안정 릴리스와 작업 복사본을 함께 실행하거나, Herdr에서 관리하는 기본 멀티플렉서 옆에 두 번째 멀티플렉서(tmux/zellij)를 실행하는 경우가 있습니다. 각 인스턴스는 전용 포트, 구성, 상태 디렉터리 및 서비스 유닛을 가집니다. 이는 Unix 사용자당 하나의 인스턴스를 할당하는 단일 호스트의 여러 Collie 인스턴스와 다릅니다. 여기서는 한 사용자가 여러 인스턴스를 실행합니다.

두 번째 인스턴스 생성

인스턴스 이름을 지정하려면 COLLIE_INSTANCE=<name>을 설정하십시오. 이름은 [a-z0-9-]{1,16}과 일치해야 합니다. 이름을 지정한 인스턴스에는 명시적인 COLLIE_PORT도 필요합니다. 이 포트가 없으면 CLI가 오류와 함께 종료되며, 기본값을 유추하지 않습니다.

~/.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, COLLIE_STATE_DIR는 모두 해당 .env 파일에 위치할 수 있습니다. 병합된 환경이 인스턴스를 확인합니다. HERDR_PLUGIN_CONFIG_DIR는 CLI가 구성을 검색하는 디렉터리를 재정의합니다.

인스턴스별 서비스 유닛 실행

환경에서 인스턴스를 설정하면 collie start이 유닛을 작성합니다. 운영자가 직접 작성하지 않습니다. macOS에서는 대신 ~/Library/LaunchAgents/ 아래에 launchd plist를 작성합니다. 유닛은 COLLIE_PORT, COLLIE_INSTANCE, COLLIE_PLUGIN_ROOT, HERDR_PLUGIN_CONFIG_DIR, EnvironmentFile=-<config dir>/.env을 설정합니다. ExecStart--instance <name>은 하나의 바이너리를 공유하는 두 인스턴스가 자체 브리지를 구분할 수 있도록 하기 위해서만 존재합니다(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

CLI는 인스턴스별 tailscale serve 핸들러 파일을 유지하므로 한 인스턴스에서 unserve을 실행해도 다른 인스턴스의 매핑이 삭제되지 않습니다.

CLI에서 이름이 지정된 인스턴스 지정

모든 CLI 동사(pair, devices, url, qr, pack …, logs, push-test, …)는 프로세스 환경에서 대상 인스턴스를 확인합니다. 동사를 호출하기 전에 COLLIE_INSTANCE을 설정하십시오.

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

COLLIE_INSTANCE이 없으면 CLI는 Herdr를 쿼리합니다. Herdr는 접미사가 없는 플러그인만 추적하므로 명령이 첫 번째 인스턴스에 대해 실행됩니다. 잘못된 인스턴스를 수정하지 않도록 단일 호스트에서 여러 인스턴스를 실행할 때는 항상 COLLIE_INSTANCE을 명시적으로 설정하십시오.

거부 규칙

COLLIE_INSTANCE이 설정되어 있지만 herdr.collie-<name>/.env이 없으면 CLI가 오류와 함께 종료됩니다. 다른 인스턴스의 구성으로 대체되지 않습니다.

페어링은 인스턴스별로 수행됩니다

collie pair은(는) 해당 인스턴스의 상태 디렉터리에 페어링 코드를 작성합니다. 각 인스턴스는 고유한 상태 디렉터리를 가지며 COLLIE_PUBLIC_URL 또는 자체 포트를 통해 고유한 진입점을 가지므로, 출력되는 QR에는 해당 인스턴스의 자체 URL이 인코딩되어 있습니다. 휴대폰에서 해당 인스턴스의 고유 URL을 열고 Settings → Paired devices에서 코드를 입력합니다. 한 인스턴스용으로 생성된 코드는 다른 인스턴스에 기기를 페어링하는 데 사용할 수 없습니다(기기 페어링).

---

스탠바이 도어: pack의 장애 조치 경로

pack 배포용. lead에 연결할 수 없게 될 때 인계받을 사전 승인된 deputy를 구성합니다(ADR 0027, ADR 0028, PACK_PROTOCOL.md §18).

deputy 및 lead 설정:

기본값기능
COLLIE_STANDBY_PORT(설정되지 않음)대기 리스너용 포트입니다. 설정하지 않으면 대기 도어가 비활성화됩니다. lead와 deputy에서 일치해야 합니다.
COLLIE_STANDBY_HOST127.0.0.1바인드 주소입니다(로컬 프록시의 경우 127.0.0.1, 원격의 경우 오버레이 IP).
COLLIE_STANDBY_ARM_MSmax(30000, 2.5 × COLLIE_POLL_IDLE_MS)준비 태세(arming)로 전환하기 전에 필요한 lead 무응답 지속 시간입니다.

lead와 deputy 양쪽에서 COLLIE_STANDBY_PORT 값을 사용되지 않는 동일한 포트로 설정하십시오.

전제 조건: 하나의 호스트 이름, 두 개의 백엔드

PWA 등록 정보와 기기 자격 증명을 공유하려면 lead와 deputy를 동일한 오리진에서 제공해야 합니다. 통합 인그레스가 없는 Standalone pack은 bin/collie promote(또는 Herdr: herdr plugin action invoke promote --plugin herdr.collie; PACK_PROTOCOL §14.4)을 통해 복구합니다.

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

헬스 체크 응답: lead는 200을 반환합니다(강등 시 200이 아님). deputy는 준비 태세가 될 때까지 503를 반환하고, 그 후 200을 반환합니다.

모든 것이 정상일 때 한 번 설정하십시오

lead의 Standalone 설정:

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

lead의 Herdr 설정:

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

deputy에서 COLLIE_STANDBY_PORT=8788을 구성하고 재시작하십시오. 모든 피어가 재시작하여 deputy warrant를 로드하도록 하십시오. https://collie.example.com/standby에서 확인하십시오.

⚠️ deputy는 감독(supervised)되어야 합니다

인계 과정에서 deputy는 상태를 기록하고 프로세스 재시작(bridge/index.ts)을 트리거하기 위해 종료 코드 75(EX_TEMPFAIL)으로 종료됩니다.

감독자(supervisor)가 0이 아닌 종료 코드에서 재시작하도록 설정하십시오(systemd: Restart=always 또는 Restart=on-failure).

장애 발생 시 런북

  1. https://collie.example.com을 엽니다.
  2. lead가 비정상 상태일 때 프록시는 /standby으로 라우팅합니다.
  3. deputy 대기 상태를 검토하십시오.
  4. 인계 실행을 선택하십시오.
  5. deputy가 피어 합의를 확인하고, warrant를 적용한 뒤 75으로 종료됩니다.
  6. 감독자가 프로세스를 재시작하고, PWA가 새 lead로 다시 로드됩니다.

복구 후 작업:

  • 이전 lead는 다시 연결될 때 스스로를 강등합니다(PACK_PROTOCOL.md §8.4).
  • 강등된 노드의 피어 주소를 업데이트하십시오: Standalone bin/collie pack set-address <member> <host:port> 또는 Herdr herdr plugin action invoke pack --plugin herdr.collie set-address <member> <host:port>.
  • 새 deputy를 지정하십시오: Standalone bin/collie pack deputy <member> 또는 Herdr herdr plugin action invoke pack --plugin herdr.collie deputy <member>.
  • 모든 구성원이 다시 연결될 때까지 pack rotate을 실행하지 마십시오.

GitHub에서 이 페이지 수정하기