Files
HY2XS_flamy/docs/runtime/09-post-install-env.md
T
founder a8407cf16b fix(admin): вход в панель падал на теге правила, пережившего переименование
RC2 на чистом Debian 13 завершался INSTALL EXIT CODE: 0 при полностью
недоступной панели. На LoginDto.Username стоял тег `validateStr` — правило с
таким именем не регистрировалось: при переименовании в `credentialStr` правка
не доехала до одного файла, оставив мёртвую регистрацию и живую ссылку на
несуществующее имя. go-playground/validator на неизвестный тег ПАНИКУЕТ при
разборе структуры, то есть до всякой проверки логина и пароля, а gin.Recovery
превращал панику в HTTP 500 на каждый POST /api/auth/login.

Дефект пережил 311 Go-тестов, и это главное, что здесь чинится. Проверялся сам
регексп, в обход валидатора, а обработчика входа не касался ни один тест.
Очевидная замена не помогла бы: цепочка правил поля обрывается на первом
несработавшем, поэтому нулевое DTO отказывает по `required` и до испорченного
тега не доходит. Теперь TestEveryValidationTagIsRegistered обходит исходники
apps/model/**, вытаскивает каждый тег `validate:"…"` и предъявляет его
валидатору отдельно — незарегистрированное правило паникует так же, как в бою,
но на сборке. Барьер проверен возвратом исходного тега.

Установка тоже не отвечала на вопрос, ради которого проверялась. Smoke считал
панель работающей по трём признакам — юнит активен, порт в LISTEN, /healthz
отвечает ok, — и все три были истинны. Теперь smoke выполняет настоящий вход
bootstrap-учётными данными и требует конверт успеха с непустым токеном: по коду
HTTP это неотличимо, админка отвечает 200 OK и на отказ. Отрицательная проба
идёт в любом режиме операции и от актуальности пароля не зависит.

Рядом лежали три расхождения того же класса, найденные при разборе.

Оркестратор не знал контракта, который сам порождает: HY2XS_ADMIN_USER по
умолчанию был `admin` — пять символов при минимуме панели в шесть, — и такая
установка проходила целиком, создавая учётную запись, под которой невозможно
войти. Про одно имя существовало три расходящихся умолчания. Оба значения
теперь проверяются при разборе окружения — той стороной, которая их порождает:
отказ, пришедший установщику, чинится строкой в hy2xs.env, а неработающий вход
на готовом сервере — переустановкой.

Панель была строже сервера. Форма входа ограничивала пароль 32 символами при
серверном пределе в 64, а форма смены пароля назначала до 64: пароль,
назначенный штатной операцией, после этого не вводился. Набор символов на
пароле отвергал значение, которое сервер принял бы, — сервер его не
ограничивает нигде. Контракт учётных данных объявлен один раз в
service/admin_credentials.go, копии в панели и оркестраторе сверяются с ним
тестами, читающими Go-исходник.

Класс символов логина был записан диапазоном по опечатке: неэкранированный
дефис превращал `+-=` в диапазон, впускающий `, - . / 0-9 : ; < =`. С серверным
набором это совпадало только потому, что обе стороны несли одну опечатку. Набор
записан явно и НЕ сужен — он уже действует на установленных серверах.

Визуально: красная рамка отказа обводила не то, что видит оператор. Element Plus
рисует состояние ошибки на el-input__wrapper селектором из четырёх классов, а
форма входа рисует видимую рамку поля на el-form-item — внутрь поля кладутся
иконка, ввод и переключатель видимости — и гасила чужую тень селектором из трёх,
проигрывая по специфичности. Рамка ложилась вокруг одного лишь ввода: у логина
начиналась после иконки, у пароля обрывалась перед «глазом». Индикация
перенесена на элемент, который оператор и видит полем; чужая тень гасится
селектором, повторяющим её собственный и добавляющим атрибут scoped-стиля, —
конкретностью, а не !important. Остальные формы панели проверены: собственная
рамка на el-form-item есть только на форме входа.

Заодно: `last_login_at` объявлен в схеме и в entity, а писать его было некому —
UpdateAdminLastLoginAt не вызывался ниоткуда. Отметка ставится в service.Login
сразу после успешной проверки пароля; отказ записи вход не отменяет, но
попадает в журнал. Обработчик входа переехал из controller/peer.go в
controller/auth.go: стек в journal указывал на управление пирами.

Требование теперь называется, а не сообщается фактом нарушения. «Неверный
формат логина» и «Некорректное значение» не давали оператору способа узнать,
что от него хотят: набор символов приходит из hy2xs.env и в панели нигде не
показан. Фразы форм и серверная причина credential_format перечисляют границы
и набор.

Гейт сборки run_admin_login_acceptance удерживает барьеры от тихого удаления —
по той же причине, что и гейт детектора гонок. Каждое из его утверждений
проверено мутационной пробой на реальный отказ; две первые редакции оказались
вакуумными и переписаны.

Прогнано: go vet + go test ./... , bun test оркестратора (427) и контрактов
панели (66), vue-tsc --noEmit, production-сборка frontend, гейт приёмки
целиком. `go test -race` не прогонялся — на машине нет C-компилятора, это
релизный гейт сборщика.

Прогон задокументирован в
docs/acceptance/2026-09-04-v1.0.0-rc2-runtime-findings.md.
2026-09-04 02:32:50 +05:00

9.3 KiB
Raw Blame History

Runtime env и post-install snapshot

Цель документа

Зафиксировать двухслойную модель:

  • editable runtime env: /etc/hy2xs/hy2xs.env
  • generated deploy snapshot: /etc/hysteria/post-install.env

Зачем нужен файл

После первичной установки оператору нужны:

  1. runtime-файл, который оркестратор читает и валидирует;
  2. snapshot-файл фактического deploy-состояния.

В snapshot видно:

  • какой пакет был установлен
  • какой build артефакт использован
  • какой стек оркестратора применён
  • какая версия Hysteria реально установилась
  • какой build HY2XS admin разложен на target
  • какие базовые параметры сети и портов заданы

Именно для этого создаётся post-install.env.

Чего файл не делает

Этот файл:

  • не делает оркестратор update-manager'ом
  • не гарантирует автоматическое применение изменений
  • не заменяет runtime-конфиги
  • не превращает target в builder

Рекомендуемые пути

/etc/hy2xs/hy2xs.env
/etc/hysteria/post-install.env

/etc/hy2xs/hy2xs.env и /etc/hysteria/post-install.env должны иметь права 0600 root:root.

/etc/hysteria/config.yaml должен иметь права 0640 hysteria:hy2xs-admin (UI только читает).

Минимальный набор переменных

Deploy / package

  • DEPLOY_TARGET_OS
  • DEPLOY_TIMESTAMP (last apply timestamp)
  • PACKAGE_NAME
  • PACKAGE_BUILD_ID
  • PACKAGE_VERSION

Orchestrator

  • ORCH_SOURCE_STACK=bun-typescript
  • ORCH_BUILD_MODE
  • ORCH_BUILD_ID
  • ORCH_ENTRYPOINT

Общие

  • HY2XS_CONFIG_SCHEMA_VERSION
  • DEPLOY_DOMAIN
  • PUBLIC_HOST
  • PUBLIC_PORT
  • SSH_PORT
  • HY2XS_FIREWALL_MODE
  • HY2XS_FIREWALL_STAGED_APPLY
  • HY2XS_ADMIN_USER
  • HY2XS_FORCE_PASSWORD_CHANGE
  • HY2XS_ALLOW_SELF_SIGNED_DEV

Hysteria

  • HY2_SOURCE=official-upstream
  • HY2_VERSION — фактически установленная версия
  • HY2_RESOLUTION — как версия была выбрана при сборке: latest-stable, pinned или override
  • HY2_TLS_MODE
  • HY2_ACME_EMAIL
  • HY2_TLS_CERT_PATH
  • HY2_TLS_KEY_PATH
  • HY2_LISTEN_HOST
  • HY2_PORT
  • HY2_AUTH_MODE
  • HY2_AUTH_URL
  • HY2_TRAFFIC_STATS_LISTEN
  • HY2_OBFS_TYPEgecko или salamander
  • HY2_OBFS_PASSWORD
  • HY2_GECKO_MIN_PACKET_SIZE
  • HY2_GECKO_MAX_PACKET_SIZE
  • HY2_BANDWIDTH_UP
  • HY2_BANDWIDTH_DOWN
  • HY2_DISABLE_LOSS_COMPENSATION
  • HY2_IGNORE_CLIENT_BANDWIDTH
  • HY2_CONGESTION_TYPE
  • HY2_BBR_PROFILE
  • HY2_DISABLE_STATELESS_RESET
  • HY2_CONFIG_PATH

HY2XS admin

  • HY2XS_ADMIN_ENABLED
  • HY2XS_ADMIN_SOURCE
  • HY2XS_ADMIN_BUILD_ID
  • HY2XS_ADMIN_BIND_HOST
  • HY2XS_ADMIN_PORT
  • HY2XS_ADMIN_INSTALL_DIR
  • HY2XS_ADMIN_DATA_DIR
  • HY2XS_ADMIN_LOG_DIR

Как работать с файлами

Правильная модель:

  1. оркестратор создаёт hy2xs.env и post-install.env при установке;
  2. оператор редактирует только hy2xs.env;
  3. оператор запускает reconfigure --dry-run, затем reconfigure --apply;
  4. оркестратор обновляет runtime и перезаписывает snapshot.

Политики проверок DNS

В /etc/hy2xs/hy2xs.env есть две независимые политики, обе по умолчанию strict:

Переменная Что проверяет
HY2XS_DNS_AAAA_POLICY наличие AAAA-записи при IPv4-only профиле
HY2XS_PUBLIC_ENDPOINT_POLICY что A-записи публичного endpoint ведут на публичные IPv4 этого сервера

HY2XS_PUBLIC_ENDPOINT_POLICY принимает strict / warn / off. Ослаблять её имеет смысл только для топологий вне baseline: сервер за NAT, floating IP, anycast. Отсутствие A-записи фатально при любом значении.

Обе политики применяются в install, reconfigure и doctor, потому что живут в общем preflight.

Сетевая идентичность админки

HY2XS_UI_PORT, HY2XS_UI_BIND_HOST, HY2XS_DATA_DIR и HY2XS_LOG_DIR — единственный источник истины для этих величин. Админка читает их из окружения юнита и не хранит собственных копий в SQLite.

Важно:

  • HY2XS_ADMIN_INITIAL_PASSWORD используется только для первичного bootstrap seed;
  • HY2XS_ADMIN_CON_PASS — отдельная runtime-сущность для Hysteria auth/smoke;
  • bootstrap secret хранится в явном формате KEY=VALUE (ADMIN_USER, ADMIN_INITIAL_PASSWORD, ADMIN_CON_PASS), права 0600;
  • HY2XS_FORCE_PASSWORD_CHANGE в production baseline установлен в false (forced UX-flow пока не реализован);
  • после первичного seed перезапуски hy2xs-admin не должны переопределять пароль admin и con_pass.

Учётные данные администратора проверяются при разборе окружения

HY2XS_ADMIN_USER и HY2XS_ADMIN_INITIAL_PASSWORD — это значения, которые потом принимает форма входа в панель. Оркестратор проверяет их против того же контракта, что и админка (apps/service/admin_credentials.go):

Переменная Требование Значение по умолчанию
HY2XS_ADMIN_USER 6-32 символа из набора a-z A-Z 0-9 !@#$%^&*()_+,-./:;<= hy2xsadmin
HY2XS_ADMIN_INITIAL_PASSWORD 6-64 символа, набор не ограничен генерируется

Значение вне контракта роняет установку с явным текстом, называющим границы и набор. Так и должно быть: отказ, пришедший установщику, чинится одной строкой в hy2xs.env, а неработающий вход на готовом сервере — переустановкой.

Проверяется и сгенерированный пароль, а не только заданный оператором: генератор — такой же источник значения.

Окружающие пробелы у HY2XS_ADMIN_USER снимаются. Иначе они уезжали бы в имя учётной записи в SQLite, и вход отказывал бы «неверным логином или паролем» — отказом, который невозможно связать с причиной.

Значение по умолчанию совпадает в трёх местах и обязано совпадать: package/config/hy2xs.env, orchestrator/src/config/env.ts и запасное значение в apps/dao/sqlite.go. Раньше оркестратор писал admin — пять символов при минимуме панели в шесть, — и установка завершалась INSTALL EXIT CODE: 0, оставляя панель, в которую невозможно войти.

Immutable-bootstrap контракт

  • /etc/hy2xs/bootstrap-admin.secret создаётся оркестратором только при первичной установке.
  • На reconfigure --apply bootstrap secret не пересоздаётся и не ротируется автоматически.
  • Изменения HY2XS_ADMIN_INITIAL_PASSWORD в runtime env после первичной установки не должны менять фактический пароль admin.
  • HY2XS_ADMIN_CON_PASS используется как bootstrap-значение при первичной установке; после создания admin account изменение этого значения в /etc/hy2xs/hy2xs.env не пересоздаёт и не обновляет существующий con_pass в SQLite.
  • Ротация con_pass выполняется через account-management слой UI/БД, а bootstrap snapshot остаётся неизменным.

Что нельзя делать

  • сваливать туда временный мусор
  • считать, что edit env автоматически меняет runtime без reconfigure --apply
  • использовать файл как замену настоящей конфигурации сервисов

Пример

Используйте canonical runtime-файл package/config/hy2xs.env как базовый шаблон значений и переносите его параметры в /etc/hy2xs/hy2xs.env.