fix(admin): закрыть обещания панели, которые продукт не выполнял
Девятый проход, по итогам приёмки v1.0.0-rc1 на живом Debian 13. Общая тема:
интерфейс обещал оператору то, что продукт умел, но до чего не доходило
управление.
Секрет пира. Подпись под полем предлагала оставить его пустым, сервер умел его
сгенерировать, и генерация была недостижима: в go-playground/validator тег
omitempty НЕ пропускает правило, если поле объявлено указателем и указатель не
nil — hasValue считает указатель на пустую строку «значением». Правило min=6
применялось к пустой строке и отказывало. Ловушка закрыта общим шагом
нормализации DTO, а не тегом на одном поле: та же ловушка ломала фильтр списка
пиров, где очищенный крестиком el-input отправляет `?name=`. Граница проходит по
каждому полю отдельно — у remark пустая строка означает «убрать пометку», у
disabled ноль означает «включён».
Отказы. Любая ошибка любого поля превращалась в слово `invalid`, а слой vo
определял код ответа СРАВНЕНИЕМ текста сообщения — тот же антипаттерн, который
запрещён панели, только на сервере. Ответ несёт errors[{code, field, message,
params}]; панель выбирает фразу по коду и подставляет причины под поля.
Сессия. Ветка «войдите заново» была недостижима дважды: сервер отвечает HTTP 200
на любой отказ, поэтому обработчик ошибок axios не вызывался, а условие в нём
проверяло code === "A0230" и поле msg, которых в этом API никогда не было.
Истёкший токен вдобавок уезжал с кодом системной ошибки.
Иконки. Контракт currentColor был объявлен в двух местах и не действовал: восемь
ассетов несли литеральный fill="#000000" на <path>, а атрибут представления
перебивает унаследованное CSS-свойство. Под это попадали все семь иконок
бокового меню на фоне #181818.
Имя пира. Два правила на одном поле противоречили друг другу (min=1 против
6-32), а копия набора символов в слое контроллеров несла неэкранированный дефис
и впускала `, - . / : ; <` — через панель проходило имя peer/name, которое
импорт того же пира отклонял. Набор символов ЛОГИНА сознательно не сужен и
закреплён тестом: он приходит из HY2XS_ADMIN_USER и оркестратором не
ограничивается.
Добавлены подпись «Разработано во Flamy» с адресом, принадлежащим приложению, и
контрактные тесты панели как обязательный шаг сборки. Их исполняет Bun, а не
vitest: jsdom не вычисляет currentColor и визуальной корректности не доказал бы,
зато vitest привёл бы в граф pnpm audit сотню транзитивных зависимостей.
docs/ разложена по слоям, 11-testing-and-acceptance.md (117 КБ) разбит на пять
частей, добавлен docs/acceptance/ с отчётом о прогоне rc1 и перечнем дефектов.
Обход документации в приёмке стал рекурсивным: плоский docs/*.md после
разнесения по каталогам совпадал бы ровно с одним файлом.
This commit is contained in:
@@ -0,0 +1,518 @@
|
||||
# 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` работает только поверх полностью
|
||||
успешной установки.
|
||||
|
||||
### Операция отказывает: уже выполняется другая
|
||||
|
||||
```text
|
||||
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` — общие, и две одновременные операции
|
||||
записывают их поверх друг друга, после чего откат одной «восстанавливает»
|
||||
состояние поверх изменений другой.
|
||||
|
||||
Отказ происходит **до** первой мутации, поэтому сервер не тронут. Что делать:
|
||||
|
||||
```bash
|
||||
# кто держит замок
|
||||
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, а мёртвого держателя следующая операция
|
||||
обнаруживает и переиспользует замок самостоятельно.
|
||||
|
||||
Удалять файл руками нужно ровно в одном случае — если оркестратор сообщил, что
|
||||
содержимое замка не является корректной записью:
|
||||
|
||||
```text
|
||||
operation lock … exists but is not a valid HY2XS lock record
|
||||
```
|
||||
|
||||
Такой замок сознательно не снимается автоматически: непонятый файл не
|
||||
доказывает, что операции нет.
|
||||
|
||||
`status` и `diagnostics collect` замок не берут и работают во время операции.
|
||||
В отчёте `status` при этом появляется поле `operation_in_progress` — читайте
|
||||
состояние как снимок незавершённой транзакции, а не как итог.
|
||||
|
||||
### Операция отказывает: guard предыдущей операции ещё вооружён
|
||||
|
||||
```text
|
||||
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 секунд.
|
||||
|
||||
```bash
|
||||
# сколько ещё ждать и что именно висит
|
||||
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'а не удалось выяснить
|
||||
|
||||
```text
|
||||
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 мог быть жив, а взведённый
|
||||
таймер — существовать. Разрешить операцию в этой ситуации означало бы допустить
|
||||
срабатывание старого таймера поверх новой операции.
|
||||
|
||||
```bash
|
||||
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`
|
||||
|
||||
```text
|
||||
automatic firewall rollback has already fired
|
||||
phase: firewall_guard_fired
|
||||
```
|
||||
|
||||
Это означает: автоматический откат firewall сработал раньше, чем операция успела
|
||||
снять guard. Сервер жив и доступен, но работает на **прежнем** firewall, а не на
|
||||
том, который сгенерировала операция. Именно поэтому фиксация успеха запрещена,
|
||||
даже если smoke успел сойтись, — иначе сервер считался бы настроенным с чужими
|
||||
правилами, что особенно дорого при смене порта Hysteria, SSH или ACME.
|
||||
|
||||
Окно guard — 45 секунд (плюс точность таймера, `AccuracySec=1s`), и оно не
|
||||
обязано покрывать smoke. Причину ищите в том, почему проход в него не уложился:
|
||||
|
||||
```bash
|
||||
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-команда туннеля:
|
||||
|
||||
```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` проверяются полностью.
|
||||
|
||||
### Где проходит граница 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 не задан»
|
||||
|
||||
Означает, что учётной записи администратора в базе нет, а переменной, из которой
|
||||
её положено создать, — тоже.
|
||||
|
||||
Придумывать пароль самостоятельно админка не будет: такой пароль не знал бы
|
||||
никто, кроме журнала, а раньше именно он туда и попадал открытым текстом.
|
||||
Сообщение говорит о повреждённом контракте запуска.
|
||||
|
||||
Что проверять:
|
||||
|
||||
```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).
|
||||
@@ -0,0 +1,333 @@
|
||||
# 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/<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.
|
||||
|
||||
## 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`.
|
||||
|
||||
Та же редакция применяется к diagnostics-бандлу
|
||||
(`hy2xs-orchestrator diagnostics collect`), который собирается автоматически при
|
||||
неудачной установке или реконфигурации. Бандл предназначен для передачи наружу,
|
||||
поэтому попадающие в него `hy2xs.env`, `post-install.env` и `config.yaml`
|
||||
редактируются перед упаковкой.
|
||||
|
||||
При отказе **до** начала применения изменений (`fatal_pre_apply`) бандл не
|
||||
собирается: его сбор сам создал бы каталоги в `/var/log/hy2xs` на сервере,
|
||||
который мы обещали не трогать.
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
# Очистка сервера от предыдущей установки
|
||||
|
||||
## Зачем этот документ
|
||||
|
||||
HY2XS v1 **не поддерживает установку поверх** и **не мигрирует состояние 0.x**.
|
||||
Это осознанное решение продукта, а не временное ограничение: попытка угадать,
|
||||
как устроен произвольный старый сервер, приводит к полурабочим установкам,
|
||||
которые невозможно диагностировать.
|
||||
|
||||
Отсюда следует жёсткий системный инвариант:
|
||||
|
||||
```text
|
||||
обнаружена старая установка
|
||||
↓
|
||||
НОЛЬ изменений на сервере
|
||||
↓
|
||||
понятный отказ
|
||||
↓
|
||||
явная очистка (этот документ)
|
||||
↓
|
||||
установка HY2XS v1 с нуля
|
||||
```
|
||||
|
||||
Установщик **никогда** не выполняет очистку самостоятельно. Удаление чужого
|
||||
состояния — операция оператора, а не побочный эффект запуска `install.sh`.
|
||||
|
||||
## Как выглядит отказ
|
||||
|
||||
Установщик проверяет чистоту хоста в **PHASE 0** — до того, как изменит хотя бы
|
||||
один persistent path, включая `/usr/local/lib/hy2xs`:
|
||||
|
||||
```text
|
||||
[hy2xs-install] PHASE 0: read-only checks (no persistent path is modified)
|
||||
[hy2xs-install] verifying package checksums
|
||||
[hy2xs-install] running clean-host preflight from the unpacked package
|
||||
[hy2xs] ERROR: На сервере обнаружена предыдущая или посторонняя установка.
|
||||
HY2XS v1 не поддерживает установку поверх и не мигрирует состояние 0.x.
|
||||
Ни один файл на сервере не изменён.
|
||||
|
||||
Найденные маркеры:
|
||||
- /etc/hysteria/post-install.env (post-install.env предыдущей установки HY2XS)
|
||||
- hy2xs-admin.service (systemd-юнит админки HY2XS)
|
||||
|
||||
Очистите сервер и установите HY2XS заново: см. docs/operations/14-legacy-cleanup.md
|
||||
```
|
||||
|
||||
Если вы видите этот текст — сервер в том же состоянии, в котором был до запуска.
|
||||
|
||||
Отдельный случай — конфигурация без маркера схемы:
|
||||
|
||||
```text
|
||||
HY2XS_CONFIG_SCHEMA_VERSION отсутствует в конфигурации.
|
||||
Похоже на конфигурацию предыдущего поколения (0.x) или на неизвестный формат.
|
||||
```
|
||||
|
||||
До v1 поля `HY2XS_CONFIG_SCHEMA_VERSION` не существовало, поэтому его отсутствие
|
||||
трактуется как legacy, а не как «текущая схема по умолчанию».
|
||||
|
||||
## Что именно проверяется
|
||||
|
||||
Контракт чистого хоста объявлен в `orchestrator/src/steps/cleanHost.ts` и покрыт
|
||||
тестами. Установка отказывается, если найден хотя бы один из объектов:
|
||||
|
||||
| Объект | Что это |
|
||||
| --- | --- |
|
||||
| `/etc/hysteria/post-install.env` | post-install.env предыдущей установки |
|
||||
| `/etc/hy2xs/hy2xs.env` | runtime-конфигурация предыдущей установки |
|
||||
| `/etc/hy2xs/bootstrap-admin.secret` | bootstrap-секрет администратора |
|
||||
| `/var/lib/hy2xs/install-state.json` | маркер состояния установки |
|
||||
| `/usr/local/lib/hy2xs/package` | runtime-пакет предыдущей установки |
|
||||
| `/etc/hysteria/config.yaml` | сгенерированный серверный конфиг |
|
||||
| `/usr/local/bin/hysteria` | уже установленный бинарник Hysteria |
|
||||
| `/etc/nftables.d/hy2xs.nft` | nftables-фрагмент HY2XS |
|
||||
| `hy2xs-admin.service` | systemd-юнит админки |
|
||||
| `hysteria-server.service` | systemd-юнит сервера Hysteria |
|
||||
| `h-ui.service`, `/usr/local/h-ui` | наследие панели поколения 0.x |
|
||||
| `HY2XS_INSTALL_DIR` (по умолчанию `/opt/hy2xs-admin`) | каталог приложения |
|
||||
| `HY2XS_DATA_DIR` (по умолчанию `/var/lib/hy2xs-admin`) | каталог данных и БД |
|
||||
|
||||
Последние два пути берутся из конфигурации, а не захардкожены: нестандартная
|
||||
установка тоже должна быть обнаружена.
|
||||
|
||||
## Перед очисткой
|
||||
|
||||
Очистка **разрушительная**. Она удаляет базу админки вместе с учётными записями
|
||||
пиров: выданные пользователям ссылки перестанут работать.
|
||||
|
||||
Сохраните то, что вам нужно:
|
||||
|
||||
```bash
|
||||
# ссылки и учётные записи пиров (если старая панель ещё работает)
|
||||
sudo sqlite3 /var/lib/hy2xs-admin/h_ui.db '.dump' > ~/hy2xs-peers-dump.sql
|
||||
|
||||
# серверный конфиг Hysteria
|
||||
sudo cp -a /etc/hysteria/config.yaml ~/hysteria-config.yaml.bak
|
||||
|
||||
# post-install-справка предыдущей установки
|
||||
sudo cp -a /etc/hysteria/post-install.env ~/post-install.env.bak
|
||||
```
|
||||
|
||||
Файлы содержат секреты. Снимите с них лишние права и не пересылайте как есть:
|
||||
|
||||
```bash
|
||||
chmod 600 ~/hy2xs-peers-dump.sql ~/hysteria-config.yaml.bak ~/post-install.env.bak
|
||||
```
|
||||
|
||||
Для безопасной передачи конфига наружу используйте редактирование секретов:
|
||||
|
||||
```bash
|
||||
hy2xs-orchestrator redact-config --config ~/hysteria-config.yaml.bak --out ~/hysteria-config.redacted.yaml
|
||||
```
|
||||
|
||||
## Очистка скриптом
|
||||
|
||||
Скрипт `tools/legacy/purge-v0.sh` лежит в репозитории. Скопируйте его на сервер.
|
||||
|
||||
Сначала — план. Без флагов скрипт **ничего не меняет**:
|
||||
|
||||
```bash
|
||||
sudo ./purge-v0.sh
|
||||
```
|
||||
|
||||
Он покажет, какие службы будут остановлены, какие пути удалены и какие из них
|
||||
существуют прямо сейчас.
|
||||
|
||||
Затем — выполнение. Требуются оба флага, `--apply` без подтверждения не работает:
|
||||
|
||||
```bash
|
||||
sudo ./purge-v0.sh --apply --yes-i-know
|
||||
```
|
||||
|
||||
Возможности «оставить бинарник Hysteria» у скрипта нет намеренно.
|
||||
`/usr/local/bin/hysteria` входит в clean-host контракт установщика: сервер, где
|
||||
он остался, установку HY2XS v1 не пройдёт. Скрипт, который сохранял бы бинарник
|
||||
и при этом сообщал «хост чист», прямо противоречил бы следующему запуску
|
||||
`install.sh`. Свежая установка всё равно кладёт собственную версию Hysteria,
|
||||
проверенную по SHA-256 против upstream `hashes.txt`.
|
||||
|
||||
В конце скрипт сам проверяет, что хост стал чистым по тому же контракту, который
|
||||
применяет установщик. Если что-то осталось, он назовёт конкретные объекты и
|
||||
завершится с ошибкой.
|
||||
|
||||
## Что скрипт делает и чего не делает
|
||||
|
||||
Делает:
|
||||
|
||||
1. останавливает и выключает `hysteria-server`, `hy2xs-admin`, `h-ui`;
|
||||
2. снимает таймеры отката firewall `hy2xs-fw-rollback-*` — они переживают
|
||||
неудачную установку и иначе продолжили бы менять ruleset уже после очистки;
|
||||
3. удаляет unit-файлы и выполняет `daemon-reload`;
|
||||
4. удаляет каталоги приложения, конфигурации, данных и логов, включая
|
||||
`/var/lib/hysteria` (там остаётся ACME-состояние и сертификаты Hysteria),
|
||||
`/usr/local/lib/hy2xs` и symlink `/usr/local/bin/hy2xs-orchestrator`;
|
||||
5. удаляет `/usr/local/bin/hysteria`;
|
||||
6. удаляет артефакты незавершённой операции в `/run`: каталог отката
|
||||
`/run/hy2xs` (прежние `nftables.conf`, `hy2xs.nft` и маркер срабатывания
|
||||
guard) и замок операций `/run/lock/hy2xs-orchestrator.lock`, который может
|
||||
пережить убитый `kill -9` процесс оркестратора и не дать запуститься
|
||||
следующей установке. `/run` — tmpfs, и перезагрузка убрала бы оба, но
|
||||
очистка не имеет права требовать перезагрузки;
|
||||
7. удаляет `*.candidate` firewall — они остаются, если операция упала между
|
||||
`nft -c` и подстановкой файла в production-путь;
|
||||
8. удаляет фрагмент `/etc/nftables.d/hy2xs.nft` и строку `include` для него из
|
||||
`/etc/nftables.conf`, после чего перезагружает ruleset;
|
||||
9. проверяет чистоту хоста.
|
||||
|
||||
Список удаляемых путей и список legacy-маркеров clean-host контракта описывают
|
||||
одну и ту же границу: расхождение между ними ловится приёмкой сборки. Иначе
|
||||
возможен сервер, с которого «всё удалено», но который установщик всё равно
|
||||
считает грязным — или, что хуже, наоборот.
|
||||
|
||||
Не делает:
|
||||
|
||||
- не трогает `sshd` и его конфигурацию;
|
||||
- не удаляет `/etc/nftables.conf` целиком — остальной ruleset принадлежит
|
||||
оператору;
|
||||
- не удаляет системные пакеты, установленные ранее;
|
||||
- не запускается автоматически из установщика.
|
||||
|
||||
## Ручная очистка
|
||||
|
||||
Если запускать скрипт нежелательно, те же шаги вручную:
|
||||
|
||||
```bash
|
||||
sudo systemctl stop hysteria-server hy2xs-admin h-ui
|
||||
sudo systemctl disable hysteria-server hy2xs-admin h-ui
|
||||
sudo systemctl reset-failed hysteria-server hy2xs-admin h-ui
|
||||
|
||||
# таймеры отката firewall от незавершённой установки.
|
||||
# --plain обязателен: у юнита в состоянии failed первой колонкой идёт маркер `●`,
|
||||
# и без флага такой юнит легко пропустить — а это ровно те, что остались после
|
||||
# аварийной установки.
|
||||
sudo systemctl list-units --all --plain 'hy2xs-fw-rollback-*'
|
||||
# для каждого найденного юнита:
|
||||
# sudo systemctl stop <unit> && sudo systemctl disable <unit>
|
||||
# sudo systemctl reset-failed <unit>
|
||||
# sudo rm -f /etc/systemd/system/<unit>
|
||||
|
||||
sudo rm -f /etc/systemd/system/hysteria-server.service \
|
||||
/etc/systemd/system/hy2xs-admin.service \
|
||||
/etc/systemd/system/h-ui.service
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
sudo rm -rf /etc/hy2xs /etc/hysteria /var/lib/hy2xs /var/lib/hy2xs-admin \
|
||||
/var/lib/hysteria /var/log/hy2xs /opt/hy2xs-admin \
|
||||
/usr/local/lib/hy2xs /usr/local/h-ui
|
||||
sudo rm -f /usr/local/bin/hysteria /usr/local/bin/hy2xs-orchestrator
|
||||
|
||||
# артефакты незавершённой операции в /run (tmpfs)
|
||||
sudo rm -rf /run/hy2xs
|
||||
sudo rm -f /run/lock/hy2xs-orchestrator.lock
|
||||
|
||||
# candidate-файлы firewall от операции, упавшей до подстановки
|
||||
sudo rm -f /etc/nftables.conf.candidate /etc/nftables.d/hy2xs.nft.candidate
|
||||
|
||||
sudo rm -f /etc/nftables.d/hy2xs.nft
|
||||
sudo sed -i '/nftables.d\/hy2xs.nft/d' /etc/nftables.conf
|
||||
sudo nft -c -f /etc/nftables.conf && sudo nft -f /etc/nftables.conf
|
||||
```
|
||||
|
||||
## После очистки
|
||||
|
||||
Устанавливайте HY2XS v1 обычным путём. PHASE 0 установщика повторит проверку
|
||||
чистоты хоста и подтвердит, что всё в порядке:
|
||||
|
||||
```bash
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
## Незавершённая установка v1 — это другой случай
|
||||
|
||||
Если установка HY2XS v1 упала **после** начала применения изменений, полная
|
||||
очистка не нужна. У такой машины есть корректный маркер состояния текущего
|
||||
поколения, и её чинит `repair`:
|
||||
|
||||
```bash
|
||||
sudo hy2xs-orchestrator repair \
|
||||
--package-dir /usr/local/lib/hy2xs/package \
|
||||
--config /etc/hy2xs/hy2xs.env \
|
||||
--allow-partial-state
|
||||
```
|
||||
|
||||
Флаг `--allow-partial-state` обязателен и осознан: без него `repair` работает
|
||||
только поверх полностью успешной установки. При этом `repair` всё равно
|
||||
проверяет, что маркер принадлежит текущему поколению (`product`,
|
||||
`release_line`, `config_schema_version`), и откажется чинить чужое состояние.
|
||||
|
||||
Отказ вида «install state marker … не относится к текущему поколению HY2XS»
|
||||
означает, что `repair` неприменим и нужна очистка по этому документу.
|
||||
Reference in New Issue
Block a user