Add complete OAuth token refresh and sync solution
- Setup wizard with auto-detection of OpenClaw paths and Claude CLI - Token sync watcher (inotifywait) for real-time credential updates - Auto-refresh trigger timer that runs Claude CLI every 30 min - Supports Claude CLI in Docker container or on host - Temporary ANTHROPIC_BASE_URL override for container environments - Anthropic model configuration for OpenClaw - Auth profile management (fixes key vs access field) - Systemd services and timers for both sync and trigger - Comprehensive documentation and troubleshooting guides - Re-authentication notification system Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
118
docs/ARCHITECTURE.md
Normal file
118
docs/ARCHITECTURE.md
Normal file
@@ -0,0 +1,118 @@
|
||||
# Architecture
|
||||
|
||||
## Token Flow Diagram
|
||||
|
||||
```
|
||||
+--------------------+ auto-refresh +------------------------------------------+
|
||||
| Claude Code CLI | ================> | .credentials.json |
|
||||
| (inside | (every ~8 hours, | { |
|
||||
| claude-proxy | built-in to CLI) | "claudeAiOauth": { |
|
||||
| container) | | "accessToken": "sk-ant-oat01-...", |
|
||||
+--------------------+ | "refreshToken": "sk-ant-ort01-...", |
|
||||
| "expiresAt": 1772120060006 |
|
||||
| } |
|
||||
| } |
|
||||
+-------------------+-----------------------+
|
||||
|
|
||||
inotifywait detects
|
||||
CLOSE_WRITE / MOVED_TO
|
||||
|
|
||||
+-------------------v-----------------------+
|
||||
| sync-oauth-token.sh |
|
||||
| (systemd service, runs continuously) |
|
||||
+---+----------------+----------------+-----+
|
||||
| | |
|
||||
+----------------+ +-----------+---------+ |
|
||||
| | | |
|
||||
+---------v---------+ +--------v--------+ +--------v--------+
|
||||
| oauth.json | | .env | | docker compose |
|
||||
| { | | ANTHROPIC_ | | down/up gateway |
|
||||
| "anthropic": { | | OAUTH_TOKEN= | | (reloads env) |
|
||||
| "access":..., | | "sk-ant-oat01-" | +---------+--------+
|
||||
| "refresh":...,| +-----------------+ |
|
||||
| "expires":... | +----------v----------+
|
||||
| } | | OpenClaw Gateway |
|
||||
| } | | (fresh token loaded |
|
||||
+--------+----------+ | from container env) |
|
||||
| +----------+----------+
|
||||
| mergeOAuthFileIntoStore() |
|
||||
| (reads on startup) |
|
||||
+-------------------->+ |
|
||||
| +--------v---------+
|
||||
+------------->| api.anthropic.com|
|
||||
| Claude Opus 4.6 |
|
||||
+------------------+
|
||||
```
|
||||
|
||||
## Volume Mounts (Docker)
|
||||
|
||||
```
|
||||
HOST PATH CONTAINER PATH
|
||||
========= ==============
|
||||
|
||||
Gateway container (openclaw-openclaw-gateway-1):
|
||||
/root/.openclaw/ -> /home/node/.openclaw/
|
||||
/root/.openclaw/credentials/oauth.json -> /home/node/.openclaw/credentials/oauth.json
|
||||
/root/.openclaw/agents/*/agent/auth-profiles.json -> /home/node/.openclaw/agents/*/agent/auth-profiles.json
|
||||
/home/node/.claude/ -> /home/node/.claude/
|
||||
/root/openclaw/.env -> loaded as container env vars (at creation time only)
|
||||
|
||||
Claude CLI container (claude-proxy):
|
||||
/root/.openclaw/workspaces/workspace-claude-proxy/
|
||||
config/ -> /root/
|
||||
config/.claude/.credentials.json -> /root/.claude/.credentials.json
|
||||
```
|
||||
|
||||
## Auth Resolution Order (inside gateway)
|
||||
|
||||
When the gateway needs to authenticate with Anthropic:
|
||||
|
||||
```
|
||||
1. resolveApiKeyForProvider("anthropic")
|
||||
2. -> resolveAuthProfileOrder()
|
||||
3. -> reads agents/<agent>/agent/auth-profiles.json
|
||||
4. -> isValidProfile() checks each profile:
|
||||
5. - type:"api_key" -> requires cred.key
|
||||
6. - type:"oauth" -> requires cred.access (NOT cred.key!)
|
||||
7. - type:"token" -> requires cred.token
|
||||
8. -> If valid profile found: use it
|
||||
9. -> If no valid profile: resolveEnvApiKey("anthropic")
|
||||
10. -> Reads ANTHROPIC_OAUTH_TOKEN from container env
|
||||
11. -> isOAuthToken(key) detects "sk-ant-oat" prefix
|
||||
12. -> Uses Bearer auth + Claude Code identity headers
|
||||
13. -> Sends request to api.anthropic.com
|
||||
|
||||
On gateway startup:
|
||||
mergeOAuthFileIntoStore()
|
||||
-> Reads /home/node/.openclaw/credentials/oauth.json
|
||||
-> Merges into auth profile store (if profile doesn't exist)
|
||||
```
|
||||
|
||||
## Why down/up and NOT restart
|
||||
|
||||
```
|
||||
docker compose restart openclaw-gateway
|
||||
-> Sends SIGTERM to container process
|
||||
-> Restarts the SAME container (same env vars from creation time)
|
||||
-> .env changes are NOT reloaded
|
||||
-> Result: gateway still has OLD token
|
||||
|
||||
docker compose down openclaw-gateway && docker compose up -d openclaw-gateway
|
||||
-> Stops and REMOVES the container
|
||||
-> Creates a NEW container (reads .env fresh)
|
||||
-> New env vars are loaded
|
||||
-> Result: gateway has NEW token
|
||||
```
|
||||
|
||||
## Source Code References (inside gateway container)
|
||||
|
||||
| File | Line | Function |
|
||||
|------|------|----------|
|
||||
| `/app/dist/paths-CyR9Pa1R.js` | 190 | `OAUTH_FILENAME = "oauth.json"` |
|
||||
| `/app/dist/paths-CyR9Pa1R.js` | 198-204 | `resolveOAuthDir()` -> `$STATE_DIR/credentials/` |
|
||||
| `/app/dist/paths-CyR9Pa1R.js` | 203 | `resolveOAuthPath()` -> joins dir + filename |
|
||||
| `/app/dist/model-auth-CmUeBbp-.js` | 3048 | `mergeOAuthFileIntoStore()` -- reads oauth.json |
|
||||
| `/app/dist/model-auth-CmUeBbp-.js` | 3358 | `buildOAuthApiKey()` -- returns `credentials.access` |
|
||||
| `/app/dist/model-auth-CmUeBbp-.js` | 3832 | `isValidProfile()` -- for oauth, checks `cred.access` |
|
||||
| `/app/dist/model-auth-CmUeBbp-.js` | 3942 | `resolveApiKeyForProvider()` -- profiles then env fallback |
|
||||
| `/app/dist/model-auth-CmUeBbp-.js` | 4023 | `resolveEnvApiKey("anthropic")` -> reads env var |
|
||||
Reference in New Issue
Block a user