165 lines
8.8 KiB
YAML
165 lines
8.8 KiB
YAML
services:
|
|
# ───────────────────────────────────────────────────────────────────────────
|
|
# roboomp 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:
|
|
# pi root: gives the web-builder stage access to the workspace
|
|
# bun.lock + catalog (web/package.json refs `catalog:` versions).
|
|
# python/robomp/data and pi-root excludes are filtered via
|
|
# Dockerfile.robomp.dockerignore at the context root.
|
|
context: ../..
|
|
dockerfile: Dockerfile.robomp
|
|
args:
|
|
# Tag of the pre-built pi-base image produced by `bun run pi:image`
|
|
# (sources: pi root /Dockerfile). Override per-environment as needed.
|
|
PI_BASE: oh-my-pi/pi:dev
|
|
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:-}
|
|
|
|
# --- PR review / vouch gate ---
|
|
# When PRs are gated by the vouch GitHub Action, set the trigger to
|
|
# `vouched_label` so robomp reviews ONLY PRs that survive the gate
|
|
# (the workflow labels survivors `ROBOMP_VOUCH_REVIEW_LABEL`), instead
|
|
# of racing the gate on PR open. `vouched_label` keeps review ENABLED.
|
|
ROBOMP_PR_REVIEW_ENABLED: ${ROBOMP_PR_REVIEW_ENABLED:-true}
|
|
ROBOMP_PR_REVIEW_TRIGGER: ${ROBOMP_PR_REVIEW_TRIGGER:-open}
|
|
ROBOMP_VOUCH_REVIEW_LABEL: ${ROBOMP_VOUCH_REVIEW_LABEL:-vouched}
|
|
ROBOMP_VOUCH_REVIEW_LABELER: ${ROBOMP_VOUCH_REVIEW_LABELER:-github-actions[bot]}
|
|
|
|
# --- model selection ---
|
|
ROBOMP_MODEL: ${ROBOMP_MODEL:-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_TASK_TIMEOUT_HARD_GRACE_SECONDS: ${ROBOMP_TASK_TIMEOUT_HARD_GRACE_SECONDS:-60}
|
|
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_QUESTION_AUTOCLOSE_ENABLED: ${ROBOMP_QUESTION_AUTOCLOSE_ENABLED:-true}
|
|
ROBOMP_QUESTION_AUTOCLOSE_HOURS: ${ROBOMP_QUESTION_AUTOCLOSE_HOURS:-4}
|
|
ROBOMP_QUESTION_AUTOCLOSE_SCAN_SECONDS: ${ROBOMP_QUESTION_AUTOCLOSE_SCAN_SECONDS:-60}
|
|
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 /srv/agent-home/.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:ro
|
|
- robomp_data:/data
|
|
# Host agent config is mounted read-only under /srv/agent-home-stage
|
|
# with host-controlled permissions. The entrypoint copies it into
|
|
# root-owned, world-readable files under /srv/agent-home; the agent
|
|
# subprocess runs with HOME=/srv/agent-home, so ~/.omp and ~/.agent
|
|
# resolve there without exposing mutable host mounts.
|
|
- ${HOME}/.omp/agent/models.container.yml:/srv/agent-home-stage/.omp/agent/models.yml:ro
|
|
- ${HOME}/.agent/AGENT.md:/srv/agent-home-stage/.agent/AGENTS.md:ro
|
|
- ${HOME}/.agent/rules:/srv/agent-home-stage/.agent/rules:ro
|
|
ports:
|
|
- "0.0.0.0:6543: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.
|
|
- robomp_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
|
|
|
|
volumes:
|
|
# Docker-managed Linux volume so UID/GID permissions on /data are enforced.
|
|
robomp_data: {}
|