Девятый проход, по итогам приёмки v1.0.0-rc1 на живом Debian 13. Общая тема:
интерфейс обещал оператору то, что продукт умел, но до чего не доходило
управление.
Секрет пира. Подпись под полем предлагала оставить его пустым, сервер умел его
сгенерировать, и генерация была недостижима: в go-playground/validator тег
omitempty НЕ пропускает правило, если поле объявлено указателем и указатель не
nil — hasValue считает указатель на пустую строку «значением». Правило min=6
применялось к пустой строке и отказывало. Ловушка закрыта общим шагом
нормализации DTO, а не тегом на одном поле: та же ловушка ломала фильтр списка
пиров, где очищенный крестиком el-input отправляет `?name=`. Граница проходит по
каждому полю отдельно — у remark пустая строка означает «убрать пометку», у
disabled ноль означает «включён».
Отказы. Любая ошибка любого поля превращалась в слово `invalid`, а слой vo
определял код ответа СРАВНЕНИЕМ текста сообщения — тот же антипаттерн, который
запрещён панели, только на сервере. Ответ несёт errors[{code, field, message,
params}]; панель выбирает фразу по коду и подставляет причины под поля.
Сессия. Ветка «войдите заново» была недостижима дважды: сервер отвечает HTTP 200
на любой отказ, поэтому обработчик ошибок axios не вызывался, а условие в нём
проверяло code === "A0230" и поле msg, которых в этом API никогда не было.
Истёкший токен вдобавок уезжал с кодом системной ошибки.
Иконки. Контракт currentColor был объявлен в двух местах и не действовал: восемь
ассетов несли литеральный fill="#000000" на <path>, а атрибут представления
перебивает унаследованное CSS-свойство. Под это попадали все семь иконок
бокового меню на фоне #181818.
Имя пира. Два правила на одном поле противоречили друг другу (min=1 против
6-32), а копия набора символов в слое контроллеров несла неэкранированный дефис
и впускала `, - . / : ; <` — через панель проходило имя peer/name, которое
импорт того же пира отклонял. Набор символов ЛОГИНА сознательно не сужен и
закреплён тестом: он приходит из HY2XS_ADMIN_USER и оркестратором не
ограничивается.
Добавлены подпись «Разработано во Flamy» с адресом, принадлежащим приложению, и
контрактные тесты панели как обязательный шаг сборки. Их исполняет Bun, а не
vitest: jsdom не вычисляет currentColor и визуальной корректности не доказал бы,
зато vitest привёл бы в граф pnpm audit сотню транзитивных зависимостей.
docs/ разложена по слоям, 11-testing-and-acceptance.md (117 КБ) разбит на пять
частей, добавлен docs/acceptance/ с отчётом о прогоне rc1 и перечнем дефектов.
Обход документации в приёмке стал рекурсивным: плоский docs/*.md после
разнесения по каталогам совпадал бы ровно с одним файлом.
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проверяет и передаёт управление, не изменяя ничего сам. Очистка предыдущей установки — отдельная явная операция оператора, см. operations/14-legacy-cleanup.md. - Выдача доступа пользователям, Telegram-бот, billing, backend профилей и похожие контуры не входят в этот baseline.
Состав документов
Документы разложены по слою, к которому относятся. Двузначный префикс в имени — стабильный идентификатор документа: под ним на него ссылаются CHANGELOG, релизные гейты и сообщения оркестратора, поэтому при переносе в каталоги он сохранён.
Архитектура и рамки
- architecture/01-architecture-baseline.md — baseline-модель двух слоёв
- architecture/03-server-hysteria2.md — серверный транспорт
- architecture/05-client-and-access-scope.md — граница клиента
- architecture/06-speed-limits-and-congestion.md — ограничения скорости
- architecture/10-access-layer-out-of-scope.md — что вне baseline
Сборка
- build/02-build-layer-and-package.md — builder layer, состав пакета, требования к сборочной машине
Runtime на target
- runtime/08-orchestrator-spec.md — спецификация оркестратора
- runtime/07-systemd-and-firewall.md — systemd и nftables
- runtime/09-post-install-env.md — post-install состояние
Панель
- admin/04-admin-panel.md — HY2XS admin
- admin/15-ui-contracts.md — контракты панели: иконки, структурированные ошибки, необязательные поля, атрибуция
Эксплуатация
- operations/13-production-runbook.md — production runbook
- operations/12-operations-and-troubleshooting.md — операции и разбор отказов
- operations/14-legacy-cleanup.md — очистка установки предыдущего поколения
Проверки
- testing/ — набор проверок по слоям (бывший
11-testing-and-acceptance.md) - acceptance/ — отчёты о фактических прогонах приёмки
История изменений проекта — в 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 и подготавливает рабочее серверное окружение.