firewall guard: барьер покоя fail-closed и явный контракт транзиентного таймера
Барьер, обязанный ДОКАЗАТЬ отсутствие асинхронного исполнителя, в трёх местах принимал за доказательство отсутствие наблюдения. - отказ `systemctl` больше не выдаётся за отсутствие guard: вместо `return []` введён единый наблюдатель inspectRollbackGuard с исходами quiescent/pending/ unknown и отдельным типом отказа GuardStateUnknownError; - покой перечисляется белым списком (inactive, failed): maintenance, refreshing и любое незнакомое состояние systemd блокируют операцию; - у транзиентного таймера явно заданы AccuracySec=1s (умолчание 1min превращало обещанные 45 секунд в 45-105) и RemainAfterElapse=no; барьер дополнительно опознаёт SubState=elapsed у *.timer как покой; - команда взведения строится чистой buildArmGuardArgv и выполняется новым runMutatingArgv без shell, поэтому её контракт проверяется значением, а не грепом по исходнику; - status перестал листить guard-юниты своей копией кода: без --plain, с `|| true` и с трактовкой failed как «вооружён» отчёт вечно противоречил барьеру. Добавлены rollback_guard_state и firewall_state=guard_unknown; - purge-v0.sh пропускал failed-юниты из-за маркера в первой колонке. Барьер покрыт поведенческими тестами через подставляемый SystemdUnitProbe: прежние проверки грепом по тексту функции пережили инверсию смысла - строка `return [];` была на месте, а решение стало неверным. Документация (README, docs/07, 11, 12, 13, 14, CHANGELOG) приведена к реальному окну 45-46 секунд и к новому тексту отказа. Отдельно исправлен комментарий PNPM_AUDIT_LEVEL в versions.env: гейт давно проверяет весь lock-граф.
This commit is contained in:
@@ -90,6 +90,14 @@ IPv4-only policy:
|
||||
3. Подставить candidate в production-пути.
|
||||
4. Взвести rollback guard: транзиентный юнит `hy2xs-fw-rollback-<op-id>`
|
||||
с окном 45 секунд.
|
||||
```bash
|
||||
systemd-run \
|
||||
--unit=hy2xs-fw-rollback-<op-id>.service \
|
||||
--on-active=45s \
|
||||
--timer-property=RemainAfterElapse=no \
|
||||
--timer-property=AccuracySec=1s \
|
||||
/bin/sh /run/hy2xs/rollback/<op-id>/auto-rollback.sh
|
||||
```
|
||||
5. Применить ruleset и проверить SSH/Hysteria/UI.
|
||||
6. Снять guard и **доказать**, что он снят (см. ниже).
|
||||
7. Долговечно зафиксировать успех.
|
||||
@@ -109,6 +117,29 @@ Guard — это защита от потери доступа к серверу
|
||||
smoke: smoke на медленном, но исправном сервере может идти заметно дольше.
|
||||
Увеличение окна лечило бы гонку расширением, а не устранением.
|
||||
|
||||
#### Почему у таймера заданы `AccuracySec` и `RemainAfterElapse`
|
||||
|
||||
`OnActiveSec=45s` сам по себе **не** означает «ровно через 45 секунд».
|
||||
`systemd.timer` разрешает себе сработать в окне
|
||||
|
||||
```text
|
||||
цель ... цель + AccuracySec
|
||||
```
|
||||
|
||||
объединяя пробуждения ради экономии энергии, и умолчание `AccuracySec=` —
|
||||
`1min`. То есть без явного значения guard, про который эта страница и текст
|
||||
отказа говорят «45 секунд», по контракту systemd мог сработать и через 105.
|
||||
Поэтому точность задаётся явно: `AccuracySec=1s`, и реальное окно — **45–46
|
||||
секунд**. Коалесценция пробуждений аварийному guard'у не нужна: он взводится
|
||||
один раз за операцию и почти всегда снимается, не сработав.
|
||||
|
||||
`RemainAfterElapse=no` задаётся по другой причине. Отработавший одноразовый
|
||||
таймер обязан выгрузиться — на этом стоит право барьера покоя считать
|
||||
отсутствие юнита доказательством того, что откатывать firewall больше некому.
|
||||
`systemd-run` выставляет это свойство транзиентным таймерам сам, но инвариант,
|
||||
который держится на чужом умолчании, нигде не записан и ничем не проверяется;
|
||||
в явном виде он попадает и в journal, и в тест.
|
||||
|
||||
Вместо этого guard оставляет за собой факт:
|
||||
|
||||
```text
|
||||
@@ -158,18 +189,81 @@ Guard — объект systemd, а не часть процесса оркест
|
||||
|
||||
Поэтому условие начала новой операции — не «PID предыдущей мёртв», а «у
|
||||
предыдущей не осталось исполнителей, способных изменить систему». Каждый захват
|
||||
замка проходит через барьер покоя: если хоть один `hy2xs-fw-rollback-*` находится
|
||||
в состоянии `active`, `activating`, `deactivating` или `reloading`, операция
|
||||
отказывает.
|
||||
замка проходит через барьер покоя.
|
||||
|
||||
`inactive` и `failed` считаются покоем: отработавший guard больше ничего не
|
||||
сделает, а отказ по `failed` заблокировал бы `repair` — ровно тот инструмент,
|
||||
которым чинят последствия.
|
||||
#### Покой перечисляется белым списком
|
||||
|
||||
Покой — это `inactive` и `failed`, и **только** они. Отработавший guard больше
|
||||
ничего не сделает, а отказ по `failed` заблокировал бы `repair` — ровно тот
|
||||
инструмент, которым чинят последствия. Всё остальное считается непокоем и
|
||||
запрещает операцию.
|
||||
|
||||
Список именно белый, а не чёрный. Перечисление непокойных состояний
|
||||
(`active`, `activating`, `deactivating`, `reloading`) объявляло бы безопасным
|
||||
любое состояние, которого автор не назвал, — включая те, которых он не знал:
|
||||
systemd 257 знает ещё `maintenance` и `refreshing`, и список может пополниться
|
||||
снова. Незнакомое состояние systemd обязано блокировать операцию, а не
|
||||
проходить молча.
|
||||
|
||||
Единственное исключение — `*.timer` в `SubState=elapsed` или `dead`. `ActiveState`
|
||||
таймера отвечает на вопрос «юнит загружен и в строю», а не «он ещё может
|
||||
сработать»: в systemd `TIMER_ELAPSED` отображается в `UNIT_ACTIVE` так же, как
|
||||
`TIMER_WAITING`, и различает их только `SubState`. У наших guard'ов такого не
|
||||
бывает (`RemainAfterElapse=no`), но инвариант «барьер не залипает» не должен
|
||||
зависеть от того, чем именно создан таймер: иначе отработавший таймер запрещал
|
||||
бы install/reconfigure/repair/doctor навсегда — и запрещал бы ради отката,
|
||||
который уже произошёл. Триггернутый сервис при этом виден барьеру отдельным
|
||||
юнитом и остаётся непокоем, пока выполняется.
|
||||
|
||||
#### Отказ запроса к systemd — это отказ операции
|
||||
|
||||
Если `systemctl` не ответил, барьер **не** считает систему спокойной:
|
||||
|
||||
```text
|
||||
unable to verify firewall rollback guard state;
|
||||
systemd query failed, refusing to start a lifecycle operation
|
||||
```
|
||||
|
||||
Отсутствие ответа — отсутствие наблюдения, а не наблюдение покоя. Обратная
|
||||
трактовка давала реальный сценарий потери firewall:
|
||||
|
||||
```text
|
||||
systemd жив, старый rollback timer взведён
|
||||
-> запрос к systemctl/D-Bus временно отказывает
|
||||
-> список guard'ов пуст
|
||||
-> барьер считает систему спокойной
|
||||
-> новая операция начинает менять firewall
|
||||
-> старый таймер срабатывает поверх неё
|
||||
```
|
||||
|
||||
Механизм, обязанный **доказать** отсутствие асинхронного исполнителя, принимал
|
||||
невозможность получить доказательство за положительный результат. Это прямо
|
||||
противоположно политике замка операций, где сомнение трактуется в пользу
|
||||
отказа.
|
||||
|
||||
Практического выигрыша у прежнего поведения не было: `systemd-run` требуется в
|
||||
preflight, поэтому без работающего systemd операция всё равно откажет — просто
|
||||
позже и с менее внятной диагностикой.
|
||||
|
||||
Состояния `hysteria-server`, `hy2xs-admin` и `nftables.service` барьер
|
||||
сознательно не проверяет: незавершённый `systemctl restart` ничего не
|
||||
откатывает, он лишь повторяет то, что новая операция сделает сама.
|
||||
|
||||
#### Один наблюдатель на барьер и на отчёт
|
||||
|
||||
Состояние guard'ов читает одна функция, и `hy2xs-orchestrator status` берёт его
|
||||
у неё же. Раньше у status была своя копия листинга, и она расходилась с
|
||||
барьером по трём пунктам сразу: без `--plain` (у `failed`-юнита первой колонкой
|
||||
идёт маркер `●`), с `|| true` (отказ systemd превращался в «guard'ов нет») и
|
||||
без разбора состояний — вооружённым считался любой найденный юнит. На практике
|
||||
это означало, что аварийно сработавший guard оставлял `failed`-сервис
|
||||
загруженным до `reset-failed`, status вечно показывал `firewall_state:
|
||||
guard_active`, а барьер тот же самый юнит считал покоем и разрешал `repair`.
|
||||
|
||||
`status` при этом остаётся отчётом: он не берёт замок и существует в том числе
|
||||
для сломанного хоста, поэтому «спросить не удалось» попадает в JSON значением
|
||||
`rollback_guard_state: "unknown"`, а не отказом команды.
|
||||
|
||||
### Проверка эффективного firewall
|
||||
|
||||
`nft -c -f /etc/nftables.conf` разбирает текущий файл, каким бы он ни был, и
|
||||
|
||||
@@ -288,7 +288,33 @@ HYSTERIA_BIN=/usr/local/bin/hysteria ./tools/test/e2e-hysteria.sh
|
||||
стадиями, идущими **до** применения ruleset;
|
||||
- candidate-файлы убираются после успеха и best-effort при откате;
|
||||
- ключ операции считается одной функцией: install писал в маркер сырой
|
||||
ISO-timestamp, и путь `/run/hy2xs/rollback/<op_id>` из runbook не существовал.
|
||||
ISO-timestamp, и путь `/run/hy2xs/rollback/<op_id>` из runbook не существовал;
|
||||
- команда взведения guard проверяется как **значение**, а не грепом по
|
||||
исходнику: `buildArmGuardArgv` возвращает готовый argv, и тест сверяет его
|
||||
целиком — имя юнита с явным суффиксом, `--on-active=45s`,
|
||||
`--timer-property=AccuracySec=1s`, `--timer-property=RemainAfterElapse=no`.
|
||||
Небезопасный ключ операции отвергается до запуска: shell в этой команде не
|
||||
участвует, поэтому единственная защита — отказ;
|
||||
- барьер покоя проверяется **поведенчески**, с подставляемым наблюдателем
|
||||
systemd (`SystemdUnitProbe`), без systemd и без Linux:
|
||||
- взведённый таймер (`active/waiting`) и идущий прямо сейчас откат
|
||||
(`activating`) запрещают операцию с `PendingRecoveryError`;
|
||||
- отказ `list-units` **и** отказ `show` на любом отдельном юните дают
|
||||
`GuardStateUnknownError`: отсутствие ответа systemd — отсутствие
|
||||
наблюдения, а не наблюдение покоя. Прежний тест закреплял обратное
|
||||
(`return [];` в тексте функции) и потому пережил инверсию смысла: строка
|
||||
была на месте, а решение стало неверным;
|
||||
- оба отказа имеют общего предка `OperationBarrierError`;
|
||||
- покой — ровно `inactive` и `failed`; `maintenance`, `refreshing` и любое
|
||||
незнакомое состояние блокируют операцию, потому что политика перечисляет
|
||||
безопасные состояния, а не опасные;
|
||||
- `*.timer` в `SubState=elapsed` считается покоем: `TIMER_ELAPSED` в systemd
|
||||
отображается в `UNIT_ACTIVE`, и без этой ветки отработавший таймер
|
||||
блокировал бы `repair` навсегда. У сервиса тот же `SubState` ничего не
|
||||
значит;
|
||||
- разбор вывода `list-units` находит имя юнита и когда первой колонкой идёт
|
||||
маркер `●` (состояние `failed`) — иначе терялся бы именно аварийно
|
||||
сработавший guard.
|
||||
|
||||
## A5f. Взаимное исключение операций (unit)
|
||||
|
||||
@@ -1040,7 +1066,19 @@ firewall и успешным smoke.
|
||||
Сценарий:
|
||||
|
||||
1. установка доходит до шага `firewall`, в журнале появляется
|
||||
`firewall rollback guard armed`;
|
||||
`firewall rollback guard armed: … fires in 45s (timer accuracy 1s)`.
|
||||
Пока guard ждёт, свойства таймера проверяются напрямую — обещанное окно
|
||||
обязано быть контрактом systemd, а не намерением:
|
||||
|
||||
```bash
|
||||
systemctl show hy2xs-fw-rollback-<op-id>.timer \
|
||||
-p ActiveState -p SubState -p AccuracyUSec -p RemainAfterElapse
|
||||
```
|
||||
|
||||
Ожидается `ActiveState=active`, `SubState=waiting`, `AccuracyUSec=1s`,
|
||||
`RemainAfterElapse=no`. Без явной точности systemd вправе сработать в окне
|
||||
`[45s; 45s + AccuracySec]`, а умолчание `AccuracySec=` — одна минута, то
|
||||
есть реальное окно было бы 45–105 секунд;
|
||||
2. smoke искусственно замедляется дольше 45 секунд. Проще всего задержать один
|
||||
из сервисов — например, добавить в `hy2xs-admin.service` временный
|
||||
`ExecStartPre=/bin/sleep 60` и выполнить `systemctl daemon-reload` до запуска
|
||||
@@ -1142,18 +1180,56 @@ guard A срабатывает и возвращает firewall, который
|
||||
|
||||
```text
|
||||
previous HY2XS operation is no longer running, but its firewall rollback guard
|
||||
is still armed: hy2xs-fw-rollback-<op-id>.timer (active)
|
||||
is still armed: hy2xs-fw-rollback-<op-id>.timer (active/waiting)
|
||||
```
|
||||
|
||||
5. отказ происходит **до** снятия резервной копии и до первой мутации;
|
||||
6. `install.sh` в том же окне отказывает на PHASE 0 по той же причине;
|
||||
7. после срабатывания guard (`hy2xs-fw-rollback-*` больше не `active`)
|
||||
`repair` проходит.
|
||||
7. после срабатывания guard транзиентный таймер выгружается
|
||||
(`RemainAfterElapse=no`), и `repair` проходит. Проверяется наблюдением, а не
|
||||
ожиданием на глаз:
|
||||
|
||||
```bash
|
||||
systemctl show hy2xs-fw-rollback-<op-id>.timer -p LoadState -p ActiveState
|
||||
systemctl list-units --all --plain 'hy2xs-fw-rollback-*'
|
||||
```
|
||||
|
||||
Ожидается, что таймера в списке больше нет; оставшийся `.service` в
|
||||
состоянии `failed` (частичное восстановление) операцию не блокирует.
|
||||
|
||||
Обратная проверка: на сервере без вооружённого guard барьер молчит и ни одну
|
||||
операцию не задерживает, а `failed` от уже отработавшего guard **не** считается
|
||||
непокоем — иначе он заблокировал бы `repair`, которым и чинят последствия.
|
||||
|
||||
Отдельная проверка того же барьера — недоказуемое состояние. Барьер обязан
|
||||
различать «guard вооружён» и «спросить не удалось»: это разные утверждения, и
|
||||
оператору по ним нужны разные действия.
|
||||
|
||||
1. на рабочей установке без вооружённого guard делается недоступным запрос к
|
||||
systemd — проще всего временно подложить в `PATH` оркестратора `systemctl`,
|
||||
завершающийся ненулевым кодом;
|
||||
2. любая операция жизненного цикла (`repair`, `reconfigure --apply`, `doctor`,
|
||||
`install.sh` на PHASE 0) обязана отказать:
|
||||
|
||||
```text
|
||||
unable to verify firewall rollback guard state; systemd query failed,
|
||||
refusing to start a lifecycle operation
|
||||
```
|
||||
|
||||
3. отказ происходит **до** первой мутации, и тип ошибки —
|
||||
`GuardStateUnknownError`, а не `PendingRecoveryError`: ждать окна отката
|
||||
здесь бессмысленно;
|
||||
4. `hy2xs-orchestrator status` при этом **не** падает: он замок не берёт и
|
||||
существует в том числе для сломанного хоста, поэтому сообщает
|
||||
`rollback_guard_state: "unknown"` и `firewall_state: "guard_unknown"`;
|
||||
5. после возврата рабочего `systemctl` операция проходит без дополнительных
|
||||
действий.
|
||||
|
||||
Смысл проверки — в том, что прежнее поведение было противоположным: отказ
|
||||
запроса давал пустой список guard'ов, барьер считал систему спокойной и
|
||||
пропускал операцию, а взведённый таймер предыдущей операции срабатывал уже
|
||||
посреди неё.
|
||||
|
||||
## D1g. Успешная установка не оставляет следов транзакции
|
||||
|
||||
Проверяется на чистом хосте, обычной успешной установкой. Это обратная проверка
|
||||
@@ -1163,7 +1239,7 @@ is still armed: hy2xs-fw-rollback-<op-id>.timer (active)
|
||||
После `installed`:
|
||||
|
||||
```text
|
||||
systemctl list-units --all 'hy2xs-fw-rollback-*' → пусто
|
||||
systemctl list-units --all --plain 'hy2xs-fw-rollback-*' → пусто
|
||||
ls /run/hy2xs/rollback/ → пусто
|
||||
ls /run/lock/hy2xs-orchestrator.lock → отсутствует
|
||||
ls /etc/nftables.conf.candidate → отсутствует
|
||||
@@ -1366,3 +1442,8 @@ hy2xs-orchestrator doctor
|
||||
68. новая операция не начинается, пока у предыдущей остаётся вооружённый rollback guard: условие старта — «у предыдущей нет исполнителей, способных изменить систему», а не «её PID мёртв»
|
||||
69. отказ записи маркера `auto-rollback-fired` не может привести к фиксации успеха: он переводит юнит guard в `failed`, а `failed` фиксацию запрещает
|
||||
70. восстановление `UnitFileState` у `nftables.service` не обещает точности, которой не даёт: восстанавливаются `enabled`/`disabled`, остальные состояния называются оператору и не трогаются
|
||||
71. отказ запроса к systemd не выдаётся за покой: барьер обязан **доказать** отсутствие исполнителей предыдущей операции, а при невозможности получить доказательство отказывает с `GuardStateUnknownError`, а не разрешает операцию
|
||||
72. покой guard перечисляется белым списком (`inactive`, `failed`): незнакомое состояние systemd блокирует операцию, а не проходит молча по принципу «его нет в списке опасных»
|
||||
73. отработавший таймер не блокирует операцию навсегда: `RemainAfterElapse=no` выгружает его, а барьер дополнительно опознаёт `SubState=elapsed` у `*.timer` как покой
|
||||
74. обещанное окно отката — контракт systemd, а не намерение: у транзиентного таймера явно задан `AccuracySec=1s`, иначе умолчание `AccuracySec=1min` превращало «45 секунд» в 45–105
|
||||
75. состояние guard читает один наблюдатель: `status` берёт его у того же кода, что и барьер, и сообщает `unknown` вместо тихого «guard'ов нет» при отказе systemd
|
||||
|
||||
@@ -201,7 +201,7 @@ operation lock … exists but is not a valid HY2XS lock record
|
||||
|
||||
```text
|
||||
previous HY2XS operation is no longer running, but its firewall rollback guard
|
||||
is still armed: hy2xs-fw-rollback-<op-id>.timer (active)
|
||||
is still armed: hy2xs-fw-rollback-<op-id>.timer (active/waiting)
|
||||
```
|
||||
|
||||
Предыдущая операция умерла аварийно **после** применения firewall. Её процесса
|
||||
@@ -211,18 +211,50 @@ systemd, и он переживает свой процесс. Если нача
|
||||
операции.
|
||||
|
||||
Ничего делать не нужно, кроме как подождать: окно guard — 45 секунд с момента
|
||||
применения firewall.
|
||||
применения firewall плюс точность таймера (`AccuracySec=1s`), то есть не больше
|
||||
46 секунд.
|
||||
|
||||
```bash
|
||||
# сколько ещё ждать и что именно висит
|
||||
systemctl list-units --all 'hy2xs-fw-rollback-*'
|
||||
systemctl list-units --all --plain 'hy2xs-fw-rollback-*'
|
||||
hy2xs-orchestrator status --package-dir /usr/local/lib/hy2xs/package
|
||||
```
|
||||
|
||||
Когда guard сработает, юнит перестанет быть `active`, и операция пройдёт.
|
||||
Состояние `failed` у него покою не мешает: оно означает, что откат отработал
|
||||
не полностью, и это как раз повод запустить `repair`, а не ждать дальше —
|
||||
подробности в `journalctl -u 'hy2xs-fw-rollback-*'`.
|
||||
`--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`
|
||||
|
||||
@@ -237,8 +269,8 @@ phase: firewall_guard_fired
|
||||
даже если smoke успел сойтись, — иначе сервер считался бы настроенным с чужими
|
||||
правилами, что особенно дорого при смене порта Hysteria, SSH или ACME.
|
||||
|
||||
Окно guard — 45 секунд, и оно не обязано покрывать smoke. Причину ищите в том,
|
||||
почему проход в него не уложился:
|
||||
Окно guard — 45 секунд (плюс точность таймера, `AccuracySec=1s`), и оно не
|
||||
обязано покрывать smoke. Причину ищите в том, почему проход в него не уложился:
|
||||
|
||||
```bash
|
||||
journalctl -u 'hy2xs-fw-rollback-*' --no-pager
|
||||
|
||||
@@ -81,10 +81,11 @@ sudo -u hy2xs-admin test ! -r /etc/hy2xs/hy2xs.env
|
||||
| `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` | guard взведён; с этого момента операция обязана снять его до фиксации успеха |
|
||||
| `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 и не обязано его покрывать: доказательством служит не время, а маркер
|
||||
@@ -169,14 +170,26 @@ operation lock … is held by install (pid 1234), which is no longer running; re
|
||||
|
||||
```text
|
||||
previous HY2XS operation is no longer running, but its firewall rollback guard
|
||||
is still armed: hy2xs-fw-rollback-<op-id>.timer (active)
|
||||
is still armed: hy2xs-fw-rollback-<op-id>.timer (active/waiting)
|
||||
```
|
||||
|
||||
Условие старта — не «PID предыдущей мёртв», а «у предыдущей не осталось
|
||||
исполнителей, способных изменить систему». Ждать нужно не больше 45 секунд с
|
||||
исполнителей, способных изменить систему». Ждать нужно не больше 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, поэтому перезагрузка снимает замок в любом случае.
|
||||
Удалять файл руками нужно только если в нём оказалось непонятное содержимое:
|
||||
такой замок сознательно не переиспользуется автоматически — непонятый файл не
|
||||
|
||||
@@ -186,10 +186,14 @@ 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 от незавершённой установки
|
||||
sudo systemctl list-units --all 'hy2xs-fw-rollback-*'
|
||||
# таймеры отката 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 \
|
||||
|
||||
Reference in New Issue
Block a user