# HY2XS production runbook ## 1. Supported target - clean Debian 13 amd64 - single host install profile - IPv4-only runtime model ## 2. Required prerequisites ```bash apt-get update apt-get install -y sudo ca-certificates curl iproute2 tar openssl nftables systemd ``` `sudo` обязателен для permission smoke-checks от имени runtime-пользователей. Предварительная ручная установка `sudo` до запуска `./install.sh` не требуется: на clean-host install-flow ставит его на стадии deps до выполнения smoke-checks. ## 3. Required open ports - UDP `${HY2XS_HYSTERIA_PORT}` - TCP `${HY2XS_UI_PORT}` (обычно localhost bind) - TCP `${HY2XS_SSH_PORT}` - TCP 80/443 для ACME (в зависимости от типа challenge) ## 4. Clean host assumptions - нет legacy-конфликта по runtime-users (`hysteria`, `hy2xs-admin`) - нет конфликтующего не-HY2XS nftables entrypoint - install запускается от root ## 5. Install command ```bash ./install.sh --non-interactive ``` Важно: packaged baseline использует `HY2XS_SSH_PORT=2323` по умолчанию. На target-хосте оператор обязан выставить свой рабочий SSH-порт в `/etc/hy2xs/hy2xs.env` и применить изменения через `reconfigure --apply`. ## 6. Post-install verification ```bash systemctl status hysteria-server systemctl status hy2xs-admin ss -H -lun | grep ':443' ss -H -ltn | grep ':8080' nft list ruleset cat /var/lib/hy2xs/install-state.json ``` ## 7. Permission verification ```bash ls -l /etc/hy2xs/hy2xs.env ls -l /etc/hysteria/config.yaml sudo -u hysteria test -r /etc/hysteria/config.yaml sudo -u hy2xs-admin test -r /etc/hysteria/config.yaml sudo -u hy2xs-admin test ! -w /etc/hysteria/config.yaml sudo -u hy2xs-admin test ! -r /etc/hy2xs/hy2xs.env ``` ## 8. Firewall recovery Если `install`/`reconfigure` падают после firewall apply: - rollback guard не должен отменяться до успешного smoke; - для recovery использовать вывод оркестратора и перезапускать apply только после устранения root-cause. Откат после операционного отказа выполняется целиком и сам по себе не может быть отменён: ни неудачной записью состояния в `/var/lib/hy2xs`, ни отказом одной из своих стадий. Поэтому в журнале нужно читать две разные вещи: | Строка в журнале | Что она означает | | --- | --- | | `failed to persist failure state, continuing with the mandatory rollback` | маркер не обновился (обычно заполненный диск), но восстановление выполнено; после освобождения места запустить `doctor` | | `rollback stage "<имя>" failed, continuing with the remaining stages` | конкретная половина восстановления не отработала; остальные выполнены | | `rollback finished with N failed stage(s); manual recovery may be required` | итог: перечисленные стадии требуют ручной проверки | | `rollback completed: N stage(s) succeeded` | восстановление отработало полностью | | `manual recovery data preserved at /run/hy2xs/rollback/` | firewall восстановлен не полностью; прежние `nftables.conf` и `hy2xs.nft` лежат по этому пути | | `firewall rollback guard armed: … fires in 45s (timer accuracy 1s)` | guard взведён; с этого момента операция обязана снять его до фиксации успеха | | `firewall rollback guard disarmed and proven inactive` | guard снят, и это подтверждено состоянием юнитов и отсутствием маркера срабатывания | | `automatic firewall rollback has already fired` | guard успел сработать; сервер работает на **прежнем** firewall, операция обязана завершиться отказом | | `firewall rollback guard is still in state "…"` | остановить guard не удалось; фиксация успеха запрещена, разбирайтесь с systemd | | `unable to verify firewall rollback guard state; systemd query failed` | состояние guard'а недоказуемо; операция не начата, чинить нужно systemd, а не ждать | Отдельно про сработавший guard. Окно 45 секунд намеренно короче худшего случая smoke и не обязано его покрывать: доказательством служит не время, а маркер ```text /run/hy2xs/rollback//auto-rollback-fired ``` Если он есть — операция откатывается независимо от результата smoke, и в маркере установки появляется `phase: firewall_guard_fired`. Это значит: сервер жив и работает на прежнем firewall, а причину, по которой проход не уложился в окно, надо искать в journal — обычно это медленный старт одного из сервисов. Юнит автоотката при частичном восстановлении уходит в `failed`, поэтому его стоит прочитать целиком: ```bash journalctl -u 'hy2xs-fw-rollback-*' --no-pager ``` Резервные копии не удаляются, пока восстановление не подтверждено, и переживают долговечную запись `phase: installed`. Поэтому при разборе неудачи всегда осмысленно посмотреть: ```bash ls -la /run/hy2xs/rollback/ # копии firewall текущей операции ls -la /etc/hy2xs/backups/ # копия состояния до последнего reconfigure cat /etc/hy2xs/backups/*/manifest.json ``` Имя каталога совпадает с полем `op_id` в `/var/lib/hy2xs/install-state.json`. Манифест прямо говорит, какие файлы существовали до операции, а какие нет: запись `"present": false` означает, что откат обязан был файл **удалить**, а не восстановить. Наружу оркестратор всегда пробрасывает **исходную** ошибку операции, а не проблему внутри отката: последняя — это информация о том, что осталось не восстановленным, а не причина отказа. ## 8a. Одна операция за раз `install`, `reconfigure`, `repair` и `doctor` сериализованы эксклюзивным замком: ```text /run/lock/hy2xs-orchestrator.lock ``` Вторая операция отказывает сразу и **до первой мутации** — до снятия резервной копии, до записи конфигов, до firewall: ```text another HY2XS operation is already in progress: reconfigure (pid 4242, started at …) ``` `doctor` тоже берёт замок: диагностика в середине транзакции описывает промежуточное состояние сервера и выдаёт бессмысленные ошибки по временным несоответствиям. `status` и `diagnostics collect` замок **не** берут — они нужны в том числе во время долгой операции, — но сообщают о ней: ```bash hy2xs-orchestrator status --package-dir /usr/local/lib/hy2xs/package # "operation_in_progress": "reconfigure (pid 4242, started at …)" ``` Замок снимается при любом завершении держателя: штатном, по `Ctrl+C`, по SIGTERM от systemd и при обрыве SSH. Если процесс был убит `kill -9`, следующая операция обнаружит мёртвого держателя и переиспользует замок сама: ```text operation lock … is held by install (pid 1234), which is no longer running; reclaiming it ``` **Замка при этом недостаточно, и это важно.** Он действует, пока жив процесс-держатель, а rollback guard firewall — отдельный объект systemd, переживающий свой процесс. Аварийно умершая операция оставляет guard вооружённым, и он способен вернуть прежний firewall уже посреди следующей операции. Поэтому каждый захват замка проходит ещё и через барьер покоя: ```text previous HY2XS operation is no longer running, but its firewall rollback guard is still armed: hy2xs-fw-rollback-.timer (active/waiting) ``` Условие старта — не «PID предыдущей мёртв», а «у предыдущей не осталось исполнителей, способных изменить систему». Ждать нужно не больше 45–46 секунд с момента применения firewall; `failed` у guard покою не мешает и означает, что пора смотреть `journalctl -u 'hy2xs-fw-rollback-*'` и запускать `repair`. Второй отказ того же барьера выглядит иначе и требует другого действия: ```text unable to verify firewall rollback guard state; systemd query failed, refusing to start a lifecycle operation ``` Здесь ждать бессмысленно. Барьер обязан **доказать** покой, а не предположить его: отсутствие ответа systemd — это отсутствие наблюдения, а не наблюдение отсутствия guard'а. Смотрите `systemctl status` и повторяйте операцию после того, как systemd отвечает. `/run/lock` — это tmpfs, поэтому перезагрузка снимает замок в любом случае. Удалять файл руками нужно только если в нём оказалось непонятное содержимое: такой замок сознательно не переиспользуется автоматически — непонятый файл не доказывает, что операции нет. ## 9. Reconfigure flow ```bash hy2xs-orchestrator reconfigure --package-dir /usr/local/lib/hy2xs/package --config /etc/hy2xs/hy2xs.env --dry-run hy2xs-orchestrator reconfigure --package-dir /usr/local/lib/hy2xs/package --config /etc/hy2xs/hy2xs.env --apply ``` ## 10. Admin bootstrap credentials - `HY2XS_ADMIN_INITIAL_PASSWORD` и `HY2XS_ADMIN_CON_PASS` — install-only bootstrap поля. - изменение значений в `/etc/hy2xs/hy2xs.env` после install не выполняет автоматическую ротацию существующих credentials. ## 10a. Отзыв учётных данных пира Смена секрета в панели — операция отзыва, и она выполняется целиком: ```text 1. новый secret_digest и новый auth_id записываются одной операцией 2. POST /kick по СТАРОМУ auth_id 3. если разрыв не удался либо соединение зарегистрировалось уже после него — старая сессия становится orphan и завершается очередным циклом учёта ``` Что это значит для оператора: - гарантия отзыва — **не позднее 30 секунд** (интервал цикла учёта), а не «до переподключения клиента по своей воле»; - частичный результат (`peer_disconnect_failed`) означает лишь то, что первая попытка разрыва не удалась: повторять операцию не требуется, состояние сойдётся само; - идентификатор пира в списке (`authId`) после смены секрета меняется — это идентичность поколения сессий, а не постоянный идентификатор записи; - трафик старой сессии за эти секунды не приписывается пиру и попадает в потери цикла учёта (запись уровня `error` в журнале админки). Для операционной границы доступа это допустимо; биллингом учёт трафика в `1.0.0` не является. ## 11. IPv4/IPv6 policy - HY2XS работает в IPv4-only режиме. - если IPv6 включён на хосте/провайдере — это вне baseline и должно быть отдельно управляемо оператором. - `HY2XS_DNS_AAAA_POLICY` управляет реакцией preflight на DNS AAAA: - `strict` (default) — install/reconfigure прекращается при наличии AAAA; - `warn` — выводится warning и выполнение продолжается; - `off` — AAAA-проверка игнорируется. ## 11a. Публичный endpoint - preflight проверяет, что A-записи `HY2XS_PUBLIC_HOST` (и `HY2XS_DOMAIN`, если он отличается) ведут на публичные IPv4 **этого** сервера; - проверка работает в `install`, `reconfigure` и `doctor`; - адрес сервера определяется локально по интерфейсам, без обращения к внешним сервисам определения IP; - `HY2XS_PUBLIC_ENDPOINT_POLICY` управляет строгостью: - `strict` (default) — расхождение останавливает операцию; - `warn` — warning и продолжение (NAT, floating IP, anycast); - `off` — сравнение не выполняется; - отсутствие A-записи остаётся фатальным при любом значении политики. Типичный сценарий, ради которого это сделано: провайдер принудительно сменил IPv4, DNS остался старым. До v1 `doctor` в такой ситуации отвечал успехом, а клиентская ссылка отправляла людей на чужую машину. ## 12. Validation command ```bash hy2xs-orchestrator doctor --package-dir /usr/local/lib/hy2xs/package --config /etc/hy2xs/hy2xs.env ``` Команда выполняет preflight + smoke как post-install/post-reboot validation. `doctor` **не перезапускает сервисы**: он диагностирует работающую установку. Раньше он собирал контекст с параметрами по умолчанию и звал общий smoke, а тот первым же действием выполняет `systemctl restart hysteria-server hy2xs-admin` — то есть команда, которую этот раздел предлагает запускать при подозрении на проблему, гарантированно обрывала все живые VPN-соединения, включая случай, когда с сервисом всё в порядке. Диагностика, меняющая то, что диагностирует, отвечает не на заданный вопрос: после рестарта проверяется уже другое состояние. Остальные проверки smoke выполняются полностью — слушатели, права и владельцы файлов, machine auth (включая негативные случаи), семантика `/etc/hysteria/config.yaml` против production-профиля, версия установленного бинаря. Состояние сервера ни одна из них не меняет. Перезапуск сервисов остаётся операцией `install`, `reconfigure --apply` и `repair` — там он является частью применения изменений, а не проверкой. ## 13. Admin UI access via SSH tunnel Production policy: UI остаётся loopback-only (`HY2XS_UI_BIND_HOST=127.0.0.1`), внешний доступ к `8080/tcp` не открывается. Операторский доступ выполняется через SSH local forwarding. Windows tunnel command: ```bash 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 ``` Open in browser: `http://127.0.0.1:8080/#/login`. Если туннель падает с `administratively prohibited`, проверить effective sshd-конфиг: ```bash sshd -T | grep -E '^(port|allowtcpforwarding|permitopen|gatewayports|passwordauthentication|permitrootlogin) ' ``` Recommended sshd hardening fragment: ```sshconfig 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 ``` ## 14. Secret-safe config sharing Для передачи конфигов в тикеты/чаты используйте встроенную redaction-команду: ```bash hy2xs-orchestrator redact-config --config /etc/hy2xs/hy2xs.env --out /root/hy2xs.redacted.env hy2xs-orchestrator redact-config --config /etc/hysteria/post-install.env --out /root/post-install.redacted.env hy2xs-orchestrator redact-config --config /etc/hysteria/config.yaml --out /root/hysteria-config.redacted.yaml --format yaml ``` Инварианты: - команда не выводит исходные секреты в stdout; - требуется выбрать ровно один режим: `--in-place` или `--out `; - `--format auto` пытается определить формат по имени файла, при неоднозначности используйте `--format env|yaml`; - YAML редактируется структурно (документ разбирается и обходится как дерево), поэтому вложенные секреты вроде `auth.http.url?access_token=…` не переживают редакцию, а результат остаётся валидным YAML; - в env-файлах секрет вырезается и из URL-значения, даже если имя ключа несекретное — например, `HY2_AUTH_URL` в `post-install.env`. Та же редакция применяется к diagnostics-бандлу (`hy2xs-orchestrator diagnostics collect`), который собирается автоматически при неудачной установке или реконфигурации. Бандл предназначен для передачи наружу, поэтому попадающие в него `hy2xs.env`, `post-install.env` и `config.yaml` редактируются перед упаковкой. При отказе **до** начала применения изменений (`fatal_pre_apply`) бандл не собирается: его сбор сам создал бы каталоги в `/var/log/hy2xs` на сервере, который мы обещали не трогать.