--- work_item: ORCH-104 stage: analysis author_agent: analyst status: ready-for-review created_at: 2026-06-11 model_used: claude-opus-4-8 --- # 02 — ТЗ (TRZ): ORCH-104 — Установочный скрипт Lite-тиража (интерактивный installer) Work Item: **ORCH-104** · Repo: **orchestrator** · Стадия: analysis > ТЗ описывает **что** должно измениться и **где** (модули/контракты/артефакты). **Как** > (имя/режимы скрипта, эвристики discovery, степень автоматизации отдельных шагов) — решает > архитектор в `06-adr/`. Тип изменения — **scripts + docs + tests** (паттерн ORCH-009/103): > рантайм `src/**` байт-в-байт. --- ## 1. Сводка изменения Ввести **единый операторский установочный скрипт Lite-тиража**: один файл в `scripts/`, который интерактивно проводит внешнего оператора по маршруту `docs/deployment/LITE_SETUP.md` §2–§12 — сканирует предусловия хоста и предлагает доустановить недостающее, обнаруживает инсталляции Plane/Gitea (при нескольких — даёт выбрать), запрашивает обязательные ключи в момент установки с немедленной верификацией, автодетектит хост-параметры, собирает `.env`/`.env.watchdog` от канонов, поднимает Lite-контур, регистрирует проект заказчика кирпичом `onboard_project.py` и выдаёт итоговый вердикт PASS/MANUAL/FAIL с exit-кодами `0/2/1`. Паттерн — `bootstrap_bundle.py` (ORCH-103): step-движок check→ensure, stdlib-only, no-delete, manual-checkpoint с верификацией, идемпотентный повтор. Канон LITE_SETUP.md не форкается — скрипт становится в нём рекомендованным быстрым путём, ручной маршрут сохраняется. --- ## 2. Задействованные модули / пути | Путь | Действие | Роль в задаче | |------|----------|---------------| | `scripts/setup_lite.py` *(имя-кандидат; финал — архитектор)* | **создать** | единый установочный CLI: step-движок, скан, discovery, интерактивный сбор, сборка конфигов, запуск, отчёт | | `scripts/gen_secrets.py` | переиспользовать, **не менять** | кирпич выпуска webhook-секретов (субпроцесс — паттерн AC-7 ORCH-103: канон-знания только субпроцессами кирпичей) | | `scripts/onboard_project.py` | переиспользовать, **не менять** | кирпич регистрации проекта: Plane-проект + 22 статуса + лейблы, Gitea-репо + webhook, merged-`ORCH_PROJECTS_JSON` | | `docs/deployment/LITE_SETUP.md` | **обновить** | скрипт = рекомендованный быстрый путь; ручной маршрут остаётся каноном-fallback; размещение не ломает пиннинг «13 разделов в порядке» ЛИБО тест обновляется синхронно | | `tests/test_setup_lite_script.py` *(имя-кандидат)* | **создать** | анти-дрейф + unit чистых функций (по образцу `tests/test_bootstrap_script.py`) | | `tests/test_lite_setup_doc.py` | обновить **при необходимости** | если меняется пиннингуемая структура LITE_SETUP.md (разделы/кирпичи) | | `CHANGELOG.md` | обновить | запись `feat:` | | `docs/overview/` | обновить **при необходимости** | если витрина описывает маршрут Lite-тиража (таблица соответствия — в индексе витрины, правило агентов №2/ORCH-011) | | `src/**`, `docker-compose.yml`, `Dockerfile`, `.env.example`, `.env.watchdog.example` | **НЕ менять** | каноны-источники: скрипт их только читает (шаблоны env, состав сервисов) | --- ## 3. Функциональные требования ### FR-1 — Единая точка входа, режимы, идемпотентность (BR-1, BR-5) - Один файл; запуск `python3 scripts/setup_lite.py …` из корня чекаута репо `orchestrator` на голом python3 (до venv и до `docker compose up`). - Обязательны как минимум: **read-only режим диагностики** (ноль мутаций: скан предусловий + discovery + план шагов — аналог `plan` ORCH-103) и **установочный интерактивный режим** (аналог `apply`); желателен `verify` (read-only пост-проверка). Дефолтный режим и имена — решение архитектора (OQ-1) с учётом паттерна D5 ORCH-009/103 («plan — дефолт») и бизнес-цели «одна команда». - Step-движок **check→ensure**: каждый шаг сперва проверяет «уже сделано?» и при PASS пропускается → повторный запуск идемпотентен; «resume» после manual-step = просто повторный запуск (паттерн `bootstrap_bundle.run_apply`). - **Exit-коды (контракт):** `0` — все шаги PASS; `2` — остановка на manual-step / незавершённое предусловие; `1` — ошибка. - Каждая мутация хоста — с **явного согласия** пользователя (per-action consent); отказ от согласия → честный MANUAL-шаг с печатью эквивалентной команды, не молчаливый пропуск. ### FR-2 — Скан предусловий с офером установки (BR-2; LITE_SETUP §2, §7) - Проверяемый перечень (каждый пункт — отдельный вердикт `OK | MISSING | WARN | MANUAL`): 1. ОС/арх: `uname -sm` = Linux x86_64 (иное → WARN «вне контура Lite»); 2. `docker --version`, `docker compose version` (v2), `git --version`, `python3 --version`, `node --version`; 3. дистрибутив claude-code (`npm root -g` → каталог `@anthropic-ai/claude-code`) и аутентификация CLI (читаемость `~/.claude/.credentials.json` uid'ом из п.5); 4. группа docker (`getent group docker` → gid для `ORCH_DOCKER_GID`); 5. uid/gid пользователя-владельца и каталога репозиториев (`ORCH_HOST_REPOS_DIR`, инвариант ORCH-040: владелец = `ORCH_RUN_UID:ORCH_RUN_GID`); 6. каталог ssh-ключей (`ORCH_HOST_SSH_DIR`): существование/ключи; 7. свободность портов (прод/staging, дефолты 8500/8501). - Для каждого `MISSING`: определить пакетный менеджер хоста (например apt/dnf/yum/zypper — точный набор фиксирует архитектор) → предложить **конкретную команду установки** → выполнить её ТОЛЬКО с явного согласия; для node+claude-code — офер `npm install -g @anthropic-ai/claude-code`; для ssh-ключей — офер `ssh-keygen` (+ печать pubkey как manual-step «добавить в Gitea», §2.4/§6.2). - Неопределимый дистрибутив/менеджер, отказ пользователя, sudo-недоступность → честный **MANUAL** с готовыми командами и ссылкой на § LITE_SETUP; молчаливый пропуск запрещён. - Интерактивный OAuth-логин claude CLI скрипт **не выполняет** — только верифицирует результат (§7.2) и выдаёт manual-step. ### FR-3 — Discovery инсталляций Plane/Gitea (BR-4) - Источник — **локальный Docker**: перечисление контейнеров, группировка в «инсталляции» по метке compose-проекта (`com.docker.compose.project`), опознание по именам образов (Plane: `makeplane/*`-семейство; Gitea: `gitea/*`) и published-портам; точные эвристики/паттерны — ADR. Из кандидата выводятся **предлагаемые** URL (`ORCH_PLANE_API_URL`/`ORCH_PLANE_WEB_URL`; `ORCH_GITEA_URL`/`ORCH_GITEA_PUBLIC_URL`). - Поведение по числу найденных: **0** → ручной ввод URL + честная подсказка «Lite не устанавливает Plane/Gitea; нет инфраструктуры — см. Bundled (BUNDLED_SETUP.md)»; **1** → префилл по умолчанию (с подтверждением); **≥2** → нумерованный список (проект, образы, порты) + выбор. Пункт **«ввести вручную»** доступен всегда. - **Best-effort, never-block:** docker недоступен / ошибка перечисления / не-docker инсталляция (native/k8s) → ручной ввод, не FAIL. - Discovery заполняет только кандидаты URL; **токены всегда вводит пользователь** (FR-4); выбранный кандидат всё равно проходит верификацию FR-4. ### FR-4 — Интерактивный сбор обязательных ключей с немедленной верификацией (BR-3) - Покрывается карта обязательных ключей нового хоста — **§4.2 LITE_SETUP** (группы: Plane, Gitea, webhook-секреты, Telegram, `ORCH_PROJECTS_JSON`, хост-параметры, порты). - Секретные значения вводятся **скрыто** (`getpass`-класс ввода) и **никогда не печатаются** (ни в stdout, ни в лог; только имена ключей) — паттерн NFR-3 ORCH-103. - Немедленная верификация каждого введённого значения фактическим вызовом: - Plane: `GET $ORCH_PLANE_API_URL/api/v1/workspaces//projects/` c `X-API-Key` → 200; - Gitea: `GET $ORCH_GITEA_URL/api/v1/user` c токеном → 200 (+ владелец → `ORCH_GITEA_OWNER`); - Telegram: `getMe` → `"ok":true`; helper определения chat-id через `getUpdates`; - публичный URL оркестратора (для webhook'ов) — синтаксическая валидация + предупреждение, что достижимость со стороны Plane/Gitea проверится на smoke. - Неуспех верификации → re-prompt с диагнозом (ограниченное число попыток — паттерн `manual_checkpoint(max_tries=3)`), затем честный MANUAL/остановка exit 2 — не бесконечный цикл и не молчаливое принятие. - **non-TTY** (нет интерактива): честный отказ с подсказкой ЛИБО неинтерактивная альтернатива (флаги/answers-file — механизм решает архитектор, OQ-2); зависание недопустимо. ### FR-5 — Автодетект хост-параметров и когерентность портов (BR-2, BR-6) - Автоматически определяются и НЕ спрашиваются у пользователя (только подтверждение): `ORCH_RUN_UID`/`ORCH_RUN_GID` (uid/gid владельца `ORCH_HOST_REPOS_DIR` / текущего пользователя), `ORCH_DOCKER_GID` (`getent group docker`), `ORCH_HOST_NODE_BIN` (`which node`), `ORCH_HOST_CLAUDE_CODE_DIR` (`npm root -g`), `ORCH_AGENT_HOME_DIR` / `ORCH_HOST_CLAUDE_DIR` / `ORCH_HOST_CLAUDE_JSON` (HOME пользователя из §2.2), `ORCH_DEPLOY_HOST_REPO_PATH` (корень чекаута). - Порты: busy-check (`ss`/socket) прод- и staging-портов; занят → предложить альтернативу. Смена прод-порта → **синхронно** согласовать тройку `ORCH_DEPLOY_PROD_TARGET_PORT` ⇄ `WATCHDOG_METRICS_URL` ⇄ `ORCH_POST_DEPLOY_BASE_URL` (§2.5/§4.2). `ORCH_STAGING_PORT` == прод-порт → **отказ fail-closed** (инвариант ORCH-058, усилен ORCH-101). ### FR-6 — Сборка `.env` / `.env.watchdog` (BR-5; LITE_SETUP §4) - `.env` собирается **от канона `.env.example`** (принцип ORCH-101: дефолт = боевое значение; записываются только собранные отличия нового хоста). `.env.watchdog` — от `.env.watchdog.example` (ключи `WATCHDOG_TG_BOT_TOKEN`/`WATCHDOG_TG_CHAT_ID` кладутся **только туда** — ловушка файла-носителя §4.3). - Webhook-секреты — **кирпичом `gen_secrets.py`** (свежий выпуск; боевые секреты не используются — stateless §12 / REPLICATION §3). - **Существующий `.env`/`.env.watchdog` → отказ** (exit 2, внятное сообщение); перезапись — только явный force-флаг (паттерн `gen_secrets --force` / `--force-secrets` ORCH-103). Подсказки-дефолты промптов — из `.env.example`/автодетекта, **никогда** из боевых значений. - После сборки — резолв-проверка `docker compose config` (PASS/FAIL, §4 «Проверка»). ### FR-7 — Подключения и машинная охрана нормативов (BR-3, BR-6) - **Plane (§5):** наличие workspace/доступность API верифицируются (FR-4); статусы/лейблы вручную НЕ создаются (только кирпич onboarding, §5.3). **Webhook (§5.4, каверза Plane CE):** Path A (UI) = manual-checkpoint с инструкцией и верификацией; Path Б (SQL в Postgres Plane) = только с **явного согласия** + подтверждённый пользователем контейнер БД Plane + пост-верификация (`SELECT url, is_active FROM webhooks`); выбор степени автоматизации — ADR (OQ-3). Молчаливый пропуск запрещён. - **Gitea (§6):** токен верифицирован (`/api/v1/user`); **branch protection на `main`:** непустой `branch_protections` → **FAIL шага с лечением** (норматив §6.4 / ADR D10 ORCH-009 / INV-4); скрипт правила **сам не удаляет** (no-delete) — только инструкция. Per-repo webhook и репо создаёт кирпич onboarding (FR-9), не сам скрипт. - **Telegram (§8):** два независимых бота; **идентичные токены орка и watchdog → отказ шага** (C-1 ORCH-100 «ЗАПРЕЩЕНО» — машинная проверка, не примечание). - **LLM (§7):** верификация дистрибутива/нод-бинаря/читаемости кредов uid'ом контейнера; наличие `ORCH_AGENT_MODEL_DEFAULT`/`ORCH_AGENT_EFFORT_DEFAULT` в собранном `.env` (§7.3). ### FR-8 — Запуск Lite-контура и health (BR-6; LITE_SETUP §9, §12) - С согласия: `docker compose up -d --build`; проверка состава: запущены **ровно** `orchestrator` + `orchestrator-watchdog`, `orchestrator-staging` НЕ поднят (строго за `profiles: [staging]`). - Health-чек контрактов: `/health` → 200 `"status":"ok"`; `/queue` → штатный JSON; `/metrics` → `"schema_version": 1` (порт — фактический из конфига). - Stateless-проверка чистоты (§12): счётчики jobs нулевые, ни одной задачи чужих проектов; нарушение → FAIL с лечением «пересобрать `data/` с нуля». ### FR-9 — Регистрация проекта заказчика кирпичом onboarding (BR-6, BR-7; LITE_SETUP §10) - Скрипт собирает параметры проекта (имя/описание/repo/prefix/stack/test-cmd/порты/ webhook-url из публичного URL оркестратора) и строит аргументы вызова `onboard_project.py` **детерминированной чистой функцией** (тестируемо без сети). - Последовательность: `plan` → показать пользователю → согласие → `apply` → `verify` (субпроцессы; exit 2 кирпича = остались ручные шаги → транслировать как MANUAL). Степень автоматизации vs «печать готовой команды» — ADR (OQ-6; прецедент драйва — `bootstrap_bundle.step_onboard`). - `ORCH_PROJECTS_JSON` из отчёта `apply` вписывается в `.env` (с согласия); затем управляемый рестарт **только собственного свежеподнятого** контура по процедуре §10: проверка тихого окна (`/queue` без running-job) → `docker compose up -d --force-recreate orchestrator` → пост-проверка `/health`+`/queue`. ### FR-10 — Итоговый отчёт (BR-1, BR-6) - Финальная сводная таблица всех шагов: PASS/FAIL/MANUAL; для каждого MANUAL — что сделать и ссылка на § LITE_SETUP; для FAIL — диагноз и лечение (зеркало §13 траблшутинга). - Smoke первой задачи (§11: issue → «To Analyse» → артефакты `01–04`) выдаётся как завершающая manual-инструкция (вердикт «тираж PASS» — за оператором). ### FR-11 — Документация: канон не форкается (BR-7) - `docs/deployment/LITE_SETUP.md`: скрипт вводится как **рекомендованный быстрый путь** (врезка/подраздел в начале маршрута); ручной маршрут §2–§13 сохраняется как канон и fallback для MANUAL-шагов. Размещение либо не ломает пиннинг `test_doc_exists_with_all_13_sections_in_order`, либо тест обновляется в том же PR. - Норматив сопровождения (NFR-5 ORCH-102) расширяется симметрично: «меняешь шаги тиража → обнови LITE_SETUP.md **и установочный скрипт** в том же PR». --- ## 4. Изменения API **Нет.** Эндпоинты оркестратора не добавляются и не меняются; скрипт — потребитель существующих read-only контрактов (`/health`, `/queue`, `/metrics`) и внешних API (Plane/Gitea/Telegram). ## 5. Изменения схемы БД **Нет.** БД оркестратора не затрагивается (создаётся пустой при первом старте — штатно); БД Plane заказчика — только опциональная webhook-вставка Path Б §5.4 (FR-7, с согласия). ## 6. Требования к новым/изменённым QG checks **Нет.** Скрипт — операторский инструмент вне рантайма и вне конвейера; `STAGE_TRANSITIONS` / `QG_CHECKS` / `check_*` / machine-verdict ключи — байт-в-байт. ## 7. Совместимость / регресс - **Kill-switch не нужен:** активация — только явный запуск CLI человеком (паттерн ORCH-009/102/103); рантайм не несёт ни одной новой кодовой ветки. - Полный существующий регресс `pytest tests/ -q` остаётся зелёным; `test_lite_setup_doc.py` — зелёный (с синхронным обновлением при правке структуры дока). - Обратимость: задача добавляет файлы `scripts/` + `tests/` + правку дока; откат = revert PR, поведение платформы не меняется в обе стороны. - Скрипт не вносит хост-литералов в `src/**` (вне скана `test_no_host_hardcodes.py` по определению, т.к. `src/**` не трогается). ## 8. Конфигурация Новых ключей `Settings`/`.env.example` **не вводится**. Скрипт читает каноны `.env.example`/`.env.watchdog.example` как шаблоны и пишет целевые `.env`/`.env.watchdog` (FR-6). Платформенные константы (`SELF_HOSTING_REPO="orchestrator"`, имена сервисов, layout контейнера) не параметризуются (норматив REPLICATION §1). ## 9. Наблюдаемость - Структурный stdout-лог прогона (шаг → вердикт → диагноз) + итоговая таблица (FR-10). - Exit-код = машинный итог прогона (`0/2/1`) — пригоден для обвязки/CI заказчика. - Опциональный файл-отчёт прогона — на усмотрение архитектора (ADR). ## 10. Артефакты pipeline (создать/обновить в ТОМ ЖЕ PR) - `docs/work-items/ORCH-104/06-adr/ADR-001-.md` — решения: имя/режимы скрипта, эвристики discovery, политика оферов установки, автоматизация webhook Path Б и onboarding, механизм non-TTY (архитектор). - `docs/deployment/LITE_SETUP.md` — быстрый путь (FR-11). - `tests/test_setup_lite_script.py` (+ правка `tests/test_lite_setup_doc.py` при необходимости). - `CHANGELOG.md` — запись `feat:`; витрина `docs/overview/` — если затронуто описание тиража. ## 11. Инварианты (не нарушать) - `src/**`, корневой `docker-compose.yml`, `Dockerfile`, `STAGE_TRANSITIONS`, `QG_CHECKS`, `check_*`, machine-verdict ключи, схема БД — **байт-в-байт**. - **stdlib-only**; модули платформы не импортируются; канон-знания — только субпроцессами кирпичей (`gen_secrets.py`, `onboard_project.py`). - **No-delete:** ни одной удаляющей операции (файлы, контейнеры, ветки, правила) — лечение всегда инструкцией. - **Никогда** не push/force-push/иные операции в `main` (INV-4); не рестартить чужие/боевые контейнеры; мутации — только с явного согласия. - Секреты: свежие, скрытый ввод, не печатаются; существующие `.env*` молча не перетираются. - Stateless: данные/задачи/секреты исходного хоста не переносятся ни одним шагом. ## 12. Открытые вопросы для архитектора (не блокируют анализ) - **OQ-1:** имя скрипта и набор/дефолт режимов: строгий паттерн D5 (`plan` — дефолт, мутации только в `apply`) vs «запуск без аргументов = wizard» (бизнес-цель «одна команда»). Возможный компромисс: wizard-дефолт, где фаза скана всегда read-only, а мутации — за per-action consent. - **OQ-2:** механизм неинтерактивного прогона (флаги / answers-file / env-переменные) и точное поведение в non-TTY. - **OQ-3:** webhook Plane: автоматизировать ли Path Б (SQL) или строго manual-checkpoint; критерий выбора/подтверждения контейнера Postgres Plane. - **OQ-4:** политика оферов установки: исполнять команды пакетного менеджера из скрипта (с согласия) vs только печатать (безопаснее); набор поддерживаемых менеджеров. - **OQ-5:** размещение быстрого пути в LITE_SETUP.md (врезка в §1 vs новый раздел с правкой пиннинга «13 разделов») и судьба нумерации. - **OQ-6:** степень драйва onboarding: субпроцесс plan→apply→verify изнутри скрипта (прецедент `bootstrap_bundle.step_onboard`) vs печать готовой команды.