b22b4b0d99
Два свойства были описаны в документации, но не обеспечены кодом.
1. doctor «не изменяет диагностируемую систему».
Принудительный skipServiceStart закрывал ровно одну ИЗВЕСТНУЮ мутацию —
рестарт сервисов. Всё остальное в smoke держалось на том, что автор правки
выбрал правильный раннер: `test -s`, `grep -q`, `stat`, `sudo -u ... test`
и `nft -c` шли через мутирующий namespace, хотя ничего не меняют. Ожидание
между попытками выполнялось подпроцессом `sleep` через runMutatingHidden,
то есть пауза между двумя чтениями объявлялась изменением системы.
Следствие: настоящая мутация, случайно добавленная в smoke, ничем бы от них
не отличалась и была бы разрешена в doctor молча — а включить guard было
нельзя, он отказал бы на первой же читающей команде.
Команды классифицированы честно, `sleep` заменён таймером, и doctor целиком
выполняется под тем же read-only guard, что и PHASE 0 установки. Guard
снимается в finally. Диагностика при этом не сузилась: слушатели, healthz,
права, machine auth, trafficStats, версия бинаря, семантика конфига и
синтаксис nft проверяются полностью.
2. reset-admin различает «администратора нет» и «база не ответила».
Слой данных специально возвращает разные sentinel'ы, но команда склеивала их
обычным `if err != nil { создать } else { обновить }`. Опасен здесь не
только нарушенный смысл: при транзиентном отказе чтения («database is
locked») ветка создания отрабатывала успешно, и в таблице оказывались ДВЕ
учётные записи администратора. GetAdminUser берёт First() и о второй строке
не сообщает — на сервере оставалась вторая рабочая учётка с паролем, уже
напечатанным на экран, и ни один запрос об этом не говорил.
Заодно исправлено проглатывание ошибки хеширования: в ветке обновления
стояло `hash, _ := util.HashPassword(password)` внутри литерала map. При
отказе bcrypt в password_hash уезжала пустая строка, а на экран печатался
пароль, которым войти уже невозможно — VerifyPassword отклоняет всё, что не
bcrypt. Команда восстановления доступа умела молча его отобрать.
Тесты: doctor-readonly.test.ts дополнен поведенческой проверкой guard и
контролем набора раннеров в smoke; apps/cmd/reset_test.go проверяет обе ветки
на настоящей SQLite и отказ чтения при полностью работоспособной базе — ровно
тот случай, который прежний код превращал во второго администратора. Добавлена
dao.CountAdminUsers: до неё появление дубликата было ненаблюдаемым.
368 lines
17 KiB
Markdown
368 lines
17 KiB
Markdown
# Operations and troubleshooting
|
||
|
||
## Цель документа
|
||
|
||
Зафиксировать минимальный operational контур после установки.
|
||
|
||
## Что должен помнить оператор
|
||
|
||
### 0. Target prerequisites обязательны
|
||
|
||
На target-хосте install-flow сам обеспечивает системные зависимости (deps stage):
|
||
|
||
```bash
|
||
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 вручную
|
||
|
||
## Базовые команды проверки
|
||
|
||
Проверка сервисов:
|
||
```bash
|
||
systemctl status hysteria-server
|
||
systemctl status hy2xs-admin
|
||
```
|
||
|
||
Проверка порта:
|
||
```bash
|
||
ss -uln
|
||
```
|
||
|
||
Проверка firewall:
|
||
```bash
|
||
nft list ruleset
|
||
```
|
||
|
||
Проверка `post-install.env`:
|
||
```bash
|
||
cat /etc/hysteria/post-install.env
|
||
```
|
||
|
||
Проверка логов через journald:
|
||
```bash
|
||
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:
|
||
```bash
|
||
cat /var/lib/hy2xs/install-state.json
|
||
```
|
||
|
||
Если `reconfigure` сообщает об отсутствии marker, нужно повторно выполнить чистый install и только потом применять runtime-изменения.
|
||
|
||
## Auth endpoint fail checklist
|
||
|
||
```bash
|
||
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](14-legacy-cleanup.md): сохранить данные, посмотреть план
|
||
`tools/legacy/purge-v0.sh`, выполнить очистку, установить заново.
|
||
|
||
Проверить хост, ничего не устанавливая:
|
||
|
||
```bash
|
||
./orchestrator/hy2xs-orchestrator preflight-install --package-dir "$(pwd)"
|
||
```
|
||
|
||
### `reconfigure`/`repair` отказываются: маркер чужого поколения
|
||
|
||
```text
|
||
Маркер установки /var/lib/hy2xs/install-state.json не относится к текущему
|
||
поколению HY2XS.
|
||
```
|
||
|
||
`installed: true` сам по себе ничего не доказывает: такой же маркер мог
|
||
остаться от `0.x`. Обе команды проверяют `product`, `release_line` и
|
||
`config_schema_version`.
|
||
|
||
Посмотреть, что видит оркестратор:
|
||
|
||
```bash
|
||
hy2xs-orchestrator status --package-dir /usr/local/lib/hy2xs/package \
|
||
| grep -o '"install_state_generation":"[^"]*"'
|
||
```
|
||
|
||
`"current"` — маркер текущего поколения; `"foreign"` — требуется чистая
|
||
переустановка; `"absent"` — установки нет.
|
||
|
||
### Незавершённая установка текущего поколения
|
||
|
||
Если установка упала **после** начала применения изменений, полная очистка не
|
||
нужна:
|
||
|
||
```bash
|
||
hy2xs-orchestrator repair \
|
||
--package-dir /usr/local/lib/hy2xs/package \
|
||
--config /etc/hy2xs/hy2xs.env \
|
||
--allow-partial-state
|
||
```
|
||
|
||
Флаг обязателен и осознан: без него `repair` работает только поверх полностью
|
||
успешной установки.
|
||
|
||
### Сервер установился, но 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-команда туннеля:
|
||
|
||
```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
|
||
```
|
||
|
||
После запуска открыть `http://127.0.0.1:8080/#/login`.
|
||
|
||
Если SSH-туннель не поднимается (`administratively prohibited`), проверить effective SSH policy:
|
||
|
||
```bash
|
||
sshd -T | grep -E '^(port|allowtcpforwarding|permitopen|gatewayports|passwordauthentication|permitrootlogin) '
|
||
```
|
||
|
||
Рекомендуемый фрагмент hardening `sshd_config`:
|
||
|
||
```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
|
||
```
|
||
|
||
### 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 ведёт не на этот сервер
|
||
|
||
Сообщение выглядит так:
|
||
|
||
```text
|
||
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` проверяются полностью.
|
||
|
||
Вариант `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 не задан»
|
||
|
||
Означает, что учётной записи администратора в базе нет, а переменной, из которой
|
||
её положено создать, — тоже.
|
||
|
||
Придумывать пароль самостоятельно админка не будет: такой пароль не знал бы
|
||
никто, кроме журнала, а раньше именно он туда и попадал открытым текстом.
|
||
Сообщение говорит о повреждённом контракте запуска.
|
||
|
||
Что проверять:
|
||
|
||
```bash
|
||
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, поэтому придуманный секрет разошёлся бы с файлом и первая же
|
||
проверка подключения провалилась бы.
|
||
|
||
### Забыт пароль администратора
|
||
|
||
```bash
|
||
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`) — те же, с которыми работает юнит.
|
||
|
||
Команда печатает новые логин и пароль в консоль и требует смены пароля при
|
||
первом входе. Работает поверх существующей установки; на машине без базы она
|
||
осмысленно откажет — это инструмент восстановления, а не установки.
|
||
|
||
### Автоматический сброс трафика не срабатывает
|
||
|
||
Проверьте сохранённое расписание:
|
||
|
||
```bash
|
||
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).
|