Files
HY2XS_flamy/docs/08-orchestrator-spec.md
T
founder 3a4ce9c751 docs: clean-install-only, versions.env и очистка предыдущего поколения
Новый docs/14-legacy-cleanup.md: как выглядит отказ установщика, полный
список маркеров чужой установки, что сохранить перед очисткой, работа
purge-v0.sh, ручная процедура и отдельно - случай незавершённой
установки текущего поколения, где нужен repair, а не очистка.

Обновлено под фактическое поведение:

- README и package/docs: установка описана как две фазы, PHASE 0 ничего
  не меняет; добавлен troubleshooting по отказу clean-host; версии
  toolchain больше не передаются через окружение;
- 02-build-layer: раздел про versions.env (что в нём есть и чего нет и
  почему), verify_versions_contract, проверка происхождения артефакта
  по upstream hashes.txt;
- 08-orchestrator-spec: двухфазный контракт, read-only guard,
  идентификация поколения в install-state, ownership-aware rollback,
  расширенная семантическая проверка конфига, структурная редакция;
- 04-admin-panel: таблица удалённых маршрутов и почему они удалены, а
  не оставлены заглушками; сужена формулировка гарантии санитайза;
- 11-testing: новые unit-наборы, полный список инвариантов конфига,
  раздел про одну реализацию URI вместо двух, сценарий проверки
  границы установки на живом сервере;
- 12-operations и 13-runbook: диагностика отказов по поколению,
  поведение diagnostics-бандла;
- tools/build/README: контракт версий, обе суммы Bun, hashes.txt.

CHANGELOG: раздел Unreleased с разбором каждого исправленного дефекта.
2026-08-27 12:16:38 +05:00

13 KiB
Raw Blame History

Install-only orchestrator spec

Цель документа

Зафиксировать ТЗ на оркестратор с учётом двухслойной архитектуры: builder отдельно, target install отдельно.

Технологический стек оркестратора

Оркестратор фиксируется как:

  • Bun + TypeScript по исходникам
  • локальная сборка builder layer'ом
  • поставка на target в виде готового install-артефакта

Это означает:

  • на target нет npm, pnpm, yarn или bun install
  • на target нет transpile/build step
  • shell на target допустим только как thin wrapper entrypoint

Главная роль оркестратора

Оркестратор работает только на target machine и умеет:

  • выполнить read-only проверку чистоты хоста (preflight-install)
  • выполнить первичную установку (install)
  • выполнить явную реконфигурацию (reconfigure --dry-run|--apply)
  • разложить bundled UI
  • скачать Hysteria2 из official upstream
  • создать/обновить конфиги
  • создать unit-файлы
  • применить staged firewall
  • создать post-install.env и runtime env-файл

Оркестратор не умеет

  • upgrade
  • standalone rollback subcommands
  • uninstall
  • repair старых неизвестных состояний
  • target-side build
  • target-side git clone исходного кода HY2XS admin
  • Telegram-бот / access delivery

Предусловия

Оркестратор рассчитан только на:

  • чистый Debian 13
  • root/sudo install context
  • один сервер
  • одну baseline-схему

Если машина уже «жила своей жизнью», baseline не обещает корректной автоадаптации.

Двухфазный контракт установки

Установка разделена на две фазы с жёсткой границей между ними:

PHASE 0 — READ ONLY
  проверка прав
  sha256sum -c metadata/checksums.txt
  ./orchestrator/hy2xs-orchestrator preflight-install --package-dir <распакованный пакет>
      ├── платформа Debian 13 amd64
      ├── clean-host контракт
      └── валидация конфигурации
        ↓  ноль persistent writes
PHASE 0 PASSED
        ↓
PHASE 1 — MUTATION
  install -d /usr/local/lib/hy2xs
  раскладка оркестратора и runtime-пакета
  hy2xs-orchestrator install

Ключевые свойства:

  • preflight-install запускается из распакованного пакета, а не из установленного /usr/local/lib/hy2xs: до PHASE 1 этого каталога может не существовать, и создавать его нельзя.
  • Граница держится не соглашением, а read-only guard (lib/guard.ts): под ним writeText/writeTextAtomic и мутирующие раннеры lib/process кидают ошибку. Это проверяется тестами.
  • Внутри install preflight() выполняется раньше первой записи install-state.json. Отказ на этом этапе означает, что на сервере не изменено ничего.

Полный список маркеров чужой установки и порядок очистки — 14-legacy-cleanup.md.

Маркер состояния установки

/var/lib/hy2xs/install-state.json отвечает на вопрос «эта машина — установка текущего поколения HY2XS, и в каком она состоянии». Поэтому кроме фазы он несёт идентификацию поколения:

{
  "product": "hy2xs",
  "release_line": 1,
  "config_schema_version": 2,
  "product_version": "1.0.0",
  "installed": true,
  "phase": "installed"
}

reconfigure и repair проверяют product / release_line / config_schema_version до всего остального. Флага installed: true недостаточно: такой же маркер мог остаться от 0.x.

repair дополнительно требует явного --allow-partial-state, чтобы работать поверх незавершённой установки. Разрешение не подразумевается: молчаливое согласие на произвольный partial marker и позволяло «чинить» чужое состояние.

Ownership и rollback

Операция ведёт учёт того, что она реально успела применить:

depsInstalled
filesystemPrepared
unitsDeployed
firewallTouched
postInstallWritten
servicesStarted

Классификация отказа строится по этим флагам и фазе, а не по тексту сообщения об ошибке. Ранее классификация шла по подстрокам, из-за чего preflight-ошибка со словом nftables приводила к откату чужого firewall.

