156 lines
13 KiB
Markdown
156 lines
13 KiB
Markdown
# REPLICATION — тираж платформы на новую инфраструктуру (ORCH-101)
|
||
|
||
> RUNBOOK фундамента тиража (эпик ORCH-10, слой **10-common**). Как развернуть
|
||
> оркестратор на чужом хосте **без правки кода**: переменные → секреты → smoke.
|
||
> Тираж **stateless**: данные/БД/секреты боевого хоста НЕ переносятся ни на одном
|
||
> шаге — на целевой инфре всё создаётся заново.
|
||
|
||
---
|
||
|
||
## 1. Границы: 10-common vs Lite vs Bundled
|
||
|
||
| Слой | Что это | Статус |
|
||
|------|---------|--------|
|
||
| **10-common** (этот док) | фундамент: все хост-значения параметризованы (env/конфиг), секреты выпускаются заново, smoke-процедура с PASS/FAIL | ✅ ORCH-101 |
|
||
| **Type A — Lite** | инструкция «поставь Plane+Gitea сам, подключи оркестратор» поверх 10-common | ✅ ORCH-102 — [`docs/deployment/LITE_SETUP.md`](../deployment/LITE_SETUP.md) |
|
||
| **Type B — Bundled** | комплект «всё в одном» (Plane+Gitea+оркестратор) поверх 10-common | ✅ ORCH-103 — [`docs/deployment/BUNDLED_SETUP.md`](../deployment/BUNDLED_SETUP.md) |
|
||
|
||
Этот док НЕ описывает установку Plane/Gitea — только параметризацию, секреты и
|
||
smoke самого оркестратора (анти-скоуп-крип Р-5).
|
||
|
||
### Платформенные конвенции тиража (нормативно, ADR-001 D3/D4)
|
||
|
||
- **Репо платформы обязан называться `orchestrator`.** Имя — узел безопасности
|
||
(`SELF_HOSTING_REPO`, на него завязаны все `*_repos`-leaf'ы «empty CSV →
|
||
self-hosting only»); оно сознательно НЕ конфигурируется.
|
||
- Имена compose-сервисов/профиля (`orchestrator`, `orchestrator-staging`,
|
||
`orchestrator-watchdog`, профиль `staging`) — константы платформы.
|
||
- Контейнерные пути (`/app/data`, `/repos`, `/opt/claude-code`) — layout
|
||
контейнера, не хост-значения; не параметризуются.
|
||
|
||
---
|
||
|
||
## 2. Карта переменных нового хоста
|
||
|
||
Принцип (ADR-001 D1): **дефолт каждой переменной = боевое значение текущего
|
||
хоста** — пустой `.env` ⇒ поведение байт-в-байт; на новом хосте задаёшь только
|
||
то, что отличается. Одно env-имя = один факт: pydantic `Settings` читает имя из
|
||
`env_file`, compose-интерполяция `${VAR:-default}` — из **`.env` проекта/shell**
|
||
(⚠️ НЕ из `env_file` сервиса: `.env.staging` на интерполяцию не влияет —
|
||
значения для маунтов/uid/портов живут в `.env`).
|
||
|
||
### 2.1. Хост-параметризация (новое в ORCH-101)
|
||
|
||
| Переменная | Дефолт | Назначение |
|
||
|-----------|--------|------------|
|
||
| `ORCH_AGENT_HOME_DIR` | `/home/slin` | HOME всех акторов (агенты, finalizer, monitor) + таргет маунтов `.claude`/`.claude.json`/`.ssh` + `ARG APP_HOME` (группа ORCH-040 двигается вместе) |
|
||
| `ORCH_AGENT_GIT_NAME` | `claude-bot` | git-имя коммитов агентов |
|
||
| `ORCH_GIT_EMAIL_DOMAIN` | `mva154.local` | домен git-email всех акторов (`claude-bot@…`, `deploy-finalizer@…`, `post-deploy-monitor@…`) |
|
||
| `ORCH_STAGING_PORT` | `8501` | порт staging; читают `image_freshness` И compose `command:`; guard: совпадение с прод-портом → отказ fail-closed (ORCH-058 AC-9) |
|
||
| `ORCH_HOST_REPOS_DIR` | `/home/slin/repos` | каталог репозиториев на хосте (источник маунта `/repos`) |
|
||
| `ORCH_HOST_CLAUDE_DIR` | `/home/slin/.claude` | источник маунта `~/.claude` |
|
||
| `ORCH_HOST_CLAUDE_JSON` | `/home/slin/.claude.json` | источник маунта `~/.claude.json` |
|
||
| `ORCH_HOST_SSH_DIR` | `/home/slin/.orchestrator-ssh` | источник маунта ssh-ключей (`→ $HOME/.ssh:ro`) |
|
||
| `ORCH_HOST_CLAUDE_CODE_DIR` | `/usr/lib/node_modules/@anthropic-ai/claude-code` | дистрибутив claude-code на хосте |
|
||
| `ORCH_HOST_NODE_BIN` | `/usr/bin/node` | бинарь node на хосте |
|
||
| `ORCH_RUN_UID` / `ORCH_RUN_GID` | `1000` / `1000` | uid:gid контейнера (`user:` + `ARG APP_UID/APP_GID`); = uid владельца `ORCH_HOST_REPOS_DIR` (ORCH-040) |
|
||
| `ORCH_DOCKER_GID` | `999` | gid группы docker хоста (`group_add`, «МИНА 1» — обязателен для docker.sock); узнать: `getent group docker` |
|
||
| `ORCH_DEPLOY_PROD_TARGET_PORT` | `8500` | (реюз) прод-порт; интерполируется в `command:` прод-сервиса |
|
||
| `ORCH_DEPLOY_SSH_USER` / `ORCH_DEPLOY_HOST_REPO_PATH` | `slin` / `/home/slin/repos/orchestrator` | (реюз) ssh-юзер хука и чекаут платформы на хосте; `REPO=` передаётся хуку явно из конфига |
|
||
|
||
### 2.2. Обязательные ключи идентичности нового хоста
|
||
|
||
| Переменная | Где взять |
|
||
|-----------|-----------|
|
||
| `ORCH_PLANE_API_URL` / `ORCH_PLANE_WEB_URL` / `ORCH_PLANE_WORKSPACE_SLUG` | инсталляция Plane целевого хоста |
|
||
| `ORCH_GITEA_URL` / `ORCH_GITEA_PUBLIC_URL` / `ORCH_GITEA_OWNER` | инсталляция Gitea целевого хоста |
|
||
| `ORCH_PROJECTS_JSON` | **обязателен на новом хосте**: встроенный fallback (`src/projects.py`) несёт Plane-UUID *исходного* хоста — чужие UUID безвредны (не сматчатся), но без своего реестра конвейер не увидит проекты. Сгенерировать: `scripts/onboard_project.py apply` печатает merged-значение |
|
||
| Когерентность портов | сменил прод-порт → синхронно `ORCH_DEPLOY_PROD_TARGET_PORT` ⇄ `WATCHDOG_METRICS_URL` ⇄ `ORCH_POST_DEPLOY_BASE_URL` |
|
||
|
||
Полный справочник всех остальных флагов — `.env.example` (канон) и
|
||
`docs/operations/INFRA.md` (карта env).
|
||
|
||
---
|
||
|
||
## 3. Секреты нового хоста (FR-4 / AC-5)
|
||
|
||
**Нормативно: боевые секреты текущего хоста НЕ копируются ни на одном шаге.**
|
||
Для нового хоста всегда выпускается новый комплект.
|
||
|
||
### 3.1. Генерация локальных webhook-секретов
|
||
|
||
```bash
|
||
python3 scripts/gen_secrets.py # печать .env-фрагмента в stdout
|
||
python3 scripts/gen_secrets.py --write # создать .env (существующий → отказ exit=2)
|
||
python3 scripts/gen_secrets.py --write --force # перезапись только явно
|
||
```
|
||
|
||
Скрипт stdlib-only (`secrets.token_hex(32)` — 32 байта энтропии); повторный
|
||
запуск даёт другие значения; существующий `.env` **никогда не перезаписывается
|
||
молча**.
|
||
|
||
| Секрет | Генерируется | Куда вписать |
|
||
|--------|--------------|--------------|
|
||
| `ORCH_PLANE_WEBHOOK_SECRET` | локально (gen_secrets) | `.env` + настройка webhook в Plane (см. `SETUP_WEBHOOKS.md`) |
|
||
| `ORCH_GITEA_WEBHOOK_SECRET` | локально (gen_secrets) | `.env` + webhook Gitea (создаёт `onboard_project.py apply`) |
|
||
|
||
### 3.2. Чек-лист внешних токенов
|
||
|
||
| Секрет | Где выпустить | Куда вписать | Как проверить |
|
||
|--------|---------------|--------------|---------------|
|
||
| `ORCH_PLANE_API_TOKEN` | Plane UI → Workspace Settings → API tokens | `.env` | `curl -H "X-API-Key: $TOKEN" $ORCH_PLANE_API_URL/api/v1/workspaces/<slug>/projects/` → 200 |
|
||
| `ORCH_PLANE_BOT_*` (7, опциональны) | Plane UI: bot-аккаунты per-агент; пусто → fallback на `ORCH_PLANE_API_TOKEN` | `.env` | комментарий в Plane от имени бота |
|
||
| `ORCH_GITEA_TOKEN` | Gitea UI → Settings → Applications → Generate Token (scope: repo, admin:repo_hook) | `.env` | `curl -H "Authorization: token $TOKEN" $ORCH_GITEA_URL/api/v1/user` → 200 |
|
||
| `ORCH_TELEGRAM_BOT_TOKEN` | BotFather (`/newbot`) | `.env` | `curl https://api.telegram.org/bot$TOKEN/getMe` → ok |
|
||
| `ORCH_TELEGRAM_CHAT_ID` (несекретный) | id чата оператора | `.env` | тестовое сообщение |
|
||
| `WATCHDOG_TG_BOT_TOKEN` / `WATCHDOG_TG_CHAT_ID` | отдельный бот sidecar-watchdog (ORCH-100, независимый канал) | `.env.watchdog` | алерт от sidecar |
|
||
|
||
### 3.3. Полнота
|
||
|
||
`.env.example` — канон 100% ключей старта (секретные значения — только
|
||
плейсхолдеры). Состав вывода `gen_secrets.py` сверяется с `.env.example`
|
||
тестом (`tests/test_secrets_gen.py`).
|
||
|
||
---
|
||
|
||
## 4. Smoke-верификация тиража (FR-5 / AC-3)
|
||
|
||
Процедура «инстанс → тестовый проект → тестовая задача → конвейер доехал» из
|
||
существующих кирпичей; **каждый шаг имеет явный PASS/FAIL**. Итог — однозначный
|
||
вердикт: все шаги PASS ⇒ тираж PASS; любой шаг FAIL ⇒ тираж FAIL (собери логи
|
||
контейнера `docker logs orchestrator` и снапшот `GET /queue` и разбирайся).
|
||
|
||
Воспроизводимость без нового железа: процедура прогоняется на текущей инфре —
|
||
staging-песочница (порт `ORCH_STAGING_PORT`, дефолт 8501) + sandbox-проект.
|
||
Stateless: ни один шаг не предполагает перенос данных/БД/секретов.
|
||
|
||
| # | Шаг | Команда | PASS | FAIL |
|
||
|---|-----|---------|------|------|
|
||
| 0 | Конфиг резолвится | `docker compose config` | резолв без ошибок; при пустом env значения = боевым дефолтам | ошибка интерполяции / неожиданные значения |
|
||
| 1 | Инстанс жив | `curl -fsS http://127.0.0.1:<port>/health` | HTTP 200, `"status":"ok"` | не-200 / таймаут |
|
||
| 2 | Контракты отвечают | `curl -fsS …/queue` и `…/metrics` | JSON со штатными блоками; `/metrics` → `schema_version: 1` | не-JSON / 5xx |
|
||
| 3 | Тестовый проект | `python3 scripts/onboard_project.py plan` → `apply` → `verify` (sandbox) | `verify` зелёный (статусы/лейблы/repo/webhook на месте) | `verify` красный / manual-step не выполнен |
|
||
| 4 | Тестовая задача | создать issue в Plane → статус «To Analyse» | задача в БД, analyst-job виден в `GET /queue` | задача не появилась (webhook/секрет/реестр) |
|
||
| 5 | **Минимальный сигнал «конвейер доехал»** | дождаться окончания `analysis` | артефакты `01`–`04` в ветке задачи: `git ls-tree origin/<branch> docs/work-items/<id>/` | стадия не завершилась / артефактов нет |
|
||
| 6 | Расширенный режим (опционально) | Approved → … → Confirm Deploy | задача дошла до `done` | застряла (разбор по `GET /queue` + логам) |
|
||
|
||
> Ручной запуск deploy-хука на нестандартных портах — всегда с явными
|
||
> `REPO=`/`TARGET_PORT=` (wired-путь оркестратора передаёт их сам из конфига;
|
||
> дефолты хука — staging текущего хоста).
|
||
|
||
---
|
||
|
||
## 5. Что НЕ переносится (stateless, решение 10.06)
|
||
|
||
- БД (`data/orchestrator.db`) — создаётся пустой при первом старте;
|
||
- `.env` / `.env.staging` / `.env.watchdog` — собираются заново (§2–§3);
|
||
- worktree'ы/runs/логи — рабочее состояние, не данные;
|
||
- боевые секреты — никогда (§3).
|
||
|
||
---
|
||
|
||
*RUNBOOK ORCH-101. Поддерживать при добавлении хост-переменных (карта §2 +
|
||
`.env.example` + INFRA.md в том же PR). Анти-регресс возврата хардкодов —
|
||
`tests/test_no_host_hardcodes.py`; параметризация инфра-файлов —
|
||
`tests/test_infra_parametrization.py`.*
|