Экспорт в панели формировался через os.Create в /var/lib/hy2xs-admin/export,
и файл там оставался навсегда. При includeSecrets=true это означало
расшифрованные секреты пиров — фактические учётные данные доступа — в
открытом виде на диске, накапливающиеся с каждым нажатием кнопки. Выгрузки
формируются в памяти, каталога export/ больше нет.
Generic export/import таблицы config удалён целиком. Он исключал только сырой
Hysteria YAML, а в той же таблице лежат JWT_SECRET, PEER_SECRET_KEY,
PEER_SECRET_ENCRYPTION_KEY и HYSTERIA2_TRAFFIC_STATS_SECRET: кнопка Export
выгружала их открытым текстом, импорт позволял подменить. Для
PEER_SECRET_ENCRYPTION_KEY подмена ломает расшифровку секретов уже
существующих пиров. Production-сценария у этой пары не было.
Импорт пиров шёл мимо всей валидации, которую проходит обычное создание пира:
в базу попадало имя любой длины и с любыми символами, disabled с произвольным
числом, отрицательные счётчики. Файл применялся построчно, поэтому ошибка в
середине оставляла список наполовину изменённым, а импорт мог перезаписать
bootstrap-admin-peer, чей секрет продублирован в bootstrap-admin.secret.
Партия проверяется целиком до первой записи, неизвестные поля отклоняются.
Убран слой сетевых настроек панели: H_UI_WEB_PORT, H_UI_WEB_CONTEXT,
H_UI_CRT_PATH, H_UI_KEY_PATH и собственный TLS. Оркестратор передавал порт
аргументом, админка писала его в SQLite и тут же читала обратно, а UI
показывал поля disabled — второй источник истины, из которого ничего нельзя
было изменить. HUI_DATA/HUI_LOG заменены на HY2XS_DATA_DIR/HY2XS_LOG_DIR,
база переименована в hy2xs-admin.db, reference-схема — в schema.sql.
API namespace разделён по природе маршрутов: операторский API на /api,
machine-auth Hysteria на /internal/hysteria/auth. Путь machine-auth —
runtime-контракт, он уезжает в config.yaml и post-install.env, поэтому
объявлен одной константой на компонент.
Go-санитайзер экспорта вырезал секреты из URL только у ключей url/addr:
будущее upstream-поле с другим именем уносило учётные данные и access_token
целиком, а URL внутри списков не обрабатывались вовсе. Граница определяется
значением, а не именем ключа — как в TS-санитайзере оркестратора.
Заодно индикатор загрузки и цвета 401/404 переведены на брендовый токен:
NProgress приходил со своим #29d и был единственным элементом вне палитры.
Маршруты, операциями которых продукт не владеет, отвечали заглушкой
"managed by orchestrator" или пустым списком:
POST /hysteria2ChangeVersion
GET /listRelease
POST /config/updateHysteria2Config
POST /config/importHysteria2Config
POST /config/restartServer
POST /config/uploadCertFile
GET /config/hysteria2AcmePath (не имел потребителя вовсе)
Они удалены, а не оставлены заглушками. Причины две. API-контракт не
должен обещать updater, которого у продукта принципиально нет:
маршрут, всегда возвращающий отказ, вводит в заблуждение. И это лишняя
attack surface плюс технический мусор от прежней архитектуры.
Вместе с маршрутами убраны мёртвые сервисы (StartHysteria2,
StopHysteria2, RestartHysteria2, SetHysteria2Config,
UpdateHysteria2Config, GetAuthHttpUrl, Hysteria2AcmePath), неиспользуемые
типы и клиентские функции фронтенда.
Отдельно - кнопки. "Перезапустить панель" и загрузка сертификатов
обращались к заглушкам, то есть гарантированно возвращали ошибку.
Кнопка, которая всегда падает, - не точка расширения на будущее, а
дефект UX. Удалены вместе со строками i18n.
Конфигурация Hysteria остаётся доступной панели на чтение и на
выгрузку: getHysteria2Config и exportHysteria2Config.