Files
HY2XS_flamy/docs/12-operations-and-troubleshooting.md
T
founder 76d78ac71f fix(orchestrator): закрыть два остатка на стыке guard и замка операций
Оба дефекта — в механизмах, введённых предыдущими коммитами, и оба относятся к
гарантиям, ради которых эти механизмы вводились.

1. Отказ записи `auto-rollback-fired` оставался незамеченным.

Инвариант фиксации "маркера нет и юниты inactive => guard не сработал" верен
только при дополнительном условии "guard способен записать маркер". Пока `rc=0`
стояло ПОСЛЕ создания маркера, отказ записи (заполненный tmpfs /run, read-only
ФС) не влиял ни на что: скрипт успешно восстанавливал прежний firewall,
завершался кодом 0, юнит уходил в inactive, маркера не было — и операция
фиксировала успех после реально сработавшего отката.

`rc` объявляется до первой операции, включая создание маркера, а ранний выход
возвращает его вместо жёсткого `exit 0`. У факта срабатывания появилось два
независимых канала: маркер и отказ юнита, потому что на пути фиксации успеха
допустим ровно один ActiveState — inactive.

Заодно маркер создаётся `touch`, а не `: >file`: двоеточие — special builtin
POSIX, ошибка перенаправления на нём обязана завершить неинтерактивный shell
целиком, и в dash скрипт умер бы ДО восстановления firewall.

2. Новая операция могла начаться, пока guard предыдущей ещё вооружён.

Замок действует, пока жив процесс-держатель. Guard — отдельный объект systemd,
переживающий свой процесс:

    A берёт замок -> применяет firewall -> вооружает guard на 45s
    A аварийно умирает
    B берёт замок и начинает менять production paths
    guard A срабатывает и возвращает firewall, который был ДО A

Случай SIGTERM/SIGHUP хуже, чем kill -9: обработчик снимает замок сам, поэтому
проверка живости держателя не видит вообще ничего, а таймер остаётся.

Введён барьер покоя `assertNoPendingRollbackGuard`, через который проходит
каждый захват замка — дважды, до и после, потому что между ними умирающая
операция успевает вооружить guard, — и PHASE 0 установщика. Непокоем считаются
active/activating/deactivating/reloading; `failed` и `inactive` — покой, иначе
барьер блокировал бы `repair`, которым чинят последствия.

Плюс P1: восстановление UnitFileState у nftables.service больше не обещает
точности, которой не даёт. `enable --runtime` не удаляет постоянную ссылку,
поэтому "восстановление" enabled-runtime оставляло юнит включённым в обоих
scope. Восстанавливаются enabled/disabled — то, что операция реально меняет, —
остальные состояния называются оператору и не трогаются.

Тесты: поведенческая проверка раннего пути rollback-скрипта настоящим shell
(ветка заканчивается до первой команды восстановления и безопасна для запуска),
проверка двойного вызова барьера и снятия замка при его отказе, структурные
инварианты. Приёмка и docs (D1h, уточнение D1f) — там же.
2026-08-31 03:30:14 +05:00

24 KiB
Raw Blame History

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)

Предыдущая операция умерла аварийно после применения firewall. Её процесса уже нет — замка может не быть тоже, — но rollback guard это отдельный объект systemd, и он переживает свой процесс. Если начать новую операцию сейчас, guard сработает посреди неё и вернёт firewall, существовавший до предыдущей операции.

Ничего делать не нужно, кроме как подождать: окно guard — 45 секунд с момента применения firewall.

# сколько ещё ждать и что именно висит
systemctl list-units --all 'hy2xs-fw-rollback-*'
hy2xs-orchestrator status --package-dir /usr/local/lib/hy2xs/package

Когда guard сработает, юнит перестанет быть active, и операция пройдёт. Состояние failed у него покою не мешает: оно означает, что откат отработал не полностью, и это как раз повод запустить repair, а не ждать дальше — подробности в journalctl -u 'hy2xs-fw-rollback-*'.

Установка отказала с firewall_guard_fired

automatic firewall rollback has already fired
phase: firewall_guard_fired

Это означает: автоматический откат firewall сработал раньше, чем операция успела снять guard. Сервер жив и доступен, но работает на прежнем firewall, а не на том, который сгенерировала операция. Именно поэтому фиксация успеха запрещена, даже если smoke успел сойтись, — иначе сервер считался бы настроенным с чужими правилами, что особенно дорого при смене порта Hysteria, SSH или ACME.

Окно guard — 45 секунд, и оно не обязано покрывать 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
  • что используется совместимый клиентский конфиг

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 провайдером.

Что делать:

  1. сверить фактический адрес сервера: ip -4 addr show scope global;
  2. обновить A-запись у DNS-провайдера;
  3. дождаться истечения TTL;
  4. повторить 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_CON_PASS относится к пиру установщика. Его секрет продублирован в bootstrap-admin.secret, откуда его читает проверка machine-auth, поэтому придуманный секрет разошёлся бы с файлом и первая же проверка подключения провалилась бы.

Забыт пароль администратора

systemctl stop hy2xs-admin
set -a; . /etc/hy2xs/hy2xs.env; set +a
"$HY2XS_INSTALL_DIR/hy2xs-admin" reset-admin
systemctl start hy2xs-admin

Runtime env подключается намеренно: из него берутся пути к базе и журналу (HY2XS_DATA_DIR, HY2XS_LOG_DIR) — те же, с которыми работает юнит.

Команда печатает новые логин и пароль в консоль и требует смены пароля при первом входе. Работает поверх существующей установки; на машине без базы она осмысленно откажет — это инструмент восстановления, а не установки.

Автоматический сброс трафика не срабатывает

Проверьте сохранённое расписание:

journalctl -u hy2xs-admin | grep RESET_TRAFFIC_CRON

Невалидное выражение теперь отклоняется API до записи в базу, поэтому попасть в это состояние можно только правкой базы в обход продукта. Планировщик в таком случае поднимается без джобы сброса и пишет ERROR — старт сервиса при этом не прерывается намеренно: на панели висит /internal/hysteria/auth, и её отказ положил бы подключения пользователей.

Починка — сохранить корректное значение в разделе настроек панели; оно применяется сразу, без перезапуска сервиса. Пустое значение — легальное и означает «автоматический сброс выключен».

Правила эксплуатации

  1. Не править сервер как будто на нём есть builder.
  2. Не считать bundled UI источником install-policy.
  3. Не считать post-install.env автоматическим механизмом применения изменений.
  4. Не расширять install-only baseline до lifecycle-manager без отдельного проектного решения.
  5. Не смешивать install baseline и access/bot platform в одной документации.
  6. Не включать IPv6 в runtime-политике HY2XS (проект IPv4-only).