Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.docker.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ ZAI_API_KEY=your-zai-api-key-here
# Anthropic API configuration - Get your API key from https://console.anthropic.com
ANTHROPIC_API_KEY=your-anthropic-api-key-here

# Claude subscription (Pro/Max) long-lived OAuth token.
# Generate once on the host with: claude setup-token
# It prints a ~1-year token — paste it here. This takes precedence over the
# mounted credentials file and removes the need for the macOS keychain/launchd
# sync. Regenerate it once a year when it expires.
CLAUDE_CODE_OAUTH_TOKEN=

# =============================================================================
# DATABASE CONFIGURATION
# =============================================================================
Expand Down
24 changes: 12 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,23 @@ dev: ## Run in development mode
test: ## Run tests
npm test

docker-build: ## Build Docker image
docker-compose build
podman-build: ## Build image with podman compose
podman compose build

docker-run: ## Run Docker container
docker-compose up -d
podman-run: ## Run containers with podman compose
podman compose up -d

docker-logs: ## Show Docker logs
docker-compose logs -f
podman-logs: ## Show container logs
podman compose logs -f

docker-stop: ## Stop Docker containers
docker-compose down
podman-stop: ## Stop containers
podman compose down

docker-clean: ## Remove Docker containers and images
docker-compose down -v
docker rmi claude-code-proxy-cc-proxy:latest 2>/dev/null || true
podman-clean: ## Remove containers and images
podman compose down -v
podman rmi claude-code-proxy-cc-proxy:latest 2>/dev/null || true

docker-restart: docker-stop docker-run ## Restart Docker containers
podman-restart: podman-stop podman-run ## Restart containers

lint: ## Run linter
npm run lint
Expand Down
22 changes: 3 additions & 19 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
services:
# Claude Code Proxy
cc-proxy:
container_name: cc-proxy
build:
context: .
dockerfile: Dockerfile
container_name: ${PROXY_CONTAINER_NAME:-cc-proxy}
ports:
- "${HOST_PROXY_PORT:-4181}:4181"
env_file:
Expand All @@ -14,9 +14,9 @@ services:
- API_STREAMING_TIMEOUT_MS=${API_STREAMING_TIMEOUT_MS:-18000000}
- API_MAX_RETRIES=${API_MAX_RETRIES:-5}
- API_RETRY_DELAY_MS=${API_RETRY_DELAY_MS:-2000}
- LOG_LEVEL=${LOG_LEVEL:-info}
- NODE_ENV=${NODE_ENV:-production}
- CLAUDE_CREDENTIALS_PATH=/app/credentials.json
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@${DB_HOST:-cc-db}:5432/${POSTGRES_DB:-claude_proxy}
volumes:
- ~/.claude/claude-credentials.json:/app/credentials.json:rw
healthcheck:
Expand All @@ -41,8 +41,8 @@ services:

# PostgreSQL Database
cc-db:
container_name: cc-db
image: postgres:${POSTGRES_VERSION:-15}-alpine
container_name: ${DB_CONTAINER_NAME:-cc-db}
environment:
- POSTGRES_DB=${POSTGRES_DB:-claude_proxy}
- POSTGRES_USER=${POSTGRES_USER:-postgres}
Expand All @@ -61,22 +61,6 @@ services:
networks:
- claude-network

# Adminer (Database Management UI)
cc-adminer:
container_name: cc-adminer
image: adminer:${ADMINER_VERSION:-latest}
ports:
- "${ADMINER_PORT:-8080}:8080"
environment:
- ADMINER_DEFAULT_SERVER=${ADMINER_DEFAULT_SERVER:-cc-db}
- ADMINER_DESIGN=${ADMINER_DESIGN:-nette}
depends_on:
cc-db:
condition: service_healthy
restart: ${RESTART_POLICY:-unless-stopped}
networks:
- claude-network

volumes:
postgres_data:
driver: local
Expand Down
32 changes: 29 additions & 3 deletions docs/CREDENTIAL_SYNC.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
# Auto-Sync Claude Credentials
# Claude Subscription Credentials

The proxy needs fresh Claude OAuth credentials to use your subscription (savings plan) instead of API keys. This setup automatically syncs credentials from the macOS Keychain to a file that the proxy container reads.
The proxy needs Claude OAuth credentials to use your subscription (Pro/Max) instead of API keys. There are two ways to provide them.

## How It Works
## Recommended: Static long-lived token (`claude setup-token`)

For containers and headless setups, mint a **~1-year** OAuth token once and pass it as an environment variable. This avoids the macOS Keychain entirely, so it keeps working when your Mac is asleep or logged out, and there's nothing to sync.

```bash
# On the host (interactive, opens a browser for OAuth):
claude setup-token
# Copy the printed token into your .env.docker:
# CLAUDE_CODE_OAUTH_TOKEN=<paste-token-here>
```

Then start the stack normally. The proxy reads `CLAUDE_CODE_OAUTH_TOKEN` and **prefers it over the mounted credentials file**. Confirm it was picked up:

```bash
curl -s http://127.0.0.1:4181/config | python3 -c "import sys,json; print(json.load(sys.stdin)['providers']['claudeSubscription']['authMode'])"
# → static-token
```

Regenerate the token once a year (or whenever it's revoked) by re-running `claude setup-token` and updating the env var.

---

## Legacy: Keychain → file sync (interactive host only)

> This is the older approach. It only works on a macOS host where a GUI login session can read the Keychain, and it can go stale under `launchd`. Prefer the static token above for containers. When `CLAUDE_CODE_OAUTH_TOKEN` is unset, the proxy falls back to the mounted credentials file described below.

### How It Works

1. **macOS Keychain** stores your Claude OAuth token (service: `Claude Code-credentials`)
2. **sync-credentials.sh** extracts the token and writes to `~/.claude/claude-credentials.json`
Expand Down
Loading
Loading