Инварианты rollback:

  • fatal_pre_apply по определению означает «ничего не применялось»: system rollback не выполняется, install-state.json не пишется, diagnostics-бандл не собирается (его сбор сам создал бы каталоги в /var/log/hy2xs).
  • systemctl stop/disable выполняется только если текущая операция сама развернула эти unit-файлы.

Что приходит на target

На target должен попадать уже готовый package, содержащий:

  • thin install entrypoint
  • compiled orchestrator artifact
  • bundled HY2XS admin
  • templates
  • unit files
  • docs/examples
  • metadata package version / build id

Логическая модульность

Даже если на target приезжает один собранный артефакт, внутри исходников оркестратор должен быть разложен по шагам:

  • preflight
  • deps
  • filesystem
  • hysteria
  • ui
  • systemd
  • firewall
  • env
  • smoke

Что делает оркестратор по шагам

  1. Проверяет, что ОС — Debian 13, и что хост чист (до любой мутации).
  2. Проверяет базовые зависимости и install context.
  3. Создаёт каталоги установки.
  4. Разворачивает bundled HY2XS admin.
  5. Скачивает pinned Hysteria2 binary из package metadata, проверяет SHA256 и выполняет install.
  6. Генерирует Hysteria config.
  7. Создаёт systemd unit для Hysteria.
  8. Создаёт systemd unit для HY2XS admin.
  9. Применяет nftables baseline.
  10. Создаёт post-install.env.
  11. Запускает сервисы и выполняет smoke-check.

Модель поставки

Рекомендуемая baseline-модель:

  • исходники оркестратора хранятся в orchestrator/
  • builder выполняет локальную сборку через Bun
  • в install package кладётся готовый артефакт, который запускается thin wrapper'ом

Например:

  • package/install.sh — проверка контекста и вызов оркестратора
  • package/orchestrator/hy2xs-orchestrator — собранный артефакт

Логирование и коды возврата

Оркестратор должен:

  • печатать понятные step-based сообщения
  • завершаться ненулевым кодом при ошибке
  • не скрывать первичный источник падения
  • разделять preflight/config/runtime ошибки хотя бы на уровне текста

Политика ошибок

  • Любой конфликт неизвестного старого состояния = stop with error.
  • Никакой сложной автомиграции.
  • Ошибки должны быть текстовыми и пригодными для диагностики.
  • Для install/reconfigure допустим bounded rollback при failure-сценариях firewall/systemd/config/smoke.

CLI baseline

Команды:

  • preflight-install --package-dir <path> [--config <source-env>]
  • install --package-dir <path> [--config <source-env>]
  • reconfigure --package-dir <path> --config /etc/hy2xs/hy2xs.env --dry-run
  • reconfigure --package-dir <path> --config /etc/hy2xs/hy2xs.env --apply
  • repair --package-dir <path> --config /etc/hy2xs/hy2xs.env [--allow-partial-state]
  • redact-config --config <path> (--in-place | --out <path>) [--format auto|env|yaml]

preflight-install не принимает --skip-*: эти флаги влияют на мутацию, а PHASE 0 ничего не меняет.

--allow-partial-state допустим только для repair.

Инварианты:

  • только IPv4 bind/listen;
  • TLS modes: acme | file | self_signed_dev;
  • trafficStats.secret отдельный от JWT_SECRET;
  • HY2XS_CONFIG_SCHEMA_VERSION — обязательное поле; его отсутствие трактуется как legacy-конфигурация и отклоняется, а не заменяется значением по умолчанию;
  • install flow фиксирует фактически установленную версию Hysteria в snapshot;
  • версия/URL/SHA256 Hysteria берутся из metadata install package;
  • reconfigure не обновляет бинарник Hysteria, только runtime-слой;
  • при reconfigure --apply: backup -> staged apply -> smoke -> rollback on fail.

Семантическая проверка сгенерированного конфига

assertHysteriaConfigMatchesProfile разбирает YAML и сверяет его с production-профилем, а не ищет подстроки. Проверяются, в частности:

  • listen, ровно один подтип obfs и его соответствие obfs.type;
  • размеры пакетов Gecko;
  • bandwidth, disableLossCompensation, ignoreClientBandwidth;
  • congestion.type / bbrProfile;
  • весь QUIC baseline, включая maxIdleTimeout;
  • trafficStats.listen и непустой secret;
  • auth.type, точный auth.http.url (host/port/path/token) и auth.http.insecure;
  • ACME: type, email, ca, dir, listenHost, первый домен;
  • отсутствие посторонних секций верхнего уровня.

Сообщение об ошибке для auth.http.url намеренно не печатает сам токен: текст уходит в логи и в diagnostics-бандл.

Редактирование секретов

redact-config и diagnostics-бандл используют структурную редакцию: YAML разбирается и обходится как дерево.

Это не косметика. Построчное правило auth:\s*(.*) подставляло маркер в заголовок mapping'а и оставляло нетронутым вложенный auth.http.url с access_token=<секрет>, то есть бандл уносил machine token наружу. Значение может лежать где угодно в дереве, поэтому обходить нужно дерево.

Редактируются:

  • поля с секретоподобным именем (password, secret, token, apiKey, privateKey, authorization, cookie, bearer, signature, …);
  • карты, где секретны все значения (auth.userpass, acme.dns.config);
  • учётные данные и секретные query-параметры внутри URL — в том числе в env-файлах, где имя ключа (HY2_AUTH_URL) ни под один маркер не подходит.

Гарантия формулируется честно: known secrets + secret-shaped unknown fields. Обобщённый sanitizer не может пообещать, что под правило попадёт любой будущий секрет.

Что не реализовывать

  • update subcommands
  • rollback subcommands
  • uninstall subcommands
  • reconcile logic
  • выдачу пользовательских ключей или bot workflow