Files
orchestrator/docs/deployment/LITE_SETUP.md
claude-bot 8351e91382 docs(deployment): ORCH-10a Lite-тираж — LITE_SETUP.md + канон watchdog-конфига + анти-дрейф контур
Закрывает Type A эпика ORCH-10 (поверх 10-common ORCH-101). Docs+tests
(паттерн ORCH-077/092): src/**, docker-compose.yml, Dockerfile, scripts/** —
ноль изменений; конвейер (STAGE_TRANSITIONS/QG_CHECKS/check_*/machine-verdict/
схема БД) — байт-в-байт.

- docs/deployment/LITE_SETUP.md (D1/D2): golden source Lite-тиража — 13
  нормативных разделов в порядке маршрута оператора, каждый шаг =
  fenced-команда + явная «Проверка:»/PASS/FAIL, хост-специфика только
  плейсхолдерами; канон не форкается (статусы/env/вебхуки/smoke — ссылками
  на ONBOARDING §1 / REPLICATION §2–§4 / SETUP_WEBHOOKS; явно — только
  fail-closed Confirm Deploy/STOP и обязательные ключи нового хоста).
- .env.watchdog.example (D5, исход А-4): третий канонический env-example;
  key-set = блок WATCHDOG_* .env.example (19 ключей, токены — пустые
  плейсхолдеры); закрывает ловушку файла-носителя (sidecar читает ТОЛЬКО
  .env.watchdog); C-1 ORCH-100 + когерентность порта в шапке; .env.watchdog
  добавлен в .gitignore (секрет-гигиена, зеркало .env.staging).
- tests/test_lite_setup_doc.py (D8): 25 структурных тестов без
  сети/LLM/subprocess — 13 разделов в порядке D2, кирпичи FR-6.1, key-sync
  watchdog-канона, env-ключи ⊂ .env.example, compose-подмножество (ровно
  орк+watchdog по дефолту, staging за профилем, анти-появление
  plane*/gitea*), fenced-скан FORBIDDEN (импорт из test_no_host_hardcodes)
  + секрет-эвристика с негативным самочеком, «22 статуса» сверкой импорта
  plane_sync._PLANE_NAME_TO_KEY, перекрёстность.
- Перекрёстные доки (FR-7): REPLICATION.md §1 (Type A — Lite →  ORCH-102 +
  ссылка), README.md (способность Lite + docs/deployment/ в структуре),
  INFRA.md (.env.watchdog в секрет-нормативе + ссылка на deployment),
  CLAUDE.md (блок ORCH-102), CHANGELOG.md.

Нормативы разделов: Gitea — branch protection на main НЕ включать (D3 /
ADR D10 ORCH-009 / INV-4), pre-receive не вводится, ОДИН глобальный
webhook-секрет; staging-вилка опциональна (D6); источник кода —
параметризованный git clone <ORCHESTRATOR_GIT_URL> (D7); stateless —
данные/задачи/секреты боевого хоста НЕ переносятся (AC-3).

Тесты: pytest tests/ -q — 1789 passed (полный регресс зелёный).

Refs: ORCH-102

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 00:42:15 +03:00

