Закрывает Type B эпика ORCH-10 (по ADR-001 ORCH-103, D1–D11): - deploy/bundled/docker-compose.yml — самодостаточный compose всего стека (орк + watchdog + Gitea 1.22.6 + зеркало upstream Plane CE v0.23.1, ~14 контейнеров); project name orchestrator-bundle (узнаваемый префикс), container_name не пиннится, staging-контура нет; одна bridge-сеть, машинный трафик — сервис-DNS, наружу только человеческие порты; GITEA__webhook__ALLOWED_HOST_LIST=orchestrator; все образы пиннованы неподвижными тегами. Корневой compose/Dockerfile/src/** — байт-в-байт. - deploy/bundled/.env.example — конфиг-канон bundle (плейсхолдеры, ни одного дефолтного пароля; key-set-sync интерполяций держит тест). - scripts/bootstrap_bundle.py — python stdlib-only, режимы plan/apply/verify, step-движок check→ensure, exit 0/2/1: preflight (fail-fast до мутаций) → секреты (gen_secrets.py + stdlib secrets, без перетирания) → up+готовность → init Gitea автоматом → init Plane (manual-step с API-верификацией) → онбординг строго onboard_project.py apply+verify → token-remote клон → сборка .env/.env.watchdog (единственный писатель, права 600) → health. Delete-операций нет вообще (D9), секреты не печатаются (NFR-3). - CHANGELOG.md, CLAUDE.md (абзац Type B), .gitignore (deploy/bundled/repos/). Док BUNDLED_SETUP.md, REPLICATION §1, arch README, adr-0038 и три структурных тест-модуля (TC-01…TC-11) — в предыдущих коммитах ветки; полный регресс 1844 passed, ruff по файлам задачи чистый. Refs: ORCH-103 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
339 lines
13 KiB
YAML
339 lines
13 KiB
YAML
# ORCH-103 (Type B Bundled, ADR-001 D1–D4): самодостаточный compose ВСЕГО стека
|
||
# для тиража «под ключ» на хост заказчика: orchestrator + orchestrator-watchdog +
|
||
# Gitea + Plane CE (зеркало официального selfhost-référence makeplane/plane
|
||
# v0.23.1: имена сервисов и env-контракт — upstream, анти-дрейф к их докам; наши
|
||
# отличия от référence: пиннинг неподвижными тегами литералом вместо ${APP_RELEASE}
|
||
# (NFR-6, держится tests/test_bundle_compose.py), убраны replicas/platform/SENTRY,
|
||
# секреты БЕЗ дефолтных значений — их генерирует scripts/bootstrap_bundle.py).
|
||
#
|
||
# Этот файл НЕ исполняется в нашем прод-контуре (корневой docker-compose.yml —
|
||
# байт-в-байт, заморожен анти-дрейфом ORCH-102); активация — только явный запуск
|
||
# оператором на целевом хосте (паттерн ORCH-009, kill-switch не нужен).
|
||
#
|
||
# Конфиг-слои (D2): интерполяции ${VAR} читаются compose'ом из deploy/bundled/.env
|
||
# (авто-чтение из project dir — без --env-file-футгана); канон ключей —
|
||
# deploy/bundled/.env.example (key-set-sync держит тест). Runtime-конфиг орка и
|
||
# watchdog — КОРНЕВЫЕ .env / .env.watchdog (канон Lite 1:1, REPLICATION §2);
|
||
# их единственный писатель — bootstrap_bundle.py.
|
||
#
|
||
# Сеть (D4): одна bridge-сеть проекта; машинный трафик — строго сервис-DNS
|
||
# (Plane→орк http://orchestrator:8500/webhook/plane, Gitea→орк .../webhook/gitea,
|
||
# орк→Plane http://proxy, орк→Gitea http://gitea:3000); network_mode: host НЕ
|
||
# используется (ssh-деплой-контур нашего хоста в bundle структурно спит —
|
||
# ORCH_DEPLOY_SSH_HOST пуст). Наружу публикуются ТОЛЬКО человеческие порты
|
||
# (орк/Plane proxy/Gitea web); postgres/redis/mq/minio не публикуются.
|
||
#
|
||
# Project name = узнаваемый префикс томов/контейнеров orchestrator-bundle_* (D1);
|
||
# container_name сознательно НЕ пиннится ни у кого — bundle и Lite/корневой
|
||
# compose не сталкиваются по именам на одном хосте.
|
||
name: orchestrator-bundle
|
||
|
||
networks:
|
||
default:
|
||
name: orchestrator-bundle
|
||
driver: bridge
|
||
|
||
# Env-контракт Plane CE — upstream-имена (référence v0.23.1). Значения секретов
|
||
# (POSTGRES_PASSWORD/SECRET_KEY/RABBITMQ_DEFAULT_PASS/MINIO_ROOT_PASSWORD) живут
|
||
# ТОЛЬКО в deploy/bundled/.env (генерирует bootstrap); дефолтных паролей нет.
|
||
x-plane-env: &plane-env
|
||
environment:
|
||
- WEB_URL=http://${BUNDLE_PUBLIC_HOST:-localhost}:${BUNDLE_PLANE_PORT:-8080}
|
||
- DEBUG=0
|
||
- CORS_ALLOWED_ORIGINS=http://${BUNDLE_PUBLIC_HOST:-localhost}:${BUNDLE_PLANE_PORT:-8080}
|
||
- GUNICORN_WORKERS=1
|
||
# db (upstream-имена; host/port — фиксированные сервис-DNS этого файла)
|
||
- PGHOST=plane-db
|
||
- PGDATABASE=${POSTGRES_DB:-plane}
|
||
- POSTGRES_USER=${POSTGRES_USER:-plane}
|
||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||
- POSTGRES_DB=${POSTGRES_DB:-plane}
|
||
- POSTGRES_PORT=5432
|
||
- PGDATA=/var/lib/postgresql/data
|
||
- DATABASE_URL=postgresql://${POSTGRES_USER:-plane}:${POSTGRES_PASSWORD}@plane-db:5432/${POSTGRES_DB:-plane}
|
||
# redis
|
||
- REDIS_HOST=plane-redis
|
||
- REDIS_PORT=6379
|
||
- REDIS_URL=redis://plane-redis:6379/
|
||
# rabbitmq
|
||
- RABBITMQ_HOST=plane-mq
|
||
- RABBITMQ_PORT=5672
|
||
- RABBITMQ_DEFAULT_USER=${RABBITMQ_DEFAULT_USER:-plane}
|
||
- RABBITMQ_DEFAULT_PASS=${RABBITMQ_DEFAULT_PASS}
|
||
- RABBITMQ_DEFAULT_VHOST=${RABBITMQ_DEFAULT_VHOST:-plane}
|
||
- RABBITMQ_VHOST=${RABBITMQ_DEFAULT_VHOST:-plane}
|
||
- AMQP_URL=amqp://${RABBITMQ_DEFAULT_USER:-plane}:${RABBITMQ_DEFAULT_PASS}@plane-mq:5672/${RABBITMQ_DEFAULT_VHOST:-plane}
|
||
# application secret (генерирует bootstrap; дефолта сознательно НЕТ)
|
||
- SECRET_KEY=${SECRET_KEY}
|
||
# datastore (minio)
|
||
- USE_MINIO=1
|
||
- AWS_REGION=
|
||
- AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER:-plane-minio-admin}
|
||
- AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
|
||
- AWS_S3_ENDPOINT_URL=http://plane-minio:9000
|
||
- AWS_S3_BUCKET_NAME=uploads
|
||
- MINIO_ROOT_USER=${MINIO_ROOT_USER:-plane-minio-admin}
|
||
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
|
||
- BUCKET_NAME=uploads
|
||
- FILE_SIZE_LIMIT=5242880
|
||
# live server
|
||
- API_BASE_URL=http://api:8000
|
||
# proxy
|
||
- NGINX_PORT=80
|
||
|
||
services:
|
||
# ── Платформа: орк + sidecar-watchdog (образы собираются из этого же чекаута;
|
||
# корневой Dockerfile / watchdog/Dockerfile — без правок, NFR-1) ──────────
|
||
orchestrator:
|
||
build:
|
||
context: ../..
|
||
# ORCH-101 (D5): uid/gid/home двигаются ОДНОЙ группой с user: и таргетами
|
||
# маунтов ниже (инвариант ORCH-040). Дефолты bundle нейтральны (D2).
|
||
args:
|
||
APP_UID: ${ORCH_RUN_UID:-1000}
|
||
APP_GID: ${ORCH_RUN_GID:-1000}
|
||
APP_HOME: ${ORCH_AGENT_HOME_DIR:-/home/orchestrator}
|
||
restart: unless-stopped
|
||
user: "${ORCH_RUN_UID:-1000}:${ORCH_RUN_GID:-1000}"
|
||
init: true
|
||
command: ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8500"]
|
||
ports:
|
||
# человеческая точка: операторский smoke `curl /health` (D4)
|
||
- "${BUNDLE_ORCH_PORT:-8500}:8500"
|
||
volumes:
|
||
# данные/репозитории — bind ВНУТРИ project dir (uid-причины ORCH-040;
|
||
# покрыты .gitignore: неякорный data/ + deploy/bundled/repos/)
|
||
- ./data:/app/data
|
||
- ./repos:/repos
|
||
- /var/run/docker.sock:/var/run/docker.sock
|
||
# LLM-предусловие хоста заказчика (bundle его НЕ поставляет, BRD §1.3)
|
||
- ${ORCH_HOST_CLAUDE_CODE_DIR:-/usr/lib/node_modules/@anthropic-ai/claude-code}:/opt/claude-code:ro
|
||
- ${ORCH_HOST_NODE_BIN:-/usr/bin/node}:/usr/bin/node:ro
|
||
- ${ORCH_HOST_CLAUDE_DIR:-~/.claude}:${ORCH_AGENT_HOME_DIR:-/home/orchestrator}/.claude
|
||
- ${ORCH_HOST_CLAUDE_JSON:-~/.claude.json}:${ORCH_AGENT_HOME_DIR:-/home/orchestrator}/.claude.json:ro
|
||
# ssh-контур в bundle сознательно НЕ вводится (ADR D8): git-доступ агентов
|
||
# — HTTP token-remote, деплой-хуки нашего хоста структурно спят.
|
||
# runtime-конфиг орка собирает bootstrap (шаг 8); required:false — первый
|
||
# `up -d` поднимает стек ДО сборки конфига (AC-1), орк жив без него.
|
||
env_file:
|
||
- path: ../../.env
|
||
required: false
|
||
environment:
|
||
- ORCH_REPOS_DIR=/repos
|
||
group_add:
|
||
- "${ORCH_DOCKER_GID:-999}"
|
||
|
||
orchestrator-watchdog:
|
||
build:
|
||
context: ../..
|
||
dockerfile: watchdog/Dockerfile
|
||
restart: unless-stopped
|
||
init: true
|
||
mem_limit: 128m
|
||
mem_reservation: 32m
|
||
volumes:
|
||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||
- ./repos:/repos:ro
|
||
- ./data:/app/data:ro
|
||
env_file:
|
||
- path: ../../.env.watchdog
|
||
required: false
|
||
environment:
|
||
# bundle-сеть ≠ host-network Lite: метрики — по сервис-DNS; имя контейнера
|
||
# орка детерминировано project name (container_name не пиннится, D1).
|
||
# environment перекрывает env_file → когерентность механическая (TR-8).
|
||
- WATCHDOG_METRICS_URL=http://orchestrator:8500/metrics
|
||
- WATCHDOG_CONTAINERS=orchestrator-bundle-orchestrator-1
|
||
group_add:
|
||
- "${ORCH_DOCKER_GID:-999}"
|
||
|
||
# ── Gitea (D6): официальный образ, НЕ rootless; init полностью автоматом —
|
||
# bootstrap создаёт админа/токен через `gitea admin ...` CLI в контейнере.
|
||
# Branch protection на main НЕ настраивается (норматив D10 ORCH-009/INV-4).
|
||
gitea:
|
||
image: gitea/gitea:1.22.6
|
||
restart: unless-stopped
|
||
ports:
|
||
- "${BUNDLE_GITEA_HTTP_PORT:-3000}:3000"
|
||
environment:
|
||
- GITEA__database__DB_TYPE=sqlite3
|
||
- GITEA__security__INSTALL_LOCK=true
|
||
- GITEA__server__DOMAIN=${BUNDLE_PUBLIC_HOST:-localhost}
|
||
- GITEA__server__ROOT_URL=http://${BUNDLE_PUBLIC_HOST:-localhost}:${BUNDLE_GITEA_HTTP_PORT:-3000}/
|
||
# ssh-контур не вводится (D8): порт не публикуется, ssh выключен.
|
||
- GITEA__server__DISABLE_SSH=true
|
||
- GITEA__service__DISABLE_REGISTRATION=true
|
||
# МИНА TR-4 (D4): Gitea по умолчанию режет webhook'и в приватные адреса —
|
||
# без этой строки «задача не появилась» гарантирован.
|
||
- GITEA__webhook__ALLOWED_HOST_LIST=orchestrator
|
||
volumes:
|
||
- gitea-data:/data
|
||
healthcheck:
|
||
test: ["CMD", "curl", "-fsS", "http://localhost:3000/api/healthz"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 12
|
||
|
||
# ── Plane CE — зеркало upstream selfhost-référence v0.23.1 (D3) ────────────
|
||
web:
|
||
<<: *plane-env
|
||
image: makeplane/plane-frontend:v0.23.1
|
||
restart: unless-stopped
|
||
command: node web/server.js web
|
||
depends_on:
|
||
- api
|
||
- worker
|
||
|
||
space:
|
||
<<: *plane-env
|
||
image: makeplane/plane-space:v0.23.1
|
||
restart: unless-stopped
|
||
command: node space/server.js space
|
||
depends_on:
|
||
- api
|
||
- worker
|
||
- web
|
||
|
||
admin:
|
||
<<: *plane-env
|
||
image: makeplane/plane-admin:v0.23.1
|
||
restart: unless-stopped
|
||
command: node admin/server.js admin
|
||
depends_on:
|
||
- api
|
||
- web
|
||
|
||
live:
|
||
<<: *plane-env
|
||
image: makeplane/plane-live:v0.23.1
|
||
restart: unless-stopped
|
||
command: node live/dist/server.js live
|
||
depends_on:
|
||
- api
|
||
- web
|
||
|
||
api:
|
||
<<: *plane-env
|
||
image: makeplane/plane-backend:v0.23.1
|
||
restart: unless-stopped
|
||
command: ./bin/docker-entrypoint-api.sh
|
||
volumes:
|
||
- logs_api:/code/plane/logs
|
||
depends_on:
|
||
- plane-db
|
||
- plane-redis
|
||
- plane-mq
|
||
|
||
worker:
|
||
<<: *plane-env
|
||
image: makeplane/plane-backend:v0.23.1
|
||
restart: unless-stopped
|
||
command: ./bin/docker-entrypoint-worker.sh
|
||
volumes:
|
||
- logs_worker:/code/plane/logs
|
||
depends_on:
|
||
- api
|
||
- plane-db
|
||
- plane-redis
|
||
- plane-mq
|
||
|
||
beat-worker:
|
||
<<: *plane-env
|
||
image: makeplane/plane-backend:v0.23.1
|
||
restart: unless-stopped
|
||
command: ./bin/docker-entrypoint-beat.sh
|
||
volumes:
|
||
- logs_beat-worker:/code/plane/logs
|
||
depends_on:
|
||
- api
|
||
- plane-db
|
||
- plane-redis
|
||
- plane-mq
|
||
|
||
migrator:
|
||
<<: *plane-env
|
||
image: makeplane/plane-backend:v0.23.1
|
||
restart: "no"
|
||
command: ./bin/docker-entrypoint-migrator.sh
|
||
volumes:
|
||
- logs_migrator:/code/plane/logs
|
||
depends_on:
|
||
- plane-db
|
||
- plane-redis
|
||
|
||
plane-db:
|
||
<<: *plane-env
|
||
image: postgres:15.7-alpine
|
||
restart: unless-stopped
|
||
command: postgres -c 'max_connections=1000'
|
||
volumes:
|
||
- pgdata:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 12
|
||
|
||
plane-redis:
|
||
<<: *plane-env
|
||
image: valkey/valkey:7.2.5-alpine
|
||
restart: unless-stopped
|
||
volumes:
|
||
- redisdata:/data
|
||
healthcheck:
|
||
test: ["CMD", "valkey-cli", "ping"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 12
|
||
|
||
plane-mq:
|
||
<<: *plane-env
|
||
image: rabbitmq:3.13.6-management-alpine
|
||
restart: always
|
||
volumes:
|
||
- rabbitmq_data:/var/lib/rabbitmq
|
||
healthcheck:
|
||
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
||
interval: 15s
|
||
timeout: 10s
|
||
retries: 12
|
||
|
||
plane-minio:
|
||
<<: *plane-env
|
||
# upstream-référence держит latest — bundle пиннит неподвижный тег (NFR-6)
|
||
image: minio/minio:RELEASE.2024-05-28T17-19-04Z
|
||
restart: unless-stopped
|
||
command: server /export --console-address ":9090"
|
||
volumes:
|
||
- uploads:/export
|
||
healthcheck:
|
||
test: ["CMD", "curl", "-fsS", "http://localhost:9000/minio/health/live"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 12
|
||
|
||
proxy:
|
||
<<: *plane-env
|
||
image: makeplane/plane-proxy:v0.23.1
|
||
restart: unless-stopped
|
||
ports:
|
||
# человеческая точка: UI Plane в браузере оператора (D4)
|
||
- "${BUNDLE_PLANE_PORT:-8080}:80"
|
||
depends_on:
|
||
- web
|
||
- api
|
||
- space
|
||
|
||
# Состояние Plane/Gitea — именованные тома проекта (префикс orchestrator-bundle_,
|
||
# D1/D2); preflight bootstrap детектирует «грязный хост» по этому префиксу.
|
||
volumes:
|
||
pgdata:
|
||
redisdata:
|
||
uploads:
|
||
logs_api:
|
||
logs_worker:
|
||
logs_beat-worker:
|
||
logs_migrator:
|
||
rabbitmq_data:
|
||
gitea-data:
|