# Handler configuration — copy to .env and fill in. Never commit real secrets. # Database. SQLite fallback (single-node) or Postgres (centralized, default for real deploys). # SQLite: sqlite:////absolute/path/to/handler.db # Postgres: postgresql+psycopg://user:pass@host:5432/handler DATABASE_URL=sqlite:////var/lib/handler/handler.db # Single global bearer token gating every API route. Required for the API to start. AUTH_TOKEN=change-me-to-a-long-random-string # Optional higher-trust token gating PUT /shared/context/:key. # Falls back to AUTH_TOKEN if unset. # SHARED_CONTEXT_WRITE_TOKEN= # Optional admin token gating the web control surface: enqueuing control commands # (spawn/kill/resume/approve/reject/forge-init/poll-ci), project CRUD, forge-host CRUD, # and credential-pointer edits. Falls back to AUTH_TOKEN if unset. Give operators this # token in the dashboard to unlock management actions. # ADMIN_TOKEN= # Optional generic webhook target for the Notification hook (ntfy, Pushover, Slack, ...). # Fully bring-your-own; the Notification hook is a no-op when unset. # WEBHOOK_URL=https://ntfy.sh/my-topic # Symmetric key for the encrypted secret store: git-server tokens and SSH private keys # are Fernet-encrypted with it before they reach the database. Set the SAME value on the # API (encrypts on write) and the control container (decrypts at clone/spawn). Generate: # python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" # Unset => storing tokens/SSH keys on git servers is refused with a clear error. # HANDLER_SECRET_KEY= # Base directory under which per-project roots and agent worktrees live (isolation). PROJECTS_ROOT=/var/lib/handler/projects # Web search provider for the agents' web_search tool (pi harness). Resolution order: # SearXNG instance -> Brave Search API -> unset = DuckDuckGo HTML fallback (zero-config, # rate-limited). web_fetch needs no provider. # SEARXNG_URL=http://searxng.lan:8080 # BRAVE_SEARCH_API_KEY= # Binary overrides (defaults shown). Point at fakes in tests/CI. # CLAUDE_BIN=claude # PI_BIN=pi # MISE_BIN=mise # TMUX_BIN=tmux # FORGE_BIN=forge # GIT_BIN=git # Phase 2 (forge integration). Pin the forge version your base image installs; spawn # verifies the injected forge matches and warns on drift. Leave unset to skip the check. # FORGE_VERSION=1.2.3 # Branches a direct `git push` may not reach without a standing approval (comma-separated). # Closes the "merge locally, push to main" path around the forge-merge approval gate. # PROTECTED_BRANCHES=main,master # Phase 3 (web UI). Serve the bundled UI from "/" and "/static". Set false for a # headless, API-only deployment. Applied at process start (restart to change). # UI_ENABLED=true # Extra origins allowed to call the API cross-origin (comma-separated). Only needed if # you host the UI on a DIFFERENT origin than the API; the shipped UI is same-origin and # needs none. Empty => no CORS middleware. # CORS_ORIGINS=https://handler.example.ts.net # Per-project credentials are NOT set here — they live on each project's `credential_ref` # as a POINTER (env:VAR / file:/path / cmd:...), resolved and injected only at spawn. # The database never stores the raw token. Example, when registering a project: # credential_ref = "env:LEEWORKS_TOKEN" (then export LEEWORKS_TOKEN where the control # layer runs; it's injected as FORGE_TOKEN + # the host-specific var, e.g. GITHUB_TOKEN)