Files
oh-my-pi/docker-compose.yml
T
can1357 bbe6959b49 feat(deployment): added gh-proxy compose service and role checks
- Added ROBOMP_GH_PROXY_HMAC_KEY/URL wiring so orchestrator calls gh-proxy over signed API paths.
- Added a gh-proxy service and internal robomp_internal network in compose, isolating token-bearing calls.
- Changed robomp container startup to explicit env allowlisting and to fail when GITHUB_TOKEN is present.
- Added proxy-role detection in entrypoint.sh to skip PI_ROOT checks when running robomp.proxy.
- Added docs and operator workflow updates documenting the two-container trust boundary and recovery flow.
2026-05-15 01:56:57 +02:00

145 lines
7.6 KiB
YAML

services:
# ───────────────────────────────────────────────────────────────────────────
# robomp orchestrator
#
# NOTE: `env_file:` is INTENTIONALLY ABSENT. We never want the gh-proxy's
# PAT (`GITHUB_TOKEN` in .env) to leak into this container's environment.
# Every variable below is an explicit allowlist; compose still reads `.env`
# for `${VAR}` interpolation, but only the keys listed here flow into the
# container. Adding a new secret means an explicit compose-level decision
# about which container is allowed to see it.
# ───────────────────────────────────────────────────────────────────────────
robomp:
build:
context: .
dockerfile: Dockerfile
additional_contexts:
# Built by `bin/stage-pi.sh` (rsynced pi checkout sans target/runs/...).
# The runtime mount below still points at the full $PI_ROOT.
pi: ./.pi-context
image: robomp:dev
container_name: robomp
# Phase B (graceful shutdown): gives the orchestrator at least
# ROBOMP_SHUTDOWN_DRAIN_TIMEOUT_SECONDS + ROBOMP_SHUTDOWN_KILL_TIMEOUT_SECONDS
# before SIGKILL. Defaults: 25 + 5 = 30s.
stop_grace_period: 30s
restart: unless-stopped
environment:
# --- gh-proxy channel ---
# The orchestrator NEVER holds GITHUB_TOKEN; it talks to the sibling
# gh-proxy container over the internal-only network.
ROBOMP_GH_PROXY_URL: http://gh-proxy:8081
ROBOMP_GH_PROXY_HMAC_KEY: ${ROBOMP_GH_PROXY_HMAC_KEY:?ROBOMP_GH_PROXY_HMAC_KEY must be set in .env}
# --- webhook + identity ---
GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET:?GITHUB_WEBHOOK_SECRET must be set in .env}
ROBOMP_BOT_LOGIN: ${ROBOMP_BOT_LOGIN:?ROBOMP_BOT_LOGIN must be set in .env}
ROBOMP_GIT_AUTHOR_NAME: ${ROBOMP_GIT_AUTHOR_NAME:-}
ROBOMP_GIT_AUTHOR_EMAIL: ${ROBOMP_GIT_AUTHOR_EMAIL:?ROBOMP_GIT_AUTHOR_EMAIL must be set in .env}
ROBOMP_REPO_ALLOWLIST: ${ROBOMP_REPO_ALLOWLIST:?ROBOMP_REPO_ALLOWLIST must be set in .env}
ROBOMP_MAINTAINER_LOGINS: ${ROBOMP_MAINTAINER_LOGINS:-}
ROBOMP_REVIEWER_BOTS: ${ROBOMP_REVIEWER_BOTS:-}
# --- model selection ---
ROBOMP_MODEL: ${ROBOMP_MODEL:-p-anthropic/claude-sonnet-4-6}
ROBOMP_PROVIDER: ${ROBOMP_PROVIDER:-}
ROBOMP_THINKING: ${ROBOMP_THINKING:-high}
# --- runtime tuning ---
ROBOMP_MAX_CONCURRENCY: ${ROBOMP_MAX_CONCURRENCY:-8}
ROBOMP_TASK_TIMEOUT_SECONDS: ${ROBOMP_TASK_TIMEOUT_SECONDS:-2400}
ROBOMP_REQUEST_TIMEOUT_SECONDS: ${ROBOMP_REQUEST_TIMEOUT_SECONDS:-120}
ROBOMP_RATE_LIMIT_WINDOW_SECONDS: ${ROBOMP_RATE_LIMIT_WINDOW_SECONDS:-3600}
ROBOMP_RATE_LIMIT_DEFAULT: ${ROBOMP_RATE_LIMIT_DEFAULT:-3}
ROBOMP_RATE_LIMIT_CONTRIBUTOR: ${ROBOMP_RATE_LIMIT_CONTRIBUTOR:-10}
ROBOMP_RATE_LIMIT_UNLIMITED: ${ROBOMP_RATE_LIMIT_UNLIMITED:-}
ROBOMP_REPLAY_TOKEN: ${ROBOMP_REPLAY_TOKEN:-}
# --- container-fixed paths ---
ROBOMP_OMP_COMMAND: omp
ROBOMP_WORKSPACE_ROOT: /data/workspaces
ROBOMP_SQLITE_PATH: /data/robomp.sqlite
ROBOMP_LOG_DIR: /data/logs
ROBOMP_BIND_HOST: ${ROBOMP_BIND_HOST:-0.0.0.0}
ROBOMP_BIND_PORT: ${ROBOMP_BIND_PORT:-8080}
PI_ROOT: /work/pi
depends_on:
gh-proxy:
condition: service_started
# Resolve `llm-gateway.internal` (used in ~/.omp/agent/models.yml) to the
# Docker host. The actual gateway listens on 127.0.0.1:4000 on the host;
# `host-gateway` is Docker's alias for the host bridge IP.
extra_hosts:
- "llm-gateway.internal:host-gateway"
networks:
- default
- robomp_internal
volumes:
- ${PI_ROOT:-/work/pi}:/work/pi:ro
- ./data:/data
# Provider config (proxy endpoints + stub api keys). Credentials stay on
# the host; only the routing config is exposed to the container.
- ${HOME}/.omp/agent/models.yml:/root/.omp/agent/models.yml:ro
# Personal omp agent config from the host: the global AGENTS.md context
# file and the rules library. The loader (`packages/coding-agent/src/
# discovery/agents.ts`) walks `~/.agent/{AGENTS.md,rules,…}`, so we mount
# the host's `AGENT.md` (singular) onto the container path `AGENTS.md`
# (plural) to match the expected name without renaming on the host.
- ${HOME}/.agent/AGENT.md:/root/.agent/AGENTS.md:ro
- ${HOME}/.agent/rules:/root/.agent/rules:ro
ports:
- "8080:8080"
# ───────────────────────────────────────────────────────────────────────────
# gh-proxy
#
# The only container that ever holds GITHUB_TOKEN. Reachable only from the
# orchestrator on the internal-only network — no host port mapping. Every
# request must carry a valid HMAC signature (ROBOMP_GH_PROXY_HMAC_KEY).
# `env_file:` is INTENTIONALLY ABSENT for the same reason: the explicit
# allowlist below is the only place where any var lands in this container.
# ───────────────────────────────────────────────────────────────────────────
gh-proxy:
image: robomp:dev
container_name: gh-proxy
restart: unless-stopped
command: ["python", "-m", "robomp.proxy", "serve"]
environment:
# PAT: lives ONLY here. The orchestrator's compose block refuses to
# let this var into its container.
GITHUB_TOKEN: ${GITHUB_TOKEN:?GITHUB_TOKEN must be set in .env}
# Shared with the orchestrator: HMAC verification key.
ROBOMP_GH_PROXY_HMAC_KEY: ${ROBOMP_GH_PROXY_HMAC_KEY:?ROBOMP_GH_PROXY_HMAC_KEY must be set in .env}
# The proxy reuses the SandboxManager pool layout under /data/workspaces.
ROBOMP_WORKSPACE_ROOT: /data/workspaces
ROBOMP_SQLITE_PATH: /data/robomp.sqlite
ROBOMP_LOG_DIR: /data/logs
# Bind on the internal network only.
ROBOMP_GH_PROXY_BIND_HOST: 0.0.0.0
ROBOMP_GH_PROXY_BIND_PORT: 8081
# Settings still requires these on construction; the proxy never uses
# them but the validator runs the same code path as the orchestrator.
GITHUB_WEBHOOK_SECRET: ${GITHUB_WEBHOOK_SECRET:?GITHUB_WEBHOOK_SECRET must be set in .env}
ROBOMP_BOT_LOGIN: ${ROBOMP_BOT_LOGIN:?ROBOMP_BOT_LOGIN must be set in .env}
ROBOMP_GIT_AUTHOR_EMAIL: ${ROBOMP_GIT_AUTHOR_EMAIL:?ROBOMP_GIT_AUTHOR_EMAIL must be set in .env}
ROBOMP_REPO_ALLOWLIST: ${ROBOMP_REPO_ALLOWLIST:?ROBOMP_REPO_ALLOWLIST must be set in .env}
networks:
# `default` gives gh-proxy outbound NAT to api.github.com; `robomp_internal`
# is how the orchestrator reaches it. No `ports:` mapping → still
# unreachable from the host or any sibling project.
- default
- robomp_internal
volumes:
# Shared workspace pool/worktrees so the proxy can drive git operations
# against the same per-issue worktrees the orchestrator builds.
- ./data:/data
networks:
# External-facing bridge: webhook ingress (8080) and the orchestrator's
# outbound path to the host LLM gateway via extra_hosts.
default: {}
# Orchestrator <-> gh-proxy only. internal: true means no egress and no
# ingress from outside the compose project.
robomp_internal:
internal: true