597 lines
33 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# LITE_SETUP — Lite-тираж: оркестратор + watchdog на вашей инфраструктуре (ORCH-102)
> **Golden source Lite-тиража (Type A эпика ORCH-10).** Сквозной маршрут
> «голый хост → работающий конвейер» для внешнего оператора/заказчика. Каждый шаг —
> исполняемая команда + явная проверка результата (**Проверка:** / PASS / FAIL).
> Хост-специфика в командах — только плейсхолдеры `<...>` и `$ENV_VAR`.
> Тираж **stateless**: данные/задачи/секреты исходного (боевого) хоста **НЕ переносятся**
> ни на одном шаге — всё создаётся заново (§12).
---
## 1. Рамка Lite
**Что разворачиваем:** два контейнера платформы — `orchestrator` (конвейер, порт
`ORCH_DEPLOY_PROD_TARGET_PORT`, дефолт 8500) и `orchestrator-watchdog`
(независимый sidecar-мониторинг). Третий сервис `orchestrator-staging` существует в том же
compose-файле, но строго за профилем `staging` и в базовом Lite-контуре не поднимается (§9).
**Что заказчик ставит и администрирует сам** (вне этой инструкции — как продукты):
- **Plane** (task-management) — своя инсталляция; здесь только её *подключение* (§5);
- **Gitea** (git-хостинг) — своя инсталляция; здесь только её *подключение* (§6);
- **Telegram-боты** (нотификации) — свои боты (§8);
- **LLM-доступ** (claude CLI + node) — свой дистрибутив и аутентификация (§7).
**Границы слоёв тиража** (10-common vs Lite vs Bundled) — `docs/operations/REPLICATION.md` §1.
Этот док собирает кирпичи 10-common (карта env, секреты, smoke) в один маршрут и не форкает их.
**Платформенные конвенции (не менять):**
- репо платформы обязан называться **`orchestrator`** (узел безопасности `SELF_HOSTING_REPO`);
- имена compose-сервисов/профиля (`orchestrator`, `orchestrator-watchdog`,
`orchestrator-staging`, профиль `staging`) — константы платформы;
- контейнерные пути (`/app/data`, `/repos`, `/opt/claude-code`) — layout контейнера,
не хост-значения; не параметризуются.
---
## 2. Предусловия хоста
Поддерживаемый контур: **Linux x86_64, Docker Engine + Compose v2, git, python3, node** +
дистрибутив claude-code на хосте. Вне контура — вне гарантии Lite. Каждое предусловие —
команда проверки; все проверки PASS → можно переходить к §3.
**2.1. ОС и базовые зависимости.**
```bash
uname -sm # Linux x86_64
docker --version # Docker Engine
docker compose version # Compose v2
git --version
python3 --version # 3.x (для scripts/*.py)
node --version # путь к бинарю станет ORCH_HOST_NODE_BIN
```
**Проверка:** все команды отвечают версиями без ошибок — PASS; любая отсутствует — FAIL
(доставить пакет средствами вашего дистрибутива).
**2.2. Пользователь-владелец и uid/gid (инвариант ORCH-040).** Контейнеры бегут под
`ORCH_RUN_UID:ORCH_RUN_GID` — это обязан быть uid:gid владельца каталога репозиториев
`ORCH_HOST_REPOS_DIR`, иначе git-артефакты конвейера не запишутся.
```bash
id -u <deploy-user>; id -g <deploy-user> # значения для ORCH_RUN_UID / ORCH_RUN_GID
mkdir -p <путь-каталога-репозиториев> # станет ORCH_HOST_REPOS_DIR
stat -c '%u:%g' <путь-каталога-репозиториев> # обязан совпасть с ORCH_RUN_UID:ORCH_RUN_GID
```
**Проверка:** uid:gid владельца каталога = будущие `ORCH_RUN_UID`/`ORCH_RUN_GID` — PASS.
**2.3. Группа docker (доступ к docker.sock).**
```bash
getent group docker # третье поле — gid, станет ORCH_DOCKER_GID
```
**Проверка:** строка вида `docker:x:<gid>:...` есть — PASS; группы нет — FAIL
(установите Docker корректно / создайте группу).
**2.4. Каталог ssh-ключей деплой-хука** (понадобится для git-push артефактов агентов и
деплой-хуков; монтируется в `$HOME/.ssh` акторов).
```bash
mkdir -p <путь-ssh-каталога> # станет ORCH_HOST_SSH_DIR
ssh-keygen -t ed25519 -f <путь-ssh-каталога>/id_ed25519 -N "" -C "orchestrator@<host>"
ls -l <путь-ssh-каталога> # ключи читаемы пользователем из 2.2
```
**Проверка:** каталог существует, ключи на месте, владелец — пользователь из 2.2 — PASS.
Публичный ключ добавьте в Gitea (Settings → SSH Keys) на шаге §6.
**2.5. Свободные порты** (дефолты платформы: 8500 — прод, 8501 — staging).
```bash
ss -ltn | grep -E ':(8500|8501)\b' || echo "ports free"
```
**Проверка:** вывод `ports free` — PASS. Порты заняты → выберите другие и на шаге §4
синхронно задайте `ORCH_DEPLOY_PROD_TARGET_PORT``WATCHDOG_METRICS_URL`
`ORCH_POST_DEPLOY_BASE_URL``ORCH_STAGING_PORT` ≠ прод-порт — guard ORCH-058 fail-closed).
---
## 3. Перенос кода
Переносится **только код** — чекаут репо `orchestrator`. **НИКАКИХ** данных, БД или `.env`
с исходного хоста (норматив §12). Watchdog отдельно не переносится: его код — каталог
`watchdog/` того же репо, образ собирается локально compose'ом.
```bash
git clone <ORCHESTRATOR_GIT_URL> <путь-чекаута> # путь станет ORCH_DEPLOY_HOST_REPO_PATH
cd <путь-чекаута>
```
Конкретный канал дистрибуции (`<ORCHESTRATOR_GIT_URL>` — зеркало/архив/доступ к
Gitea поставщика) согласуйте с поставщиком платформы; опционально — `--branch <тег-среза>`.
**Проверка:**
```bash
git -C <путь-чекаута> log --oneline -1 # коммит виден
ls <путь-чекаута>/docker-compose.yml <путь-чекаута>/watchdog/Dockerfile \
<путь-чекаута>/.env.example <путь-чекаута>/.env.watchdog.example
```
Все файлы на месте — PASS.
---
## 4. Конфигурация
`.env` собирается **с нуля от канона `.env.example`** (100% ключей старта; полная карта
переменных и их семантика — `docs/operations/REPLICATION.md` §2). Дефолт каждого ключа =
значению исходного хоста, поэтому задаёте только то, что отличается у вас.
**4.1. Создать `.env` и выпустить webhook-секреты.**
```bash
cd <путь-чекаута>
cp .env.example .env
python3 scripts/gen_secrets.py # печатает свежие ORCH_PLANE_WEBHOOK_SECRET / ORCH_GITEA_WEBHOOK_SECRET
```
Вставьте оба напечатанных значения в `.env`. Секреты выпускаются **только заново**
боевые не копируются (§12).
**4.2. Обязательные ключи нового хоста** (заполняются в `.env` по ходу §5§8):
| Группа | Ключи | Откуда |
|--------|-------|--------|
| Plane | `ORCH_PLANE_API_URL`, `ORCH_PLANE_WEB_URL`, `ORCH_PLANE_WORKSPACE_SLUG`, `ORCH_PLANE_API_TOKEN` | §5 |
| Gitea | `ORCH_GITEA_URL`, `ORCH_GITEA_PUBLIC_URL`, `ORCH_GITEA_OWNER`, `ORCH_GITEA_TOKEN` | §6 |
| Webhook-секреты | `ORCH_PLANE_WEBHOOK_SECRET`, `ORCH_GITEA_WEBHOOK_SECRET` | 4.1 (`gen_secrets.py`) |
| Telegram | `ORCH_TELEGRAM_BOT_TOKEN`, `ORCH_TELEGRAM_CHAT_ID` | §8 |
| Реестр проектов | `ORCH_PROJECTS_JSON`**обязателен**: встроенный fallback несёт Plane-UUID исходного хоста | §10 |
| Хост-параметры | `ORCH_AGENT_HOME_DIR`, `ORCH_HOST_REPOS_DIR`, `ORCH_HOST_CLAUDE_DIR`, `ORCH_HOST_CLAUDE_JSON`, `ORCH_HOST_SSH_DIR`, `ORCH_HOST_CLAUDE_CODE_DIR`, `ORCH_HOST_NODE_BIN`, `ORCH_RUN_UID`, `ORCH_RUN_GID`, `ORCH_DOCKER_GID`, `ORCH_DEPLOY_HOST_REPO_PATH`, `ORCH_AGENT_GIT_NAME`, `ORCH_GIT_EMAIL_DOMAIN` | значения из §2§3 |
| Порты | `ORCH_DEPLOY_PROD_TARGET_PORT``WATCHDOG_METRICS_URL``ORCH_POST_DEPLOY_BASE_URL`; `ORCH_STAGING_PORT` ≠ прод-порт | §2.5 |
**4.3. Конфиг sidecar-watchdog — отдельный файл-носитель.** Sidecar-контейнер читает
**ТОЛЬКО `.env.watchdog`**; ключ `WATCHDOG_ENABLED` (и любой другой `WATCHDOG_*`),
положенный в `.env`, для sidecar **инертен**.
```bash
cp .env.watchdog.example .env.watchdog
# заполнить два ключа: WATCHDOG_TG_BOT_TOKEN / WATCHDOG_TG_CHAT_ID (бота создадим в §8)
```
**Проверка (резолв всей конфигурации):**
```bash
docker compose config >/dev/null && echo "compose config: PASS"
```
`PASS` без ошибок интерполяции — конфигурация согласована; ошибка — FAIL (ищите
незакрытую кавычку/невалидный JSON в `ORCH_PROJECTS_JSON`).
---
## 5. Подключение Plane
Инсталляция Plane — ваша; платформа подключается к ней API-токеном и webhook'ом.
**5.1. Workspace и проект.** Создайте workspace (его slug → `ORCH_PLANE_WEB_URL` /
`ORCH_PLANE_WORKSPACE_SLUG`) и проект под вашу разработку — через UI Plane.
```bash
# базовая доступность API из хоста оркестратора:
curl -fsS "$ORCH_PLANE_API_URL/api/v1/workspaces/<workspace-slug>/projects/" \
-H "X-API-Key: <plane-api-token>" | head -c 200
```
**Проверка:** HTTP 200 и JSON со списком проектов — PASS; 401/403 — токен (5.2) ещё не
выпущен или не имеет прав.
**5.2. API-токен.** Plane UI → Workspace Settings → API tokens → выпустить токен →
`ORCH_PLANE_API_TOKEN` в `.env`. Токен должен иметь право создавать проекты/статусы
(нужно для `onboard_project.py apply`, §10).
**5.3. Модель статусов — НЕ вручную.** Конвейеру нужны **22 канонических статуса** с
точными именами и группами; их создаёт `python3 scripts/onboard_project.py apply` (§10),
полная таблица — `docs/operations/ONBOARDING.md` §1 (golden source; здесь не дублируется).
Два имени фиксируем явно, потому что они **fail-closed** (без них ветка просто не
активируется, без ошибки): **`Confirm Deploy`** (человеческий гейт прод-деплоя) и
**`STOP`** (отмена задачи; обязан быть в группе `cancelled`).
```bash
# после §10 — проверить, что статусы созданы:
curl -fsS "$ORCH_PLANE_API_URL/api/v1/workspaces/<workspace-slug>/projects/<project-uuid>/states/" \
-H "X-API-Key: $ORCH_PLANE_API_TOKEN" | python3 -m json.tool | grep -c '"name"'
```
**Проверка:** счётчик имён = 22 (или больше, если в проекте остались дефолтные статусы
Plane) и среди них `Confirm Deploy` и `STOP` — PASS.
**5.4. Webhook + HMAC.** Приёмник — `POST https://<orchestrator-public-host>/webhook/plane`;
подпись — заголовок `X-Plane-Signature` (HMAC-SHA256, hex digest); секрет — значение
`ORCH_PLANE_WEBHOOK_SECRET` из 4.1. События: Issue, Issue Comment.
**Каверза Plane CE:** webhook **не экспонирован во внешнем `/api/v1`** — настраивается
одним из двух путей.
*Путь А — UI (если ваша сборка Plane его показывает):* Workspace Settings → Webhooks →
Add Webhook → URL + Secret (значение `ORCH_PLANE_WEBHOOK_SECRET`) → события Issue,
Issue Comment → Save.
*Путь Б — прямой SQL в Postgres инсталляции Plane:*
```bash
WORKSPACE_ID=$(docker exec -e PGPASSWORD=<plane-db-password> <plane-db-container> \
psql -U plane -d plane -t -A -c "SELECT id FROM workspaces WHERE slug='<workspace-slug>'")
WEBHOOK_ID=$(cat /proc/sys/kernel/random/uuid)
docker exec -e PGPASSWORD=<plane-db-password> <plane-db-container> psql -U plane -d plane -c "
INSERT INTO webhooks (id, created_at, updated_at, deleted_at, workspace_id, url, is_active,
secret_key, project, issue, module, cycle, issue_comment, is_internal, version)
VALUES ('${WEBHOOK_ID}', NOW(), NOW(), NULL, '${WORKSPACE_ID}',
'https://<orchestrator-public-host>/webhook/plane',
true, '<значение ORCH_PLANE_WEBHOOK_SECRET>', true, true, false, false, true, false, 'v1');
"
```
**Проверка:**
```bash
docker exec -e PGPASSWORD=<plane-db-password> <plane-db-container> psql -U plane -d plane -c \
"SELECT url, is_active FROM webhooks;"
```
Строка с вашим URL и `is_active = t` — PASS. Сквозная проверка доставки — §11 (smoke);
generic-образец команд и формат подписи — `docs/operations/SETUP_WEBHOOKS.md`.
---
## 6. Подключение Gitea
**6.1. Токен.** Gitea UI → Settings → Applications → Generate Token, scope: `repo`,
`admin:repo_hook``ORCH_GITEA_TOKEN` в `.env`.
```bash
curl -fsS -H "Authorization: token $ORCH_GITEA_TOKEN" "$ORCH_GITEA_URL/api/v1/user" | head -c 200
```
**Проверка:** HTTP 200 с JSON вашего пользователя — PASS; владелец репозиториев
(организация/пользователь) → `ORCH_GITEA_OWNER`, браузерный URL → `ORCH_GITEA_PUBLIC_URL`.
**6.2. Репо проекта.** Создаёт `onboard_project.py apply` (§10) — или вручную (пустой
репо + initial push). Чекаут обязан появиться в `$ORCH_HOST_REPOS_DIR/<repo>` (общий
каталог репозиториев из §2.2). Публичный ключ из §2.4 добавьте в Gitea
(Settings → SSH Keys), чтобы акторы могли пушить.
```bash
git -C "$ORCH_HOST_REPOS_DIR" clone <git-url-репо-проекта> <repo>
stat -c '%u:%g' "$ORCH_HOST_REPOS_DIR/<repo>" # владелец = ORCH_RUN_UID:ORCH_RUN_GID
```
**Проверка:** чекаут на месте, владелец совпадает — PASS.
**6.3. Per-repo webhook.** Создаёт `onboard_project.py apply` (§10). Параметры (если
вручную): URL `https://<orchestrator-public-host>/webhook/gitea`, content type `json`,
события **`push` / `pull_request` / `status`**, branch filter `*`, подпись —
`X-Gitea-Signature` (HMAC-SHA256). Секрет — **ОДИН глобальный `ORCH_GITEA_WEBHOOK_SECRET`
на ВСЕ репо** (приёмник валидирует только его; «свой секрет на репо» сломал бы HMAC
остальных).
```bash
curl -fsS -H "Authorization: token $ORCH_GITEA_TOKEN" \
"$ORCH_GITEA_URL/api/v1/repos/<owner>/<repo>/hooks" | python3 -m json.tool
```
**Проверка:** hook с вашим URL и тремя событиями существует, `active: true` — PASS.
**6.4. Норматив защиты `main` (ВАЖНО).** **Branch protection на `main` НЕ включать**
(никаких required-approvals / required-status-checks): merge-актор конвейера мержит PR
строго через Gitea PR-merge API (INV-4), и protection-правила дают 405/409-класс отказов →
ложные HOLD задач (ADR D10 ORCH-009). **pre-receive хуки платформа не вводит** и не
проверяет — защита `main` держится конвенцией (агенты не пушат `main`) + скоупом токенов.
```bash
curl -fsS -H "Authorization: token $ORCH_GITEA_TOKEN" \
"$ORCH_GITEA_URL/api/v1/repos/<owner>/<repo>/branch_protections" | python3 -m json.tool
```
**Проверка:** пустой список `[]` — PASS; есть правила на `main` — FAIL (удалите их,
симптом «PR не мержится / HOLD» — §13.7).
---
## 7. LLM (claude CLI)
Агенты конвейера — процессы claude CLI **внутри контейнера**, но дистрибутив, node и
аутентификация живут **на хосте** и пробрасываются маунтами (источники маунтов =
ключи `.env`).
**7.1. Дистрибутив claude-code и node.** Установите claude-code (npm-дистрибутив
Anthropic) и node на хост. Пути → `.env`:
```bash
which node # → ORCH_HOST_NODE_BIN
npm root -g # каталог глобальных модулей
ls "<npm-root>/@anthropic-ai/claude-code" # → ORCH_HOST_CLAUDE_CODE_DIR
```
**Проверка:** каталог дистрибутива существует и непуст — PASS. Внутри контейнера бинарь
доступен как `ORCH_CLAUDE_BIN` (дефолт менять не нужно).
**7.2. Аутентификация CLI.** Выполните первичный интерактивный логин claude CLI **на
хосте** под пользователем из §2.2 (по инструкции Anthropic к claude-code). Логин создаёт
каталог `~/.claude` и файл `~/.claude.json` — их пути задайте в `ORCH_HOST_CLAUDE_DIR` /
`ORCH_HOST_CLAUDE_JSON`.
```bash
claude --version # CLI работает
sudo -u "#<uid-из-2.2>" test -r <путь-~/.claude>/.credentials.json && echo "creds: PASS"
```
**Проверка:** версия печатается; `creds: PASS` — креды читаемы uid'ом контейнера
(иначе — `chown -R <uid>:<gid>` каталога, симптом §13.3).
**7.3. Модели агентов.** Резолв модели/эффорта — только из конфига (ORCH-41/74):
дефолты канона уже в `.env.example` (`ORCH_AGENT_MODEL_DEFAULT`,
`ORCH_AGENT_EFFORT_DEFAULT` и per-агент ключи рядом) — менять не обязательно.
```bash
grep -E '^ORCH_AGENT_(MODEL|EFFORT)_DEFAULT=' .env
```
**Проверка:** оба ключа присутствуют и непусты — PASS.
---
## 8. Telegram
Каналов **два и они независимы** (C-1 ORCH-100): бот live-трекера оркестратора и
**отдельный** бот sidecar-watchdog. Токен орка для watchdog переиспользовать
**ЗАПРЕЩЕНО** — упавший орк не сможет сообщить о себе своим же ботом.
**8.1. Бот трекера.** BotFather → `/newbot` → токен → `ORCH_TELEGRAM_BOT_TOKEN` в `.env`.
```bash
curl -fsS "https://api.telegram.org/bot<токен-трекера>/getMe"
# напишите боту любое сообщение (или добавьте его в чат), затем:
curl -fsS "https://api.telegram.org/bot<токен-трекера>/getUpdates" | python3 -m json.tool | grep -m1 '"id"'
```
**Проверка:** `getMe``"ok":true`; `id` чата из `getUpdates``ORCH_TELEGRAM_CHAT_ID`
PASS.
**8.2. Watchdog-бот (отдельный).** BotFather → `/newbot` ещё раз → токен и chat-id →
**`.env.watchdog`** (`WATCHDOG_TG_BOT_TOKEN` / `WATCHDOG_TG_CHAT_ID`). Помните о
файле-носителе: эти ключи работают только в `.env.watchdog` (§4.3).
```bash
curl -fsS "https://api.telegram.org/bot<токен-watchdog>/getMe"
grep -E '^WATCHDOG_TG_(BOT_TOKEN|CHAT_ID)=.+' .env.watchdog
```
**Проверка:** `getMe``"ok":true`; оба ключа в `.env.watchdog` непусты — PASS.
---
## 9. Запуск
**9.1. Базовый Lite-контур (дефолт): орк + watchdog.**
```bash
cd <путь-чекаута>
docker compose config --services # ровно: orchestrator, orchestrator-watchdog, orchestrator-staging
docker compose up -d --build
docker compose ps
```
**Проверка:** запущены **ровно два** контейнера — `orchestrator` и
`orchestrator-watchdog`; `orchestrator-staging` НЕ поднялся (он строго за
`profiles: [staging]`) — PASS. Поднялось что-то ещё/меньше — FAIL.
**9.2. Health-чек контрактов.**
```bash
curl -fsS http://127.0.0.1:8500/health
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -20
curl -fsS http://127.0.0.1:8500/metrics | python3 -m json.tool | head -10
```
**Проверка:** `/health` → HTTP 200, `"status":"ok"`; `/queue` → штатный JSON
(счётчики очереди); `/metrics` → JSON со `"schema_version": 1` — PASS. (Порт замените,
если меняли `ORCH_DEPLOY_PROD_TARGET_PORT`.)
**9.3. Вилка staging (опционально).** Базовому контуру «гонять СВОИ проекты» staging
**не нужен**. Он нужен ТОЛЬКО если вы регистрируете проект `orchestrator` (self-hosting
развитие самой платформы у себя): стадия `deploy-staging` требует песочницу на
`ORCH_STAGING_PORT` (изолированная БД `./data/staging`; guard ORCH-058: staging-порт ≠
прод-порт, fail-closed).
```bash
cp .env.staging.example .env.staging # заполнить по аналогии с .env
docker compose --profile staging up -d orchestrator-staging
curl -fsS http://127.0.0.1:8501/health
```
**Проверка (только для этой вилки):** `/health` staging → 200 — PASS.
---
## 10. Регистрация проекта заказчика
Onboarding-CLI создаёт Plane-проект с 22 статусами и лейблами (`autoApprove` /
`autoDeploy` / `Bug`), Gitea-репо с webhook'ом, скелет репо (kit) и печатает merged-реестр.
Полный runbook — `docs/operations/ONBOARDING.md`; Lite-последовательность:
```bash
cd <путь-чекаута>
python3 scripts/onboard_project.py plan \
--name "<имя проекта>" --description "<зачем проект>" \
--repo <repo> --prefix <PREFIX> \
--stack "<стек>" --test-cmd "<команда тестов>" \
--prod-port <порт-прода-проекта> --staging-port <порт-staging-проекта> \
--webhook-url https://<orchestrator-public-host>/webhook/gitea
# план устроил → тот же вызов с apply; затем read-only контроль:
python3 scripts/onboard_project.py verify <те же аргументы>
```
**Проверка:** `apply` завершился без ошибок (exit 0; `2` = остались 🖐 ручные шаги — выполните
их по отчёту), `verify` зелёный — PASS.
Дальше реестр и рестарт:
```bash
# 1) строку ORCH_PROJECTS_JSON=[...] из отчёта apply вставить в .env (заменить целиком);
# 2) дождаться тихого окна и управляемо перезапустить орк:
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -20 # нет running-job
docker compose up -d --force-recreate orchestrator
# 3) убедиться, что инстанс жив и реестр подхвачен:
curl -fsS http://127.0.0.1:8500/health
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -20
```
**Проверка:** `/health` → 200 после рестарта; в Plane создан проект со статусами
(см. §5.3), в Gitea — репо с webhook (§6.3) — PASS.
---
## 11. Smoke: «конвейер доехал»
Процедура — чек-лист `docs/operations/REPLICATION.md` §4 (шаги 05; шаг 6 «до `done`» —
опционально), без форка; каждый шаг несёт явный PASS/FAIL. Lite-предусловия: §2§10 этого
дока выполнены, проект заказчика зарегистрирован (§10).
Минимальный сигнал «конвейер доехал» (шаги 45 чек-листа): создайте issue в Plane-проекте
и переведите в статус **To Analyse**, затем:
```bash
# задача появилась и analyst-job в очереди:
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -40
# по завершении стадии analysis — артефакты 0104 в ветке задачи:
git -C "$ORCH_HOST_REPOS_DIR/<repo>" fetch origin
git -C "$ORCH_HOST_REPOS_DIR/<repo>" ls-tree --name-only origin/<ветка-задачи> "docs/work-items/<id-задачи>/"
```
**Проверка:** в `/queue` виден job задачи; `ls-tree` показывает `01-brd.md`
`04-test-plan.yaml` — PASS.
**Итоговый вердикт:** все шаги чек-листа PASS ⇒ **тираж PASS**; любой шаг FAIL ⇒ тираж
FAIL — соберите `docker logs orchestrator --tail 100` и снапшот `GET /queue`, разбор —
§13.
---
## 12. Stateless-проверка
**Нормативно: данные/задачи/секреты боевого (исходного) хоста НЕ переносятся** (зеркало
`docs/operations/REPLICATION.md` §5). БД создаётся **пустой** при первом старте; `.env` /
`.env.staging` / `.env.watchdog` собраны с нуля (§4); секреты — только свежевыпущенные
(`gen_secrets.py` + чек-лист внешних токенов `docs/operations/REPLICATION.md` §3).
Проверка чистоты развёрнутого инстанса (выполнить ДО первой своей задачи):
```bash
ls -la <путь-чекаута>/data/ # БД появилась только после первого старта
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool # счётчики jobs нулевые
```
**Проверка:** в `/queue` нулевые счётчики и НИ ОДНОЙ задачи чужих проектов (никаких
`ORCH-*`/`ET-*` исходного хоста) — PASS. Любая чужая задача/перенесённый файл БД — FAIL:
инстанс собран не stateless, пересоберите `data/` с нуля.
---
## 13. Траблшутинг первичной настройки
Формат: симптом → команда диагностики → лечение.
**13.1. Webhook отвечает 401 / HMAC mismatch.**
```bash
docker logs orchestrator --tail 50 2>&1 | grep -i "webhook\|signature\|401"
```
Лечение: секрет в `.env` (`ORCH_PLANE_WEBHOOK_SECRET` / `ORCH_GITEA_WEBHOOK_SECRET`)
обязан **байт-в-байт** совпадать с секретом в настройке webhook'а (Plane §5.4 / Gitea
§6.3); после правки `.env` — управляемый рестарт (§10). Формат подписи —
`docs/operations/SETUP_WEBHOOKS.md`.
**13.2. Задача в Plane создана, но в оркестраторе не появилась.**
```bash
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -30 # есть ли job
docker logs orchestrator --tail 50 2>&1 | grep -i "ignored\|unknown project"
grep ORCH_PROJECTS_JSON .env # uuid вашего проекта в реестре?
```
Лечение: (а) проект отсутствует/с чужим UUID в `ORCH_PROJECTS_JSON` → поправить реестр
(§10) + рестарт; (б) webhook не доставлен → Plane: `SELECT url, is_active FROM webhooks;`
(§5.4), Gitea: Recent Deliveries в настройках hook'а; (в) подпись → §13.1.
**13.3. claude CLI не найден / не аутентифицирован (агент падает на старте).**
```bash
docker exec orchestrator /usr/bin/claude --version
sudo -u "#<uid-из-2.2>" test -r <путь-~/.claude>/.credentials.json && echo "creds: PASS"
```
Лечение: маунты указывают на фактические пути хоста (`ORCH_HOST_CLAUDE_CODE_DIR`,
`ORCH_HOST_NODE_BIN`, `ORCH_HOST_CLAUDE_DIR`, `ORCH_HOST_CLAUDE_JSON` в `.env`); креды
читаемы uid'ом из §2.2 (`chown -R <uid>:<gid>`); при невалидной сессии — повторный логин
на хосте (§7.2) + `docker compose up -d --force-recreate orchestrator`.
**13.4. `docker.sock: permission denied` в логах орка/watchdog.**
```bash
getent group docker # фактический gid
grep ORCH_DOCKER_GID .env # gid в конфиге
```
Лечение: значения обязаны совпадать → выставить `ORCH_DOCKER_GID` = фактическому gid и
`docker compose up -d --force-recreate`.
**13.5. `Permission denied` при создании worktree (права `/repos`, ORCH-040/057).**
```bash
stat -c '%u:%g' "$ORCH_HOST_REPOS_DIR" "$ORCH_HOST_REPOS_DIR/<repo>"
grep -E '^ORCH_RUN_(UID|GID)=' .env
```
Лечение: владелец каталога репо обязан совпадать с `ORCH_RUN_UID:ORCH_RUN_GID`
(§2.2) → `chown -R <uid>:<gid> "$ORCH_HOST_REPOS_DIR"` (включая legacy root-owned файлы)
и пересоздать контейнер.
**13.6. Telegram молчит (нет карточек/алертов).**
```bash
curl -fsS "https://api.telegram.org/bot<токен-трекера>/getMe"
curl -fsS "https://api.telegram.org/bot<токен-watchdog>/getMe"
grep -E '^ORCH_TELEGRAM_(BOT_TOKEN|CHAT_ID)=.+' .env
grep -E '^WATCHDOG_TG_(BOT_TOKEN|CHAT_ID)=.+' .env.watchdog
```
Лечение: оба бота отвечают `"ok":true`; chat-id корректен (бот добавлен в чат / получил
сообщение); ключи watchdog-бота лежат именно в `.env.watchdog``.env` они инертны,
§4.3); пустой токен = режим «логи без отправки» (fail-safe, не ошибка).
**13.7. PR задачи не мержится / задача в HOLD.** Первая проверка — **не включена ли
branch protection на `main`** (§6.4):
```bash
curl -fsS -H "Authorization: token $ORCH_GITEA_TOKEN" \
"$ORCH_GITEA_URL/api/v1/repos/<owner>/<repo>/branch_protections" | python3 -m json.tool
```
Лечение: непустой список правил на `main` → удалить (норматив §6.4); merge выполняет
PR-merge API оркестратора, ручной merge не требуется.
---
*Golden source Lite-тиража (ORCH-102, ADR-001). **Норматив сопровождения (NFR-5):**
меняешь шаги тиража (env-ключи, compose-сервисы, маршрут онбординга, smoke) → обнови
этот док В ТОМ ЖЕ PR (правило агентов №2). Полноту и гигиену дока держит структурный
анти-дрейф тест `tests/test_lite_setup_doc.py`; кирпичи-каноны: REPLICATION.md (карта
env §2, секреты §3, smoke §4), ONBOARDING.md (статусы §1, онбординг), SETUP_WEBHOOKS.md
(формат вебхуков), `.env.example` / `.env.watchdog.example` (канон ключей).*