Новый 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
Admin panel: HY2XS admin
Цель документа
Зафиксировать модель работы с admin-панелью: HY2XS admin является штатным компонентом HY2XS, а не внешней зависимостью, которую target server где-то добывает во время установки.
Место компонента в системе
Проект состоит из компонентов двух разных типов:
- Hysteria2 — external runtime dependency. Ванильный upstream-бинарник, который оркестратор забирает из официального источника во время установки.
- HY2XS admin — native HY2XS component. Исходный код лежит в репозитории, компонент собирается production builder'ом вместе с остальными артефактами HY2XS.
Это противопоставление и есть основная архитектурная граница.
Состав компонента
- исходный код admin-компонента хранится в
apps/; - backend реализован на Go;
- frontend реализован на Vue/Vite;
- frontend-ассеты встраиваются в Go-бинарник;
- компонент собирается production builder'ом вместе с остальными артефактами HY2XS;
- готовый бинарник едет в install package как
ui/hy2xs-admin/hy2xs-admin.
Правила поставки
HY2XS admin:
- поставляется внутри итогового пакета;
- имеет свой install dir;
- имеет свой data dir;
- запускается отдельным rootless systemd unit (
hy2xs-admin); - не требует target-side build.
Target server не собирает admin-компонент из исходного кода и не скачивает его из внешнего репозитория.
Scope панели
Панель нужна для:
- operator-facing управления;
- просмотра статуса;
- работы с пользователями / трафиком / сущностями доступа;
- удобной админской рутины.
Панель не должна:
- определять install lifecycle сервера;
- превращать систему в сложный control plane;
- диктовать scope оркестратора.
HY2XS admin работает как надстройка над Hysteria YAML/API-слоем. Это нормально: важно только, чтобы источник истины по runtime-состоянию был понятен и не было двух конкурирующих конфигурационных миров без правил синхронизации.
Относительно конфигурации Hysteria панель read-only: конфиг генерирует оркестратор.
Два слоя работы с конфигом Hysteria
Это важное архитектурное разделение.
| Слой | Назначение | Поведение при неизвестных полях |
|---|---|---|
| Типизированная модель | отображение известных HY2XS полей в UI | неизвестные поля не отображаются |
| Сырой YAML | экспорт и сохранение | неизвестные поля сохраняются |
Причина: если бы экспорт работал через типизированную модель (Unmarshal → структура → Marshal), то любое поле, о котором HY2XS ещё не знает, терялось бы при round-trip. Панель незаметно урезала бы современный конфиг.
Поэтому:
- экспорт читает исходный YAML и сохраняет структуру документа целиком;
- будущие версии Hysteria не ломают экспорт только потому, что backend и frontend ещё не научились показывать новый параметр;
- это прямое следствие модели «latest stable на сборке»: схема upstream может опережать модель HY2XS.
Санитайз экспорта
Экспортируемый файл покидает сервер, поэтому секреты из него вырезаются:
- пароли обфускации (
obfs.*.password); trafficStats.secret;access_tokenв auth-URL и учётные данные, встроенные в URL;auth.password,auth.userpass;- учётные данные ACME DNS-провайдера;
- неизвестные поля с секретоподобным именем:
password,passwd,passphrase,secret,token,credential,apiKey/api_key,privateKey/private_key,accessKey,secretKey,authorization,cookie,bearer,signature.
Последний пункт — обратная сторона сохранения неизвестных полей: новое upstream-поле с секретом вырезается ещё до того, как HY2XS про него узнает.
Как формулируется гарантия
Точная формулировка:
вырезаются известные секреты и неизвестные поля с секретоподобным именем.
Не «любой будущий секрет будет автоматически удалён». Обобщённый sanitizer
работает по именам полей и не может предугадать произвольное имя, которое
upstream выберет для нового секрета. Список маркеров синхронизирован с
orchestrator/src/lib/redaction.ts; при появлении нового поля его нужно
добавить в оба места.
Пути к файлам (tls.cert, tls.key, ech.keyPath, tls.clientCA) секретами
не считаются и остаются читаемыми — они нужны для диагностики.
Модель современной схемы Hysteria
Модель админки понимает актуальную серверную схему, даже там, где UI не позволяет ничего включить: obfs.gecko, ech, congestion, mimic, realm, tls.clientCA, quic.disableStatelessReset, bandwidth.disableLossCompensation, masquerade.proxy.xForwarded.
Смысл в том, чтобы admin понимал текущую upstream-схему, а не считал неизвестными поля собственного конфига.
Генерация клиентских ссылок
- тип обфускации и пароль берутся из фактического конфига одинаково для всех поддерживаемых типов (
gecko,salamander); - неизвестный тип обфускации в ссылку не попадает: лучше отсутствие параметра, чем параметр, который клиент не понимает;
- публичный endpoint берётся из
HY2XS_PUBLIC_HOST+HY2XS_PUBLIC_PORT, а не изlistenили Host-заголовка запроса; - SNI берётся из ACME-домена, затем из
HY2XS_DOMAIN, затем изHY2XS_PUBLIC_HOST; IP-адрес как SNI не используется; minPacketSize/maxPacketSizeGecko в ссылку не помещаются — поэтому HY2XS держит их на upstream-defaults512/1200.
Правила ответственности
Source of truth
- runtime transport layer: Hysteria2
- операторский UI layer: HY2XS admin
- install lifecycle: оркестратор HY2XS
- deploy facts:
post-install.env
Production lifecycle Hysteria2
В production package HY2XS admin не скачивает и не обновляет бинарь Hysteria2 самостоятельно.
Правильная модель:
- Hysteria2 устанавливается install-оркестратором с official upstream;
- Hysteria2 запускается отдельным
hysteria-server.service; - HY2XS admin работает как operator UI и HTTP auth/traffic layer;
- HY2XS admin не запускается от root;
- смена версии Hysteria2 через UI отсутствует как API;
- список upstream releases не является частью operator UI baseline;
- port hopping не является частью production path.
Удалённые операции: почему не заглушки
Маршруты, которые продукт принципиально не поддерживает, удалены, а не оставлены отвечающими «feature disabled»:
| Удалённый маршрут | Кто владеет операцией |
|---|---|
POST /hysteria2ChangeVersion |
install-оркестратор |
GET /listRelease |
build layer |
POST /config/updateHysteria2Config |
install-оркестратор |
POST /config/importHysteria2Config |
install-оркестратор |
POST /config/restartServer |
systemd |
POST /config/uploadCertFile |
оператор + оркестратор |
GET /config/hysteria2AcmePath |
не имел потребителя |
Причины две.
Во-первых, API-контракт не должен даже обещать updater, которого у продукта нет: маршрут, всегда возвращающий отказ, вводит в заблуждение.
Во-вторых, это лишняя attack surface и технический мусор от прежней архитектуры.
Вместе с маршрутами удалены соответствующие клиентские функции фронтенда, кнопки и строки i18n. Кнопка, которая гарантированно возвращает ошибку, — не «точка расширения на будущее», а дефект UX. Возвращение любого из этих маршрутов ломает acceptance-проверку сборки.
Конфигурация Hysteria остаётся доступной панели на чтение и на выгрузку:
GET /config/getHysteria2Config и POST /config/exportHysteria2Config.
Что нельзя делать
- собирать admin-компонент на target server;
- скачивать admin-компонент на target из внешнего репозитория;
- склеивать unit Hysteria2 и unit HY2XS admin в один сервис;
- раздувать оркестратор из-за особенностей панели;
- использовать HY2XS admin как updater бинаря Hysteria2;
- использовать
JWT_SECRETкакtrafficStats.secretдля Hysteria API; - экспортировать конфиг Hysteria через типизированную модель — так теряются неизвестные upstream-поля;
- выгружать конфиг с секретами в открытом виде.
Что фиксировать в post-install.env
Минимум:
HY2XS_ADMIN_ENABLEDHY2XS_ADMIN_SOURCEHY2XS_ADMIN_BUILD_IDHY2XS_ADMIN_BIND_HOSTHY2XS_ADMIN_PORTHY2XS_ADMIN_INSTALL_DIRHY2XS_ADMIN_DATA_DIRHY2XS_ADMIN_LOG_DIR
Инварианты
Схема считается корректной, если:
- HY2XS admin приезжает на target уже в составе пакета
- target не скачивает и не собирает admin-компонент
- HY2XS admin работает отдельным сервисом
- HY2XS admin не меняет install-only scope оркестратора
- Hysteria2 остаётся внешним vanilla upstream-компонентом
- HY2XS admin не выступает updater-менеджером Hysteria2
trafficStats.secretне связан сJWT_SECRET- экспорт конфига сохраняет неизвестные upstream-поля
- экспорт конфига не содержит секретов
- сгенерированная
hysteria2://ссылка содержит фактический тип обфускации, и совместимый клиент подключается по ней напрямую