# 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 ## Рекомендуемые пути ```bash /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_TYPE` — `gecko` или `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`; значения с пробелами по краям, кавычками или обратными слешами записываются в двойных кавычках — читать файл следует парсером формата, а не `cut -d= -f2-`; - `HY2XS_FORCE_PASSWORD_CHANGE` в production baseline установлен в `false` (forced UX-flow пока не реализован); - после первичного seed перезапуски `hy2xs-admin` не должны переопределять пароль admin и `con_pass`. ### Учётные данные администратора проверяются при разборе окружения `HY2XS_ADMIN_USER` и `HY2XS_ADMIN_INITIAL_PASSWORD` — это значения, которые потом принимает **форма входа в панель**. Оркестратор проверяет их против того же контракта, что и админка (`apps/credential/admin.go`): | Переменная | Требование | Значение по умолчанию | | --- | --- | --- | | `HY2XS_ADMIN_USER` | 6-32 символа из набора `a-z A-Z 0-9 !@#$%^&*()_+,-./:;<=` | `hy2xsadmin` | | `HY2XS_ADMIN_INITIAL_PASSWORD` | 6-64 символа Unicode **и** не более 72 байт в UTF-8; значение, загружаемое systemd из `EnvironmentFile=`; набор не ограничен, кроме `Cc` и `U+FEFF` | генерируется | Значение вне контракта **роняет установку** с явным текстом, называющим границы и набор. Так и должно быть: отказ, пришедший установщику, чинится одной строкой в `hy2xs.env`, а неработающий вход на готовом сервере — переустановкой. Проверяется и сгенерированный пароль, а не только заданный оператором: генератор — такой же источник значения. Границ у пароля **две**, и они в разных единицах. Предел в 72 байта ставит bcrypt: `GenerateFromPassword` отвечает `ErrPasswordTooLong` на пароль длиннее 72 байт, а у 64 символов длина от 64 до 256 байт в зависимости от алфавита. Пока байтовой границы здесь не было, `HY2XS_ADMIN_INITIAL_PASSWORD` из 64 кириллических букв (128 байт) проходил установку целиком, а первая учётная запись администратора не создавалась вовсе — админка падала при старте. Подробно границы описаны в `docs/admin/15-ui-contracts.md`. Окружающие пробелы у `HY2XS_ADMIN_USER` снимаются. Иначе они уезжали бы в имя учётной записи в SQLite, и вход отказывал бы «неверным логином или паролем» — отказом, который невозможно связать с причиной. У `HY2XS_ADMIN_INITIAL_PASSWORD` пробелы по краям, наоборот, **являются частью пароля** и не снимаются нигде — ни оркестратором, ни админкой, ни хешированием. Чтобы такое значение пережило запись и чтение, оно записывается **в двойных кавычках** с экранированием `\` и `"`: ```text HY2XS_ADMIN_INITIAL_PASSWORD="пароль с пробелом на конце " ``` Кавычки здесь не украшение. Файл читает не только оркестратор, но и systemd — он объявлен `EnvironmentFile=` в юните `hy2xs-admin`, — а у **незакавыченного** значения systemd срезает пробелы по краям и трактует `\` как escape. Если вы правите `hy2xs.env` руками и в пароле есть пробел по краям, кавычка или обратный слеш, закавычьте значение тем же способом. Обычные значения (порты, пути, домены) кавычек не требуют и записываются как раньше. Есть два ограничения набора символов, и они разного происхождения. **Домен systemd.** Значение обязано быть загружаемым из `EnvironmentFile=`: валидный UTF-8 из Unicode scalar values, без NUL, без суррогатов и без noncharacters (`U+FDD0..U+FDEF` и все `*FFFE`/`*FFFF`). Это не наше правило — systemd прогоняет значение через `utf8_is_valid` и отвечает `-EINVAL`, то есть файл окружения **не загружается** и юнит не стартует. Оркестратор проверяет домен на **каждом** значении файла, а не только на пароле: `HY2XS_ADMIN_CON_PASS` или obfs-пароль сломали бы загрузку юнита ровно так же. **Политика HY2XS.** Сверх этого запрещены управляющие символы Unicode (категория `Cc`) и `U+FEFF`. Формат их несёт, но ввести такой пароль в однострочное поле формы входа всё равно нельзя. Отказ по любому из двух правил приходит **при разборе конфигурации**, то есть до первой необратимой операции над хостом: `preflight-install` и `install` видят его одинаково. Значение по умолчанию совпадает в трёх местах и обязано совпадать: `package/config/hy2xs.env`, `orchestrator/src/config/env.ts` и запасное значение в `apps/dao/sqlite.go`. Раньше оркестратор писал `admin` — пять символов при минимуме панели в шесть, — и установка завершалась `INSTALL EXIT CODE: 0`, оставляя панель, в которую невозможно войти. ### Backlog: секреты в окружении Документация systemd отдельно рекомендует **не** передавать секреты через переменные окружения и предлагает для них `LoadCredential=` / `LoadCredentialEncrypted=`: окружение процесса видно шире, чем файл с правами `0600`. HY2XS v1 этим не пользуется, и это осознанное решение по срокам, а не недосмотр: переход затрагивает модель секретов всего продукта (машинный токен Hysteria, obfs-пароль, `con_pass`), а не только пароль администратора. Действующая защита — права `0600`, владелец `root:root` и отсутствие доступа у служебных пользователей (`hy2xs-admin` и `hysteria` файл прочитать не могут, что проверяет smoke). Пункт остаётся в backlog как отдельная работа. ### 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`.