Новый 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 с разбором каждого исправленного дефекта.
13 KiB
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кидают ошибку. Это проверяется тестами. - Внутри
installpreflight()выполняется раньше первой записи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
Что делает оркестратор по шагам
- Проверяет, что ОС — Debian 13, и что хост чист (до любой мутации).
- Проверяет базовые зависимости и install context.
- Создаёт каталоги установки.
- Разворачивает bundled HY2XS admin.
- Скачивает pinned Hysteria2 binary из package metadata, проверяет SHA256 и выполняет install.
- Генерирует Hysteria config.
- Создаёт systemd unit для Hysteria.
- Создаёт systemd unit для HY2XS admin.
- Применяет nftables baseline.
- Создаёт
post-install.env. - Запускает сервисы и выполняет 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-runreconfigure --package-dir <path> --config /etc/hy2xs/hy2xs.env --applyrepair --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