# 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`.*
