Разбор предыдущего прохода со сверкой по исходникам systemd v257.13 — той самой линии, что стоит на Debian 13. Тема та же и слоем глубже: контракт, объявленный шире, чем его принимает чужая сторона. Прошлый проход сделал транспорт lossless для значений, которые systemd принимает, но не спросил, какие значения он принимает вообще. 1. Домен значений файла окружения Перед тем как принять пару, systemd прогоняет ключ и значение через utf8_is_valid (src/basic/env-file.c, check_utf8ness_and_warn), и отказ там возвращает -EINVAL — то есть НЕзагруженный EnvironmentFile= и юнит, который не стартует, а не предупреждение. unichar_is_valid (src/basic/utf8.c) отвергает суррогаты, U+FDD0..U+FDEF и все code points вида *FFFE/*FFFF, а сам utf8_is_valid — встроенный NUL и невалидный UTF-8. Пароль "abcde" + U+FDD0 — шесть символов, восемь байт, ни одного управляющего — проходил панель, оркестратор, DTO и хеширование, записывался в hy2xs.env, и после этого админка не поднималась. Тот же класс дефекта, ради уничтожения которого контракт и существует, только слоем ниже. Введён IsEnvTransportableText (Go) / isEnvTransportable (TS), повторяющий множество systemd точно — не шире и не уже. Отдельно отвергаются одиночные суррогаты: строка JavaScript вправе их содержать, а TextEncoder молча заменяет непарный суррогат на U+FFFD, то есть без проверки в файл уехал бы ДРУГОЙ секрет, а не отказ. Заодно разделены домен транспорта и политика продукта. Проверка отвергала C0 и DEL с формулировкой «формат управляющих символов не несёт» — неправда: внутри двойных кавычек перевод строки накапливается как обычный байт и переживает round-trip. Именно эта подмена и позволила проверке не знать про noncharacters. Политика HY2XS теперь запрещает категорию Cc целиком (была шире кода ровно на C1) плюс U+FEFF — последний отдельным решением продукта, а не форматом: 0xFEFF & 0xFFFE это 0xFEFE, и systemd такое значение принимает. 2. Рецепт восстановления выполнял env-файл как код В docs/operations/12, раздел «Забыт пароль администратора», стояло `set -a; . /etc/hy2xs/hy2xs.env; set +a`. Строка стала опасной ровно тогда, когда файл научился нести произвольные значения. Для systemd HY2XS_ADMIN_INITIAL_PASSWORD="$(...)" — буквальное значение: подстановок в EnvironmentFile= нет вовсе. Но `.` обрабатывает файл bash, а bash внутри двойных кавычек выполняет подстановку команд — от root, прямо в рецепте восстановления доступа. Соседний раздел той же страницы при этом уже правильно запрещал source/eval для bootstrap-admin.secret: документ запрещал действие и тут же его предлагал. Рецепт читает нужные значения как ДАННЫЕ. Поставлен гейт приёмки, запрещающий возврат source/./eval над этими файлами в командах документации и в скриптах; гейт смотрит только внутрь ```-блоков, чтобы объяснение, называющее убранную конструкцию по имени, его не роняло. 3. Отказ приходил после мутаций хоста Проверка транспорта жила только внутри renderRuntimeEnv, то есть срабатывала на шаге «write runtime env» — уже после bootstrap оркестратора, установки пакетов и раскладки файловой системы, — а read-only preflight-install говорил PASS: он зовёт parseRuntimeEnv и ничего не рендерит. Детерминированно известная ошибка конфигурации роняла операцию, оставив за собой изменённый хост, что прямо противоречит контракту PHASE 0. validateRuntimeEnvTransport вызывается теперь из parseRuntimeEnv и проходит по ВСЕМ парам runtimeEnvEntries: ограничение принадлежит формату, а не полю пароля, и HY2XS_ADMIN_CON_PASS сломал бы загрузку юнита так же. 4. Точность порта автомата и его описания - в состоянии DOUBLE_QUOTE_VALUE_ESCAPE systemd пишет `c != '\n'`, а не проверку на любой перевод строки (в VALUE_ESCAPE — наоборот, strchr(NEWLINE, c)). Порт съедал и \<LF>, и \<CR>; - комментарий обещал одно намеренное расхождение с systemd, а их два: кроме строки без `=`, HY2XS отказывает и на незакрытой кавычке в конце файла. Оба fail-closed и теперь названы оба. Тесты: граничная таблица во всех слоях дополнена значениями вне домена (U+FDD0, U+FDEF, U+FFFE, U+FFFF, U+1FFFF, U+10FFFF, невалидный UTF-8), соседями диапазонов (U+FDCF, U+FDF0, U+FFFD, U+10FFFD), C1 и U+FEFF, одиночным суррогатом. Добавлены TestEnvTransportDomainMatchesSystemd (домен не шире и не уже) и TestProductPolicyIsWiderThanTransportDomain (домен и политика различимы), а также проверки fail-closed порядка: parseRuntimeEnv отвергает непригодную конфигурацию, проверяются все значения файла, запись и проверка ходят по одному списку пар. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
36 KiB
Operations and troubleshooting
Цель документа
Зафиксировать минимальный operational контур после установки.
Что должен помнить оператор
0. Target prerequisites обязательны
На target-хосте install-flow сам обеспечивает системные зависимости (deps stage):
apt-get update
apt-get install -y sudo ca-certificates curl iproute2 tar openssl nftables systemd
sudo обязателен для smoke-проверок прав от имени runtime-пользователей (hysteria, hy2xs-admin), но его не нужно ставить вручную заранее: на clean-host он устанавливается на deps-стадии до smoke.
Важно: packaged baseline использует HY2XS_SSH_PORT=2323 по умолчанию. На target-хосте это значение обязательно нужно привести к фактическому рабочему SSH-порту оператора в /etc/hy2xs/hy2xs.env и применить через reconfigure --apply.
1. Builder и target — разные миры
Если нужно изменить состав install package, это делается в локальном builder layer, а не на target server.
2. UI приезжает из нашего пакета
Если проблема в UI, сначала смотреть:
- какой
HY2XS_ADMIN_SOURCE - какой
HY2XS_ADMIN_BUILD_ID - тот ли пакет вообще стоит на сервере
3. Hysteria приходит из upstream
Если проблема в ядре Hysteria, сначала смотреть:
- какую фактическую версию оркестратор установил
- что записано в
HY2_VERSION - не связано ли поведение со свежим upstream release
- какая версия Hysteria зафиксирована в metadata установленного пакета
Для обновления бинарника Hysteria2 используйте новый release install package. Изменение runtime env не обновляет бинарник Hysteria2.
4. Оркестратор — Bun/TypeScript, но target не билдит его
Если проблема в install flow, сначала смотреть:
- какой
ORCH_BUILD_ID - какой
ORCH_ENTRYPOINT - не подменён ли install package вручную
Базовые команды проверки
Проверка сервисов:
systemctl status hysteria-server
systemctl status hy2xs-admin
Проверка порта:
ss -uln
Проверка firewall:
nft list ruleset
Проверка post-install.env:
cat /etc/hysteria/post-install.env
Проверка логов через journald:
journalctl -u hysteria-server.service -n 100 --no-pager
journalctl -u hy2xs-admin.service -n 100 --no-pager
Источник логов в UI:
- Страница «Логи Hysteria» читает записи из journald unit
hysteria-server.service. - Экспорт «Логи Hysteria» также формируется из journald (
journalctl), а не из отдельного файлаhysteria2.log.
Проверка install-state marker:
cat /var/lib/hy2xs/install-state.json
Если reconfigure сообщает об отсутствии marker, нужно повторно выполнить чистый install и только потом применять runtime-изменения.
Auth endpoint fail checklist
systemctl status hysteria-server
systemctl status hy2xs-admin
sudo -u hy2xs-admin test -r /etc/hysteria/config.yaml
curl -sS -X POST \
-H 'Content-Type: application/json' \
--data '{"addr":"127.0.0.1:12345","auth":"invalid","tx":0}' \
http://127.0.0.1:8080/internal/hysteria/auth
curl -sS \
-H "Authorization: <trafficStatsSecret>" \
http://127.0.0.1:36712/online
Типовые проблемы
Установка отказывается: обнаружена предыдущая установка
Отказ происходит в PHASE 0, до любой мутации. Сервер остался в том
состоянии, в котором был: ни /usr/local/lib/hy2xs, ни
/var/lib/hy2xs/install-state.json, ни работающие службы не тронуты.
В тексте отказа перечислены конкретные найденные маркеры. Порядок действий —
14-legacy-cleanup.md: сохранить данные, посмотреть план
tools/legacy/purge-v0.sh, выполнить очистку, установить заново.
Проверить хост, ничего не устанавливая:
./orchestrator/hy2xs-orchestrator preflight-install --package-dir "$(pwd)"
reconfigure/repair отказываются: маркер чужого поколения
Маркер установки /var/lib/hy2xs/install-state.json не относится к текущему
поколению HY2XS.
installed: true сам по себе ничего не доказывает: такой же маркер мог
остаться от 0.x. Обе команды проверяют product, release_line и
config_schema_version.
Посмотреть, что видит оркестратор:
hy2xs-orchestrator status --package-dir /usr/local/lib/hy2xs/package \
| grep -o '"install_state_generation":"[^"]*"'
"current" — маркер текущего поколения; "foreign" — требуется чистая
переустановка; "absent" — установки нет.
Незавершённая установка текущего поколения
Если установка упала после начала применения изменений, полная очистка не нужна:
hy2xs-orchestrator repair \
--package-dir /usr/local/lib/hy2xs/package \
--config /etc/hy2xs/hy2xs.env \
--allow-partial-state
Флаг обязателен и осознан: без него repair работает только поверх полностью
успешной установки.
Операция отказывает: уже выполняется другая
another HY2XS operation is already in progress: reconfigure (pid 4242, started at …)
install, reconfigure, repair и doctor сериализованы замком
/run/lock/hy2xs-orchestrator.lock. Это не перестраховка: production paths —
/etc/hysteria/config.yaml, unit-файлы, /etc/nftables.conf,
/var/lib/hy2xs/install-state.json — общие, и две одновременные операции
записывают их поверх друг друга, после чего откат одной «восстанавливает»
состояние поверх изменений другой.
Отказ происходит до первой мутации, поэтому сервер не тронут. Что делать:
# кто держит замок
cat /run/lock/hy2xs-orchestrator.lock
# что делает держатель
ps -o pid,etime,cmd -p "$(sed -n 's/.*"pid": *\([0-9]*\).*/\1/p' /run/lock/hy2xs-orchestrator.lock)"
Дождитесь завершения. Замок снимается сам при любом завершении держателя,
включая Ctrl+C, SIGTERM и обрыв SSH, а мёртвого держателя следующая операция
обнаруживает и переиспользует замок самостоятельно.
Удалять файл руками нужно ровно в одном случае — если оркестратор сообщил, что содержимое замка не является корректной записью:
operation lock … exists but is not a valid HY2XS lock record
Такой замок сознательно не снимается автоматически: непонятый файл не доказывает, что операции нет.
status и diagnostics collect замок не берут и работают во время операции.
В отчёте status при этом появляется поле operation_in_progress — читайте
состояние как снимок незавершённой транзакции, а не как итог.
Операция отказывает: guard предыдущей операции ещё вооружён
previous HY2XS operation is no longer running, but its firewall rollback guard
is still armed: hy2xs-fw-rollback-<op-id>.timer (active/waiting)
Предыдущая операция умерла аварийно после применения firewall. Её процесса уже нет — замка может не быть тоже, — но rollback guard это отдельный объект systemd, и он переживает свой процесс. Если начать новую операцию сейчас, guard сработает посреди неё и вернёт firewall, существовавший до предыдущей операции.
Ничего делать не нужно, кроме как подождать: окно guard — 45 секунд с момента
применения firewall плюс точность таймера (AccuracySec=1s), то есть не больше
46 секунд.
# сколько ещё ждать и что именно висит
systemctl list-units --all --plain 'hy2xs-fw-rollback-*'
hy2xs-orchestrator status --package-dir /usr/local/lib/hy2xs/package
--plain здесь не для красоты: у юнита в состоянии failed systemctl печатает
первой колонкой маркер ●, и без флага его легко не заметить в списке.
Когда guard сработает, отработавший таймер выгрузится (RemainAfterElapse=no),
и операция пройдёт. Состояние failed у сервиса покою не мешает: оно означает,
что откат отработал не полностью, и это как раз повод запустить repair, а не
ждать дальше — подробности в journalctl -u 'hy2xs-fw-rollback-*'. Такой
failed-юнит остаётся загруженным до systemctl reset-failed, но операцию не
блокирует.
Операция отказывает: состояние guard'а не удалось выяснить
unable to verify firewall rollback guard state; systemd query failed,
refusing to start a lifecycle operation: systemctl list-units failed: …
Это не «guard вооружён», и ждать здесь нечего. Барьер обязан доказать, что у
предыдущей операции не осталось исполнителей, способных изменить firewall;
systemctl не ответил, доказательства нет, и операция отказывает до первой
мутации.
Отсутствие ответа не равно отсутствию guard'а: systemd мог быть жив, а взведённый таймер — существовать. Разрешить операцию в этой ситуации означало бы допустить срабатывание старого таймера поверх новой операции.
systemctl status
systemctl list-units --all --plain 'hy2xs-fw-rollback-*'
journalctl -u 'hy2xs-fw-rollback-*' --no-pager
Разбирайтесь с systemd/D-Bus и повторяйте операцию. Отдельно ускорять ничего не
нужно: systemd-run требуется в preflight, поэтому без работающего systemd
операция всё равно не прошла бы — барьер лишь сообщает об этом раньше и точнее.
Установка отказала с firewall_guard_fired
automatic firewall rollback has already fired
phase: firewall_guard_fired
Это означает: автоматический откат firewall сработал раньше, чем операция успела снять guard. Сервер жив и доступен, но работает на прежнем firewall, а не на том, который сгенерировала операция. Именно поэтому фиксация успеха запрещена, даже если smoke успел сойтись, — иначе сервер считался бы настроенным с чужими правилами, что особенно дорого при смене порта Hysteria, SSH или ACME.
Окно guard — 45 секунд (плюс точность таймера, AccuracySec=1s), и оно не
обязано покрывать smoke. Причину ищите в том, почему проход в него не уложился:
journalctl -u 'hy2xs-fw-rollback-*' --no-pager
journalctl -u hysteria-server -u hy2xs-admin --since '-10 min' --no-pager
Обычная причина — медленный старт одного из сервисов. После устранения root-cause операция запускается заново; откат уже вернул сервер в исходное состояние.
Сервер установился, но UI не работает
Проверить:
- разложился ли bundled UI
- корректен ли unit
hy2xs-admin - совпадает ли
HY2XS_ADMIN_INSTALL_DIRс реальностью - не сломан ли bind host / port
Admin UI access via SSH tunnel
Production-модель для UI: HY2XS_UI_BIND_HOST=127.0.0.1, внешний доступ к 8080/tcp не открывается.
Доступ оператора выполняется через SSH local forwarding.
Windows-команда туннеля:
ssh -p 2323 \
-i C:\Users\kirap\.ssh\id_ed25519_uk1 \
-N \
-L 127.0.0.1:8080:127.0.0.1:8080 \
root@185.156.108.141
После запуска открыть http://127.0.0.1:8080/#/login.
Если SSH-туннель не поднимается (administratively prohibited), проверить effective SSH policy:
sshd -T | grep -E '^(port|allowtcpforwarding|permitopen|gatewayports|passwordauthentication|permitrootlogin) '
Рекомендуемый фрагмент hardening sshd_config:
Port 2323
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
AllowTcpForwarding local
PermitOpen 127.0.0.1:8080 localhost:8080
GatewayPorts no
X11Forwarding no
AllowAgentForwarding no
MaxAuthTries 3
LoginGraceTime 20
ClientAliveInterval 300
ClientAliveCountMax 2
Hysteria скачалась, но не стартует
Проверить:
- валиден ли config
- совпадают ли listen port и firewall rule
- домен / SNI / TLS policy
- реальную установленную версию Hysteria
Тестовый клиент не подключается
Проверить:
server_name- порт
obfs.password- auth material
- что используется совместимый клиентский конфиг
Ни один пир не проходит авторизацию
Симптом резкий: подключения перестают устанавливаться у всех сразу, в журнале
админки — device limit unavailable.
Лимит устройств проверяется fail-closed: без ответа /online админка не
знает, сколько устройств уже на связи, и пускать подключения не имеет права.
Значит вопрос ровно один — почему недоступен Traffic Stats API.
# 1. что записано в конфиге
grep -A2 '^trafficStats:' /etc/hysteria/config.yaml
# 2. отвечает ли API по этому адресу
curl -sS -H "Authorization: <trafficStatsSecret>" http://127.0.0.1:36712/online
# 3. что говорит сама админка
journalctl -u hy2xs-admin -n 100 --no-pager | grep -i 'traffic stats'
Частая причина — правка trafficStats.listen руками. Админка обращается к
Traffic Stats API только по loopback, поэтому адрес вроде 192.168.1.10
разводит Hysteria и панель по разным адресам: сама Hysteria работает, туннели
существующих клиентов живут, но лимит устройств, учёт трафика и принудительное
отключение выключаются разом. С таким конфигом админка отказывает явно:
trafficStats.listen слушает 192.168.1.10, а админка обращается к Traffic Stats
API только по loopback. ...Верните 127.0.0.1 через `hy2xs-orchestrator reconfigure`
Страница конфигурации показывает тот же адрес и называет его состояние. Ответов три, и они означают разное:
| адрес | что показано | что это значит |
|---|---|---|
127.0.0.1:36712 |
без пометки | канон production-профиля |
0.0.0.0:36712, :36712 |
предупреждение | API достижим, но опубликован на всех интерфейсах; при HY2XS_FIREWALL_MODE=external|off его не прикрывает ничто |
127.0.0.5:36712 |
ошибка | недостижим, см. ниже |
192.168.1.10:36712 |
ошибка | панель до него не достучится, доступ пиров уже не работает |
Две детали, на которых легко ошибиться:
-
пустой хост в
listen— это не loopback: в Go:36712означает все интерфейсы, ровно как0.0.0.0; -
другой адрес loopback — это не «почти правильно». Слушатель на конкретном адресе принимает соединения только на него:
bind 127.0.0.5:36712 → dial 127.0.0.1:36712 → connection refusedАдминка обращается к Traffic Stats API строго через
127.0.0.1, поэтому127.0.0.5ломает контур доступа так же, как LAN-адрес.
Дашборд показывает «состояние службы неизвестно»
Это не «Hysteria остановлена». Значение означает, что не удалось получить
ответ systemctl is-active hysteria-server: сломанный или недоступный systemctl
при живой Hysteria выглядит именно так.
Проверяется отдельно от туннеля:
systemctl is-active hysteria-server # active | inactive | failed | ...
Доступность Traffic Stats API на дашборде — независимый факт, полученный фактическим обращением к API. Сочетание «состояние неизвестно» + «API доступен» означает исправно работающий туннель и сломанную диагностику службы; сочетание «служба активна» + «API недоступен» — предыдущий раздел.
Install/reconfigure падает на DNS AAAA
Проверить значение HY2XS_DNS_AAAA_POLICY в /etc/hy2xs/hy2xs.env:
strict(default): AAAA приводит к fail в IPv4-only профиле;warn: warning + продолжение;off: AAAA-check отключён.
Для production baseline рекомендуется strict.
DNS IPv4 mismatch: DNS ведёт не на этот сервер
Сообщение выглядит так:
DNS IPv4 mismatch for HY2XS_PUBLIC_HOST fi.api.withen.pro:
DNS A records: 185.xxx.xxx.10
server public IPv4: 185.xxx.xxx.27
Update the DNS A record before using this server.
Это не ложное срабатывание, а именно то, ради чего проверка сделана: сервисы на машине живы, но публичный endpoint ведёт куда-то ещё. Чаще всего — после принудительной смены IPv4 провайдером.
Что делать:
- сверить фактический адрес сервера:
ip -4 addr show scope global; - обновить A-запись у DNS-провайдера;
- дождаться истечения TTL;
- повторить
hy2xs-orchestrator doctor.
doctor безопасно запускать на работающем сервере: он не перезапускает
сервисы и живые соединения не рвёт. Раньше это было не так — команда звала
общий smoke, который начинается с systemctl restart hysteria-server hy2xs-admin, и диагностика подозрения на проблему сама создавала обрыв у всех
подключённых клиентов.
Безопасность здесь — инвариант рантайма, а не свойство текущего кода. doctor
целиком выполняется под тем же read-only guard, что и PHASE 0 установки: любая
запись в файл и любой мутирующий вызов под ним отказывают. Раньше от рестарта
защищал один принудительный флаг, а остальные проверки smoke — чтение прав,
владельцев и синтаксиса nftables — выполнялись мутирующими раннерами, поэтому
настоящая мутация, случайно добавленная в smoke, была бы разрешена молча.
При этом диагностика не сужается: слушатели, healthz, права на файлы, machine
auth, trafficStats, версия бинаря, семантика /etc/hysteria/config.yaml и
синтаксис nft проверяются полностью.
Где проходит граница read-only
Guard действует внутри процесса оркестратора. Он не способен запретить побочный эффект, который вызвал бы HTTP-запрос в другом процессе, поэтому эта половина границы держится не им, а составом проб.
Существенный случай — machine-auth. Успешная авторизация пира заставляет админку
выполнить UPDATE peer.last_connection_at, то есть диагностика изменила бы
отображаемое «последнее подключение» у bootstrap-admin-peer. Поэтому проба с
действующим паролем выполняется только в режиме install; doctor работает
в режиме reconfigure и до неё не доходит. Полный happy-path авторизации
проверяют установка и E2E, а не диагностика.
doctor отправляет только пробы, которые заведомо не проходят авторизацию
(отсутствующий machine token, неверные учётные данные, некорректный тип поля) и
читающие запросы (/healthz, trafficStats /online). Ни одна из них не
изменяет данные.
Честная формулировка гарантии:
doctorне изменяет конфигурацию, состояние сервисов, firewall и данные.
Единственный след, который он оставляет, — записи в журнале админки: пробы проходят через обычный обработчик логирования, как любой запрос. Это не состояние системы, но и не «совсем ничего», поэтому сказано прямо.
Вариант server public IPv4: пустой означает, что на интерфейсах нет ни одного
публичного маршрутизируемого IPv4 — сервер за NAT. Это топология вне baseline;
осознанное решение оформляется через HY2XS_PUBLIC_ENDPOINT_POLICY=warn.
Если в A-записях присутствует правильный адрес и посторонний, проверка тоже отказывает. HY2XS — single-host профиль: второй backend за тем же именем означает, что часть клиентов попадёт не на этот сервер.
Скорость не соответствует ожиданиям
Проверить:
bandwidth.*на сервере- клиентские
up_mbps/down_mbps - нет ли ложного ожидания, что один только host BBR решает speed policy
Изменили post-install.env, но runtime не изменился
Это ожидаемо.
post-install.env — reference file, а не autoreconcile engine.
Редактировать нужно /etc/hy2xs/hy2xs.env и затем запускать reconfigure --dry-run/--apply.
Изменили bootstrap-поля, но пароль admin не сменился
Это ожидаемо.
HY2XS_ADMIN_INITIAL_PASSWORD и HY2XS_ADMIN_CON_PASS используются только как bootstrap-данные при первичной установке.
Для ротации существующих credentials нужен отдельный flow на уровне account-management.
hy2xs-admin не стартует: «HY2XS_ADMIN_INITIAL_PASSWORD не задан»
Означает, что учётной записи администратора в базе нет, а переменной, из которой её положено создать, — тоже.
Придумывать пароль самостоятельно админка не будет: такой пароль не знал бы никто, кроме журнала, а раньше именно он туда и попадал открытым текстом. Сообщение говорит о повреждённом контракте запуска.
Что проверять:
systemctl cat hy2xs-admin | grep EnvironmentFile
grep -c '^HY2XS_ADMIN_INITIAL_PASSWORD=' /etc/hy2xs/hy2xs.env
grep -c '^ADMIN_INITIAL_PASSWORD=' /etc/hy2xs/bootstrap-admin.secret
Починка — hy2xs-orchestrator repair --allow-partial-state: значения принадлежат
оркестратору, он же приводит hy2xs.env и bootstrap-admin.secret в
согласованное состояние.
hy2xs-admin не стартует: «HY2XS_ADMIN_INITIAL_PASSWORD не удовлетворяет контракту панели»
Значение задано, но панель его не приняла бы на форме входа, поэтому учётная запись администратора с ним не создаётся: установка иначе завершилась бы успешно, а войти было бы нельзя.
Контракт пароля — две границы в разных единицах и один запрет:
| Требование | Кто его ставит |
|---|---|
| 6-64 символа Unicode | форма входа и форма смены пароля |
| не более 72 байт в UTF-8 | bcrypt (ErrPasswordTooLong) |
| без управляющих символов | формат KEY=VALUE, который читает systemd |
Границы независимы: у 64 символов длина от 64 до 256 байт. Пароль из 64 кириллических букв — это 128 байт, и он отвергается, хотя в границу символов укладывается. Практический предел: 36 кириллических букв или 18 эмодзи.
Набор символов не ограничен ничем сверх этого, а пробелы по краям являются
частью пароля. Именно поэтому такое значение записывается в hy2xs.env в
двойных кавычках:
HY2XS_ADMIN_INITIAL_PASSWORD="пароль с пробелом на конце "
Без кавычек пробелы по краям срежет systemd — файл объявлен
EnvironmentFile= в юните, — и админка получит не то значение, которое вы
записали.
Починка: исправьте значение в /etc/hy2xs/hy2xs.env и выполните
hy2xs-orchestrator repair --allow-partial-state.
Как посмотреть bootstrap-пароль
/etc/hy2xs/bootstrap-admin.secret — файл того же формата KEY=VALUE, и
значения в нём могут быть закавычены. Читать их cut -d= -f2- нельзя: кавычки
уедут в пароль. source и eval тоже не годятся — shell выполнит подстановку
команд внутри двойных кавычек, чего сам systemd не делает.
read_bootstrap_field() {
sudo sed -n "s/^$1=//p" /etc/hy2xs/bootstrap-admin.secret | head -n1 \
| sed -e 's/^"//' -e 's/"$//' -e 's/\\\(["\\]\)/\1/g'
}
read_bootstrap_field ADMIN_USER
read_bootstrap_field ADMIN_INITIAL_PASSWORD
Аналогичное сообщение про HY2XS_ADMIN_CON_PASS относится к пиру установщика.
Его секрет продублирован в bootstrap-admin.secret, откуда его читает проверка
machine-auth, поэтому придуманный секрет разошёлся бы с файлом и первая же
проверка подключения провалилась бы.
Забыт пароль администратора
systemctl stop hy2xs-admin
# Значения читаются КАК ДАННЫЕ. Обоснование — ниже, оно существенно.
read_runtime_field() {
sed -n "s/^$1=//p" /etc/hy2xs/hy2xs.env | head -n1 \
| sed -e 's/^"//' -e 's/"$//' -e 's/\\\(["\\]\)/\1/g'
}
HY2XS_INSTALL_DIR="$(read_runtime_field HY2XS_INSTALL_DIR)"
HY2XS_DATA_DIR="$(read_runtime_field HY2XS_DATA_DIR)"
HY2XS_LOG_DIR="$(read_runtime_field HY2XS_LOG_DIR)"
export HY2XS_DATA_DIR HY2XS_LOG_DIR
"$HY2XS_INSTALL_DIR/hy2xs-admin" reset-admin
systemctl start hy2xs-admin
Пути к базе и журналу (HY2XS_DATA_DIR, HY2XS_LOG_DIR) берутся из runtime env
намеренно: это те же значения, с которыми работает юнит.
Почему не set -a; . /etc/hy2xs/hy2xs.env. Здесь стояла именно эта строка, и
она стала опасной ровно тогда, когда файл научился нести произвольные значения.
Оператор задаёт HY2XS_ADMIN_INITIAL_PASSWORD, набор символов у пароля не
ограничен, и запись в файле выглядит так:
HY2XS_ADMIN_INITIAL_PASSWORD="$(touch /tmp/pwn)"
Для systemd это буквальное значение: подстановки в EnvironmentFile= нет
вовсе, $ там обычный символ. Но . (source) обрабатывает файл bash, а
bash внутри двойных кавычек выполняет подстановку команд — и выполнил бы её от
root, вместе с рецептом восстановления доступа.
То же правило действует и для /etc/hy2xs/bootstrap-admin.secret (см. «Как
посмотреть bootstrap-пароль» выше): файлы этого формата читаются как ДАННЫЕ.
Результат $(read_runtime_field …) повторно как код не исполняется — он
становится значением переменной, и это принципиальная разница.
Команда печатает новые логин и пароль в консоль и требует смены пароля при первом входе. Работает поверх существующей установки; на машине без базы она осмысленно откажет — это инструмент восстановления, а не установки.
Автоматический сброс трафика не срабатывает
Проверьте сохранённое расписание:
journalctl -u hy2xs-admin | grep RESET_TRAFFIC_CRON
Невалидное выражение теперь отклоняется API до записи в базу, поэтому попасть в
это состояние можно только правкой базы в обход продукта. Планировщик в таком
случае поднимается без джобы сброса и пишет ERROR — старт сервиса при этом не
прерывается намеренно: на панели висит /internal/hysteria/auth, и её отказ
положил бы подключения пользователей.
Починка — сохранить корректное значение в разделе настроек панели; оно применяется сразу, без перезапуска сервиса. Пустое значение — легальное и означает «автоматический сброс выключен».
Правила эксплуатации
- Не править сервер как будто на нём есть builder.
- Не считать bundled UI источником install-policy.
- Не считать
post-install.envавтоматическим механизмом применения изменений. - Не расширять install-only baseline до lifecycle-manager без отдельного проектного решения.
- Не смешивать install baseline и access/bot platform в одной документации.
- Не включать IPv6 в runtime-политике HY2XS (проект IPv4-only).