Files
HY2XS_flamy/docs/operations/13-production-runbook.md

364 lines
22 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` | восстановление отработало полностью |
| `systemctl reset-failed <unit> exited with …, but the cleanup postcondition is satisfied` | юнит уже выгружен или не находится в `failed`; это допустимый идемпотентный cleanup, ручное восстановление не требуется |
| `manual recovery data preserved at /run/hy2xs/rollback/<op>` | 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 <unit> 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/<op-id>/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-<op-id>.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 <path>`;
- `--format auto` пытается определить формат по имени файла, при неоднозначности используйте `--format env|yaml`;
- YAML редактируется структурно (документ разбирается и обходится как дерево),
поэтому вложенные секреты вроде `auth.http.url?access_token=…` не переживают
редакцию, а результат остаётся валидным YAML;
- в env-файлах секрет вырезается и из URL-значения, даже если имя ключа
несекретное — например, `HY2_AUTH_URL` в `post-install.env`.
- `HY2XS_FORCE_PASSWORD_CHANGE=true|false` сохраняется как диагностический
boolean; это точное исключение, остальные ключи с `PASSWORD` редактируются.
Та же редакция применяется к diagnostics-бандлу
(`hy2xs-orchestrator diagnostics collect`), который собирается автоматически при
неудачной установке или реконфигурации. Бандл предназначен для передачи наружу,
поэтому попадающие в него `hy2xs.env`, `post-install.env` и `config.yaml`
редактируются перед упаковкой. Готовый архив находится в
`/var/lib/hy2xs/diagnostics`, принадлежит `root:root` и имеет режим `0600`.
Сам каталог имеет режим `0700`; не ослабляйте его права и не переносите bundle
в `HY2XS_LOG_DIR`, доступный сервисному пользователю.
При отказе **до** начала применения изменений (`fatal_pre_apply`) бандл не
собирается: его сбор сам создал бы каталог в `/var/lib/hy2xs/diagnostics` на сервере,
который мы обещали не трогать.