629 lines
36 KiB
Markdown
629 lines
36 KiB
Markdown
# LITE_SETUP — Lite-тираж: оркестратор + watchdog на вашей инфраструктуре (ORCH-102)
|
||
|
||
> **Golden source Lite-тиража (Type A эпика ORCH-10).** Сквозной маршрут
|
||
> «голый хост → работающий конвейер» для внешнего оператора/заказчика. Каждый шаг —
|
||
> исполняемая команда + явная проверка результата (**Проверка:** / PASS / FAIL).
|
||
> Хост-специфика в командах — только плейсхолдеры `<...>` и `$ENV_VAR`.
|
||
> Тираж **stateless**: данные/задачи/секреты исходного (боевого) хоста **НЕ переносятся**
|
||
> ни на одном шаге — всё создаётся заново (§12).
|
||
> **Быстрый путь — `scripts/setup_lite.py`** (§1.1): интерактивный installer проводит по
|
||
> §2–§12 за один прогон; ручной маршрут ниже остаётся каноном и fallback'ом.
|
||
|
||
---
|
||
|
||
## 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 контейнера,
|
||
не хост-значения; не параметризуются.
|
||
|
||
### 1.1. Быстрый путь: `setup_lite.py` (рекомендуется)
|
||
|
||
Вместо ручного прохода §2–§12 запустите **интерактивный installer** — он сканирует
|
||
предусловия хоста и предлагает доустановить недостающее, обнаруживает ваши инсталляции
|
||
Plane/Gitea (при нескольких — даёт выбрать), запрашивает обязательные ключи **в момент
|
||
установки с немедленной верификацией**, автодетектит хост-параметры, собирает
|
||
`.env`/`.env.watchdog` от канонов (свежие webhook-секреты — кирпичом `gen_secrets.py`),
|
||
поднимает ровно орк+watchdog и регистрирует ваш проект строго кирпичом `onboard_project.py`.
|
||
|
||
```bash
|
||
cd <путь-чекаута> # корень репо orchestrator
|
||
python3 scripts/setup_lite.py # apply (дефолт): интерактивная установка
|
||
python3 scripts/setup_lite.py plan # read-only диагностика (ноль мутаций)
|
||
python3 scripts/setup_lite.py verify # read-only пост-проверка контура
|
||
```
|
||
|
||
**Контракт:** дефолт-режим `apply` — установка «одной командой»; безопасность дефолта
|
||
структурна — фаза 0 любого `apply` ≡ `plan` (read-only скан), каждая мутация хоста — с
|
||
**явного согласия** (per-action consent с печатью точной команды), существующий чужой
|
||
`.env`/`.env.watchdog` **не перетирается** без `--force` (guard managed-маркера), в
|
||
non-TTY без `--yes` — честный выход без зависания. Exit-коды: `0` — все шаги PASS; `2` —
|
||
остановка на ручном шаге (повторный запуск продолжит с него — resume); `1` — ошибка.
|
||
Секреты вводятся скрыто и **никогда не печатаются**; delete-операций скрипт не выполняет
|
||
(лечение — всегда инструкция). Любой ручной шаг ссылается на соответствующий § ниже —
|
||
ручной маршрут §2–§13 остаётся полным каноном и fallback'ом для MANUAL-шагов.
|
||
|
||
**Проверка:** `python3 scripts/setup_lite.py plan` завершается без блокеров (exit 0) —
|
||
PASS; есть блокеры (exit 2) — устраните по выводу и повторите.
|
||
|
||
---
|
||
|
||
## 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 (шаги 0–5; шаг 6 «до `done`» —
|
||
опционально), без форка; каждый шаг несёт явный PASS/FAIL. Lite-предусловия: §2–§10 этого
|
||
дока выполнены, проект заказчика зарегистрирован (§10).
|
||
|
||
Минимальный сигнал «конвейер доехал» (шаги 4–5 чек-листа): создайте issue в Plane-проекте
|
||
и переведите в статус **To Analyse**, затем:
|
||
|
||
```bash
|
||
# задача появилась и analyst-job в очереди:
|
||
curl -fsS http://127.0.0.1:8500/queue | python3 -m json.tool | head -40
|
||
# по завершении стадии analysis — артефакты 01–04 в ветке задачи:
|
||
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, расширен
|
||
ORCH-104):** меняешь шаги тиража (env-ключи, compose-сервисы, маршрут онбординга, smoke) →
|
||
обнови этот док **И установочный скрипт `scripts/setup_lite.py`** В ТОМ ЖЕ 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` (канон ключей).*
|