Files
handler/docker-compose.yml
T
Claude fa2e97130d feat: bundle agent executables + web-driven claude login
Two changes so an operator can stand up and authenticate Handler entirely
from the browser, with a self-contained control image.

Bundle executables in the control image (Dockerfile.control)
- Node.js (NodeSource) + the Claude Code CLI, mise (official apt repo), and
  forge (git-pkgs/forge, built in a Go stage) join the existing git/tmux/ssh.
  No more bring-your-own binaries: live agent spawning, the verification gate,
  CI resolution, and the login flow all work out of the box. Installed under
  /usr so the /var/lib/handler VOLUME never masks them; mise apt source pinned
  to $TARGETARCH for the multi-arch (amd64/arm64) build.

Claude login from the web UI
- New login_start / login_submit command types (migration 0005) drive the
  interactive `claude /login` through the same enqueue→worker handoff every
  other control action uses — the API container has no claude binary.
- control/login.py opens `claude` in a dedicated tmux session, sends /login,
  selects the subscription account, and scrapes the claude.com authorization
  URL (tmux.capture_pane, -pJ so a wrapped URL rejoins); a second command feeds
  back the pasted code. Fully mockable via the tmux seam.
- API: POST /login/start, POST /login/submit (admin-gated).
- Dashboard: a "Claude Login" pane — a button that starts the flow, embeds the
  URL in an iframe (with a new-tab fallback, since claude.com may refuse
  framing), and takes the code to finish.

Also un-ignores frontend/lib/ (a broad Python `lib/` rule was swallowing the
UI's own api client + formatters, breaking rebuilds from a fresh clone) and
reconstructs those two source files; rebuilt static export committed.

Tests: control/login unit tests (tmux faked), worker dispatch, and API route
tests. Full suite green (195 tests), ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YKVyBmKvWDVgrFC9WER2f2
2026-07-13 17:45:58 +00:00

80 lines
3.1 KiB
YAML

# Local/dev deployment: API + Postgres. For a single-node SQLite deploy, drop the
# `db` service and DATABASE_URL override — the image defaults to SQLite on the
# handler-data volume.
services:
api:
image: ghcr.io/0xwheatyz/handler:latest
build: .
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql+psycopg://handler:handler@db:5432/handler
# Required — the API refuses to start without it. Set in .env or the shell.
AUTH_TOKEN: ${AUTH_TOKEN:?set AUTH_TOKEN in .env or the environment}
SHARED_CONTEXT_WRITE_TOKEN: ${SHARED_CONTEXT_WRITE_TOKEN:-}
WEBHOOK_URL: ${WEBHOOK_URL:-}
UI_ENABLED: ${UI_ENABLED:-true}
CORS_ORIGINS: ${CORS_ORIGINS:-}
# The API computes new projects' root_dir under this path (shared volume with the
# control container, which does the actual cloning).
PROJECTS_ROOT: /var/lib/handler/projects
# Encrypts git-server tokens/SSH keys at rest; must match the control container.
HANDLER_SECRET_KEY: ${HANDLER_SECRET_KEY:-}
volumes:
- handler-data:/var/lib/handler
depends_on:
db:
condition: service_healthy
restart: unless-stopped
# Control layer: the `handler` worker. Drains the control-command queue the API enqueues
# (spawn/kill/resume/approve/reject/forge-init/poll-ci/sync), fires due schedules, and
# sweeps CI on an interval.
# Shares the database and the handler-data volume with the API. It waits for the API
# (which owns migrations), so RUN_MIGRATIONS is off here to avoid a startup race. Run
# one-shot control commands against the same image with, e.g.,
# `docker compose run --rm control handler list`. Every executable it shells out to —
# `git`, `tmux`, `node`+`claude`, `mise`, `forge` — is bundled in the image, so live
# agent spawning and the claude web-login flow work with no bring-your-own binaries.
control:
image: ghcr.io/0xwheatyz/handler/control:latest
build:
context: .
dockerfile: Dockerfile.control
environment:
DATABASE_URL: postgresql+psycopg://handler:handler@db:5432/handler
RUN_MIGRATIONS: "false"
PROJECTS_ROOT: /var/lib/handler/projects
# Per-project forge credentials are resolved from credential_ref pointers at spawn;
# export the referenced vars here when spawning agents from this container.
FORGE_VERSION: ${FORGE_VERSION:-}
# Decrypts git-server tokens/SSH keys stored by the API; must match the API's key.
HANDLER_SECRET_KEY: ${HANDLER_SECRET_KEY:-}
volumes:
- handler-data:/var/lib/handler
depends_on:
db:
condition: service_healthy
api:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: handler
POSTGRES_PASSWORD: handler
POSTGRES_DB: handler
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U handler -d handler"]
interval: 5s
timeout: 3s
retries: 10
restart: unless-stopped
volumes:
handler-data:
postgres-data: