shunt는 업스트림 Claude, Codex/ChatGPT, Antigravity 계정을 프로비저닝하고 claude_oauth, chatgpt_oauth, antigravity_oauth 계정 풀의 상태를 볼 수 있는, 관리자 인증이 걸린 웹 화면을 노출할 수 있습니다. 이는 옵트인입니다: [server.admin]이 없으면 /admin* 라우트는 하나도 등록되지 않으며 shunt의 기본 HTTP 표면(surface)은 그대로입니다.
이 기능은 Anthropic 멀티 계정의 스토어와 선택 동작 위에 구축됩니다. 브라우저 폼에서는 갱신 가능한 Full OAuth 계정 또는 1년짜리 추론 전용 setup token 계정을 만들 수 있습니다. 기존 Claude Code credential 파일 가져오기는 CLI 전용입니다.
관리자 화면 활성화
선택적 테이블을 추가하고, 구성된 환경 변수를 통해 관리자 자격 증명을 하나 이상 제공합니다:
[server.admin] # 모든 키 선택; 기본값 표시됨
header = "x-shunt-admin-token"
tokens_env = "SHUNT_ADMIN_TOKENS"
session_ttl_secs = 3600
pending_ttl_secs = 600
# hide_observed = false # (기본값) 호스트 CLI/앱 로그인 스캔export SHUNT_ADMIN_TOKENS="ops:$(openssl rand -hex 32)"
shunt check
shunt run자격 증명은 SHUNT_CLIENT_TOKENS와 같은 쉼표 구분 name:token 형식을 쓰지만, 별도의 보안 경계입니다. [server.auth] 클라이언트 토큰을 관리자 토큰으로 재사용하지 마세요. [server.admin]이 있는데 세 자격 증명 소스 — 토큰 환경 변수(또는 tokens_file), [[server.admin.write_keys]], [[server.admin.read_keys]] — 가 모두 비어 있거나 형식이 잘못되면 시작은 닫힌 채로 실패(fail closed)합니다. name:token 쌍은 쓰기(write) 티어이며, read_keys 항목은 관리자 화면의 모든 GET을 통과하지만 모든 변경 작업에서는 거부됩니다. 대시보드에 로그인할 수는 있으며, 그 세션은 읽기 전용입니다.
모든 키와 기본값은 설정 레퍼런스를 참고하세요. 브라우저 라우트와 JSON 라우트는 엔드포인트 레퍼런스에 나열되어 있습니다.
대시보드를 공유하거나 원격으로 접속해서 게이트웨이 호스트의 프로바이더 로그인을 드러내지 않아야 할 — 아예 읽지도 않아야 할 — 때는 hide_observed = true로 설정하세요. 그러면 shunt는 호스트 CLI/앱 자격 증명 탐색을 완전히 건너뜁니다: GET /admin/api/observed는 여전히 관리자 자격 증명을 요구하지만 빈 목록을 반환하고, 대시보드는 이 엔드포인트를 요청하지 않습니다. Accounts and usage 표는 관리형 풀 계정의 사용량이 표시되는 곳이기도 하므로 페이지에 그대로 남으며, 관리형 풀 계정만 나열합니다. 이 변경은 설정 핫 리로드 시 적용됩니다. 이미 열려 있는 대시보드는 페이지를 불러올 때 이 플래그를 읽으므로, 페이지를 새로고침하면 새 값을 따릅니다. [server.admin] 테이블 자체를 추가하거나 제거하려면 여전히 재시작이 필요합니다.
브라우저에서 Claude 계정 프로비저닝
-
/admin을 열고 관리자 토큰으로 로그인합니다. -
소문자, 숫자, 하이픈만 포함하는 계정 이름을 입력합니다.
-
Full OAuth (refreshable)(대시보드 기본값) 또는 Setup token (1-year, inference-only)을 선택한 뒤 Start를 선택합니다.
-
표시된 인가 URL을 다른 탭에서 엽니다. 대상 Claude 계정으로 로그인하고 접근을 승인합니다.
-
결과로 나온
<code>#<state>값을 관리자 페이지에 붙여 넣고 Complete를 선택합니다. -
shunt가 계정을 저장합니다.
accounts목록이 비어 있는 provider는 재시작 없이 다음 요청에서 그 계정을 집어 듭니다. 그렇지 않으면 이름만 있는 항목을 추가하고 reload하세요:[[providers.anthropic.accounts]] name = "backup"
시작된 플로우는 pending_ttl_secs(기본 10분) 동안 유효해서 운영자가 인가 페이지를 열고 결과를 붙여 넣을 시간을 확보할 수 있습니다. 서버는 선택한 mode를 pending attempt와 함께 기록하므로 completion 요청에서 token 종류를 바꿀 수 없습니다. Start를 누른 순간부터 플로우가 끝날 때까지 대시보드는 로그인 방식 라디오 버튼을 비활성화하고 그 이유를 함께 보여 줍니다. pending attempt의 mode가 요청을 보내는 시점에 고정되기 때문입니다. Start를 다시 눌러도 잠금은 풀리지 않습니다. 플로우를 완료하거나 페이지를 새로고침해야 다시 선택할 수 있습니다. 또한 Start를 누르면 이미 열려 있던 인가 단계도 함께 닫히므로, 교체 요청을 보내는 순간 링크와 Complete 폼이 사라집니다. 그 교체 요청이 실패하면 — 거부되든, 아예 응답이 없든 — 실패 메시지가 이전 단계를 닫았다는 사실을 함께 알려 줍니다. 코드를 이미 제출한 단계는 그 교환 자체가 실패한 뒤에야 안내에 포함됩니다. 그전까지는 계정이 저장될 수도 있기 때문입니다. 그래서 교환이 느리면 이 문구는 실패 메시지가 뜨고 조금 뒤에 덧붙습니다. 서버에 무엇이 남는지는 경우에 따라 다릅니다. 거부된 교체 요청은 이전 pending 로그인을 건드리지 않으므로 남은 pending_ttl_secs 동안 그대로 유효하지만, 응답이 없던 요청은 서버에 도달했을 수 있고 같은 계정 이름을 썼다면 그 로그인을 이미 대체해 버렸습니다. 어느 쪽이든 페이지에서는 더 이상 닿을 수 없으므로, 플로우를 처음부터 다시 시작해야 합니다. Full OAuth는 access token과 refresh token을 저장하며 credential kind는 imported로 표시됩니다. setup-token mode는 kind가 setup_token인 정적 credential을 저장합니다. 완료 응답은 계정이 저장됐는지, 그리고 현재 provider 구성에서 그 계정이 실제로 사용되는지(live)를 알려 줍니다.
계정 스토어 변경은 요청마다 감지되므로, 스캔 모드 프로바이더는 계정이 추가되거나 제거된 뒤 재시작할 필요가 없습니다.
브라우저에서 Codex 계정 프로비저닝
- Add Codex account에서 소문자 계정 이름을 입력하고 Start Codex login을 선택합니다.
- 인가 URL을 열고 대상 ChatGPT 계정으로 로그인한 뒤 접근을 승인합니다.
- 브라우저는
http://localhost:1455/auth/callback으로 이동합니다. 로컬 페이지가 열리지 않는 것은 정상입니다. - 브라우저 주소창의 전체 URL을 복사해 관리자 페이지에 붙여 넣고 Complete Codex login을 선택합니다. JSON API에서는
<code>#<state>도 사용할 수 있습니다. - shunt가 code를 교환하고 갱신 가능한 Codex 자격 증명을 비공개 계정 파일에 저장합니다.
accounts가 비어 있는 chatgpt_oauth 프로바이더(기본 codex 포함)는 다음 요청에서 새 계정을 발견합니다. 명시적 계정 목록을 쓰는 경우 이름만 있는 항목을 추가해야 합니다. SHUNT_CODEX_TOKEN_URL은 로컬 통합 테스트용 token endpoint override이며, 운영에서는 설정하지 마세요.
브라우저에서 Antigravity 계정 프로비저닝
- Add Antigravity account에서 소문자 계정 이름을 입력하고 Start Antigravity login을 선택합니다.
- 인가 URL을 열고 대상 Google 계정으로 로그인한 뒤 접근을 승인합니다.
- 브라우저는
http://localhost:51121/oauth-callback으로 이동합니다. 로컬 페이지가 열리지 않는 것은 정상입니다. - 브라우저 주소창의 전체 URL을 복사해 관리자 페이지에 붙여 넣고 Complete Antigravity login을 선택합니다. JSON API에서는
<code>#<state>도 사용할 수 있습니다. - shunt가 code를 교환하고, Code Assist project를 해석한 뒤(실패해도 치명적이지 않으며 첫 요청에서 재시도됩니다), 비공개 계정 파일에 저장합니다.
accounts가 비어 있는 antigravity_oauth 프로바이더(기본 antigravity 포함)는 다음 요청에서 새 계정을 발견합니다. 명시적 계정 목록을 쓰는 경우 이름만 있는 항목을 추가해야 합니다. Codex와 달리 Antigravity 표에는 Refresh 버튼도 있어, 저장된 refresh 그랜트를 즉시 실행해 로그인이 아직 살아 있는지 보고합니다. SHUNT_ANTIGRAVITY_TOKEN_URL은 로컬 통합 테스트용 token endpoint override이며, 운영에서는 설정하지 마세요.
풀 상태 점검
대시보드는 auth = "claude_oauth", auth = "chatgpt_oauth", 또는 auth = "antigravity_oauth"로 구성된 프로바이더의 계정 스토어 메타데이터와 현재 상태를 보여 줍니다. Claude 행에는 업스트림에서 관측한 쿼터 사용률이 상태·쿨다운 상태와 함께 표시됩니다. 업스트림이 리셋 시각을 보고한 경우 각 윈도우 셀에 리셋까지 남은 시간(예: 3d 4h)도 함께 표시되며, 마우스를 올리면 절대 리셋 시각을 볼 수 있습니다. Codex 행에는 x-codex-* 응답 헤더로 보고된 5시간·7일 윈도우가 표시되며, 지원하지 않는 윈도우는 무시되고 Codex에는 대응 창이 없어 7d_oi는 —로 남습니다. Codex 사용량도 Claude 사용량과 똑같이 쿼터 인지 계정 선택에 반영됩니다(이슈 #195).
죽은 자격 증명과 Refresh 버튼
운영자 개입 없이는 어떤 재시도로도 되살릴 수 없는 계정은 cooling이 아니라 Needs re-login으로 표시됩니다. 주 표와 고급 풀 표 양쪽 모두입니다. 이 구분이 중요한 이유는, 쿨다운은 5분 뒤 저절로 만료되어 계정이 다시 시도되기 때문에 영구히 죽은 자격 증명이 쿼터 일시정지와 영원히 똑같아 보이기 때문입니다. 이 표식은 프로바이더가 refresh 그랜트를 종결적으로 거부했을 때, 갱신 자체가 불가능한 자격 증명(token_env 토큰이나 만료된 setup token)이 거부됐을 때, 그리고 갓 갱신한 bearer로 재시도한 요청이 여전히 거부됐을 때 세워집니다. 쿨다운과 독립적으로 보고되며 인메모리라, 재시작하면 해당 계정이 다시 실패할 때까지 초기화됩니다. 이 표식은 걸려 넘어진 프로바이더 항목이 아니라 자격 증명을 따라갑니다. 저장된 계정 하나를 여러 프로바이더 표에서 이름으로 활성화했다면 해당 행이 모두 함께 바뀝니다. 자체 credentials 파일이나 token_env를 가리키는 계정은 이름이 같아도 다른 자격 증명이므로, 거기서 난 실패는 다른 무엇도 표시하지 않습니다.
복구 방법은 계정을 제거하고 다시 추가하는 것입니다. imported 계정 행에는 Refresh 버튼도 있습니다. 그 계정의 refresh 그랜트를 즉시 실행해 로그인이 아직 살아 있는지 보고하므로, 트래픽을 기다리지 않고 재로그인이 성공했는지 확인할 때 유용합니다. 프로바이더 토큰 엔드포인트를 호출하므로 rate limit이 걸리고, 공용 자격 증명 스토어를 반드시 경유해 프록시의 자체 갱신과 경합하지 않으며, 일반 사용과 똑같이 저장된 refresh token을 회전시킵니다. 새 만료 시각만 반환하고 토큰 물질은 절대 반환하지 않습니다. setup_token 계정은 refresh 그랜트가 없으므로 프로바이더에 접속하지 않고 400으로 답합니다. 이 버튼은 write 등급 세션에서만 렌더링됩니다(아래 참고).
프로브 성공은 그랜트 실패가 표식을 세웠을 때만 표식을 지웁니다. 프로바이더가 이미 발급한 bearer를 거부해서 계정이 죽은 것으로 판정됐다면, 그랜트가 동작한다는 사실은 추론 서빙에 대해 아무것도 증명하지 못하므로 표식이 남고 응답도 그렇게 알려 줍니다.
계정 목록은 메타데이터만 노출합니다: 계정 이름, 자격 증명 종류(setup_token 또는 imported), 만료, UUID. 토큰 자체는 절대 반환하지 않습니다. shunt가 계정을 고를 때 쿼터 상태, 쿨다운, 모델 인지 주간 버킷을 어떻게 쓰는지는 Anthropic 멀티 계정을 참고하세요.
대시보드의 Claude 표는 이 만료 값을 자격 증명 종류에서 파생한 Status로 표시합니다. 원본 값은 약 8시간짜리 access token 만료라 종류에 따라 의미가 정반대이기 때문입니다. imported 계정은 refresh token을 갖고 있어 shunt가 필요할 때 갱신하므로 Auto-refreshes로 표시되며(원본 타임스탬프는 hover로 확인), 지난 날짜여도 정상입니다. setup_token 계정은 갱신 자체가 불가능하므로 1년 만료가 실제로 조치 대상입니다: 갱신 버퍼보다 많이 남아 있으면 Valid until + 날짜, 버퍼 안에 들어왔거나 날짜가 지났으면 danger 스타일의 Expired와 재로그인 안내가 표시됩니다. 버퍼는 라우팅이 적용하는 것과 같은 5분(EXPIRY_BUFFER)이라, 라우팅이 이미 refresh token 없음 경로로 실패시키는 구간을 대시보드가 사용 가능하다고 말하는 일은 없습니다. 각 행에는 Re-login 버튼도 있어서 위 추가 폼에 해당 계정의 이름과 로그인 방식을 채워 줍니다 — 기존 이름으로 일반 플로우를 완료하면 계정이 제자리에서 덮어써집니다. Codex 표에도 같은 Status 열이 있지만 조건 없이 적용됩니다. 그 store에는 갱신 불가 종류 자체가 없어서(저장 경로 두 곳 모두 refresh token을 요구합니다) shunt가 모든 Codex 계정을 갱신하며, 각 행은 Auto-refreshes로 표시되고 원본 타임스탬프는 마찬가지로 hover로 확인합니다. Codex 행에도 같은 Re-login 버튼이 있으며, 로그인 방식 선택만 없습니다 — 그 store로 들어가는 길은 ChatGPT OAuth 하나뿐입니다. Antigravity 표도 마찬가지로 조건 없이 Auto-refreshes를 표시하며, Re-login 버튼에 더해 위에서 설명한 Refresh 버튼도 갖습니다. 위쪽 사용량 표의 읽기 전용 행은 영향을 받지 않습니다. 그 자격 증명은 shunt가 아니라 소유한 provider 클라이언트가 갱신합니다. 여기의 행 동작은 모두 write 등급입니다. read_keys 로그인은 같은 표와 같은 Status 열을 보지만 행 동작 버튼과 계정 추가 폼은 렌더링되지 않습니다: 서버의 require_write가 그 변경을 거부하므로 대시보드가 애초에 제시하지 않습니다.
계정 metadata, 풀 상태, 프로비저닝, 또는 계정 제거에 API/curl로 접근하려면 구성된 헤더(기본 x-shunt-admin-token) 또는 x-api-key로 관리자 자격 증명을 보내고 — 두 슬롯 모두 관리자 라우트에서만 허용되며 추론 라우트에서는 허용되지 않습니다 — HTTP 엔드포인트에 문서화된 JSON route를 사용하세요. 헤더로 인증된 요청은 브라우저 세션을 쓰지 않으며 CSRF 검사에서 제외됩니다. 프로비저닝을 시작할 때 { "name": "backup", "mode": "oauth" } 또는 { "name": "backup", "mode": "setup_token" }을 보내세요. mode를 생략하면 API 하위 호환을 위해 setup_token이 기본값입니다. 완료 호출의 업스트림 토큰 교환에는 서버 측 30초 제한이 있습니다. 이를 넘기면 shunt는 502와 함께 Claude token exchange timed out(Codex는 Codex token exchange timed out)을 응답하고 아무것도 저장하지 않습니다. 타임아웃이 나도 대기 중인 로그인은 남고 그 시도도 5회 제한에 포함되지만, 인가 코드는 업스트림에서 이미 소비되었을 수 있습니다. 같은 코드를 다시 보내지 말고 플로를 새로 시작해 새 코드를 받으세요.
CLI와 SSH fallback
shunt 호스트에 브라우저로 접근할 수 없으면 CLI를 사용하세요. Full OAuth는 보통 브라우저를 열고 임시 127.0.0.1 callback으로 완료합니다. SSH 또는 headless 환경에서는 관리자 페이지와 같은 수동 붙여넣기 redirect를 강제하세요:
shunt login claude --name backup --mode oauth --manual대신 호스트의 현재 갱신 가능한 Claude Code 로그인을 가져오려면:
shunt login claude --name primary --mode import1년짜리 추론 전용 credential을 만들려면:
shunt login claude --name ci --mode setup-token--long-lived는 --mode setup-token의 deprecated alias입니다. 관리자 화면은 Claude Full OAuth/setup-token, Codex ChatGPT OAuth, Antigravity OAuth 프로비저닝을 지원합니다. 기존 credential 파일 import만 호스트 접근이 필요해 CLI 전용입니다.
보안
- 관리자 화면은 HTTPS 또는 WireGuard·Tailscale 같은 신뢰할 수 있는 터널 뒤에 두세요. shunt 자체는 평문 HTTP를 제공합니다; 원격에 노출할 때는 앞단에서 TLS를 종료하세요.
- 강한 관리자 토큰을 생성하고
[server.auth]클라이언트 자격 증명과 분리해 두세요. 관리자 접근은 업스트림 계정을 추가·제거할 수 있습니다. - 브라우저 로그인은 HttpOnly, SameSite=Strict 세션 쿠키를 만듭니다. 쿠키는 루프백 호스트를 제외하고 Secure이므로 로컬 HTTP 개발은 계속 동작합니다.
- 상태를 바꾸는 브라우저 요청은 세션별
x-csrf-token을 요구하고 동일 오리진 검사를 통과해야 합니다. API/curl 호출은 대신 관리자 헤더로 인증하며 암묵적(ambient) 쿠키 권한을 갖지 않습니다. - 프로비저닝 완료에는 레이트 리밋이 적용됩니다. shunt는 토큰 자체를 절대 로깅하거나 반환하지 않으며, 계정 추가와 제거는 계정 이름으로 감사 로그에 남습니다.
[server.admin]이 없으면 그 라우트들은 존재하지 않습니다. 이는 사용하지 않는 대시보드를 인증 없이 두는 것보다 강력합니다: 명시적으로 활성화하지 않는 한 관리자 화면 자체가 없습니다.