Экспорт в панели формировался через 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 и был единственным элементом вне палитры.
Версии были размазаны: PACKAGE_VERSION в build.sh, схема конфигурации в
profile.ts и в hy2xs.env, версии toolchain в deps.sh, Debian 13 в
нескольких местах. Расхождение уже перестало быть теоретическим - пакет
1.0.0 сообщал "HY2XS admin version v0.0.22".
Введён корневой versions.env: версия продукта, линия релиза, схема
конфигурации, целевая платформа, версии и контрольные суммы
Go/Bun/Node/pnpm, политика выбора Hysteria.
Чего в нём нет намеренно:
1. Прикладных зависимостей - у них есть pnpm-lock.yaml, bun.lock,
go.sum. Второй слой неизбежно разъедется с настоящим графом.
2. Конкретной версии Hysteria - здесь только политика
HYSTERIA_CHANNEL, результат резолва живёт в hysteria-lock.env.
Пин версии здесь вернул бы ручное обновление.
Подход - проверка, а не генерация. profile.ts, hy2xs.env и
packageManager в двух package.json остаются обычными файлами, чтобы
bun test, tsc и go test работали из чистого чекаута до сборки. Новый
шаг verify_versions_contract роняет сборку до создания tarball при
расхождении. Контракт оркестратора сверяется не grep'ом по исходникам,
а выводом print-contract.ts: это доказывает, что в бинарь попало то же
значение.
Версия админки перестала быть константой и приезжает через ldflags;
собранный бинарь проверяется запуском hy2xs-admin version.
Контрольные суммы toolchain больше не передаются через окружение. Для
Bun зафиксированы обе суммы: артефакт выбирается по наличию AVX2,
поэтому одной архитектурно недостаточно. Production-сборка снова
запускается одной командой.