Два свойства были описаны в документации, но не обеспечены кодом.
1. doctor «не изменяет диагностируемую систему».
Принудительный skipServiceStart закрывал ровно одну ИЗВЕСТНУЮ мутацию —
рестарт сервисов. Всё остальное в smoke держалось на том, что автор правки
выбрал правильный раннер: `test -s`, `grep -q`, `stat`, `sudo -u ... test`
и `nft -c` шли через мутирующий namespace, хотя ничего не меняют. Ожидание
между попытками выполнялось подпроцессом `sleep` через runMutatingHidden,
то есть пауза между двумя чтениями объявлялась изменением системы.
Следствие: настоящая мутация, случайно добавленная в smoke, ничем бы от них
не отличалась и была бы разрешена в doctor молча — а включить guard было
нельзя, он отказал бы на первой же читающей команде.
Команды классифицированы честно, `sleep` заменён таймером, и doctor целиком
выполняется под тем же read-only guard, что и PHASE 0 установки. Guard
снимается в finally. Диагностика при этом не сузилась: слушатели, healthz,
права, machine auth, trafficStats, версия бинаря, семантика конфига и
синтаксис nft проверяются полностью.
2. reset-admin различает «администратора нет» и «база не ответила».
Слой данных специально возвращает разные sentinel'ы, но команда склеивала их
обычным `if err != nil { создать } else { обновить }`. Опасен здесь не
только нарушенный смысл: при транзиентном отказе чтения («database is
locked») ветка создания отрабатывала успешно, и в таблице оказывались ДВЕ
учётные записи администратора. GetAdminUser берёт First() и о второй строке
не сообщает — на сервере оставалась вторая рабочая учётка с паролем, уже
напечатанным на экран, и ни один запрос об этом не говорил.
Заодно исправлено проглатывание ошибки хеширования: в ветке обновления
стояло `hash, _ := util.HashPassword(password)` внутри литерала map. При
отказе bcrypt в password_hash уезжала пустая строка, а на экран печатался
пароль, которым войти уже невозможно — VerifyPassword отклоняет всё, что не
bcrypt. Команда восстановления доступа умела молча его отобрать.
Тесты: doctor-readonly.test.ts дополнен поведенческой проверкой guard и
контролем набора раннеров в smoke; apps/cmd/reset_test.go проверяет обе ветки
на настоящей SQLite и отказ чтения при полностью работоспособной базе — ровно
тот случай, который прежний код превращал во второго администратора. Добавлена
dao.CountAdminUsers: до неё появление дубликата было ненаблюдаемым.
HY2XS baseline docs
Этот набор документов фиксирует актуальную baseline-модель HY2XS под следующие ограничения:
- серверный транспорт: ванильная Hysteria2
- UI: HY2XS admin, штатный компонент проекта, поставляется вместе с пакетом
- target OS: только чистый Debian 13
- оркестратор: install-only, только первичная установка и базовая настройка
- стек оркестратора: Bun + TypeScript
- target-side build: запрещён
- standalone update / rollback / uninstall subcommands: вне scope
- сборка и упаковка: отдельный локальный build layer
- post-install state:
/etc/hysteria/post-install.env - установка: только на чистый хост, миграция с 0.x не поддерживается
- контракт версий продукта/платформы/toolchain: корневой
versions.env - клиентский delivery/access layer: вне baseline этого пакета docs
Главная архитектурная схема
В этой редакции зафиксированы два слоя:
-
Builder layer — работает на отдельном Debian 13 amd64 build host. Он собирает итоговый пакет, собирает HY2XS admin, компилирует Bun/TypeScript оркестратор в install-артефакт, упаковывает шаблоны, unit-файлы и примеры конфигов.
-
Runtime / target layer — работает на чистом Debian 13. Здесь нет сборщика. Здесь запускается только итоговый install package / orchestrator, который:
- ставит системные зависимости
- разворачивает встроенный HY2XS admin
- забирает закреплённую в пакете Hysteria2 из официального upstream и сверяет её по SHA-256 и версии
- создаёт конфиги, systemd unit-файлы и
post-install.env - выполняет базовую настройку сервера
Версия Hysteria2 выбирается на builder layer: последняя стабильная разрешается при сборке и замораживается в metadata пакета. Target layer никогда не обращается к moving latest.
Базовые правила
- Hysteria2 не вендорится и не собирается как часть HY2XS.
- HY2XS admin — штатный компонент проекта и поставляется вместе с пакетом.
- Оркестратор пишется на Bun + TypeScript.
- На target нет
npm/pnpm/yarn/bun install/ transpile step. - На target нет standalone логики update / rollback / uninstall.
- В install/reconfigure есть bounded rollback для failure-сценариев firewall/systemd/config/smoke. Rollback опирается на то, что операция реально успела применить: сервисы, которые она не разворачивала, не останавливаются никогда.
- Установка двухфазная: PHASE 0 — read only, PHASE 1 — mutation.
До успешного clean-host preflight на сервере не изменяется ни один
persistent path. У мутирующей фазы ровно один владелец — оркестратор:
install.shпроверяет и передаёт управление, не изменяя ничего сам. Очистка предыдущей установки — отдельная явная операция оператора, см. 14-legacy-cleanup.md. - Выдача доступа пользователям, Telegram-бот, billing, backend профилей и похожие контуры не входят в этот baseline.
Состав документов
- 01-architecture-baseline.md
- 02-build-layer-and-package.md
- 03-server-hysteria2.md
- 04-admin-panel.md
- 05-client-and-access-scope.md
- 06-speed-limits-and-congestion.md
- 07-systemd-and-firewall.md
- 08-orchestrator-spec.md
- 09-post-install-env.md
- 10-access-layer-out-of-scope.md
- 11-testing-and-acceptance.md
- 12-operations-and-troubleshooting.md
- 13-production-runbook.md
- 14-legacy-cleanup.md
История изменений проекта — в CHANGELOG.md.
Жёсткие рамки baseline
Не делаем:
- upgrade manager
- rollback manager
- uninstall
- reconcile engine
- target-side build pipeline
- Docker baseline
- multi-node
- port hopping
- Telegram-бот
- backend выдачи remote profiles
- «умную» миграцию сломанных старых инсталляций
Одной фразой
Правильная baseline-модель теперь такая:
Локальный builder разрешает последнюю стабильную Hysteria2, проверяет её на совместимость с конфигом HY2XS и собирает install package с HY2XS admin и Bun/TypeScript оркестратором; серверный install-only orchestrator ставит этот пакет на чистый Debian 13, скачивает ровно закреплённую Hysteria2, разворачивает HY2XS admin, создаёт systemd + nftables + post-install env и подготавливает рабочее серверное окружение.