docs: описать транзакционный guard и взаимное исключение операций

- docs/07: полный порядок staged apply, инвариант снятия guard, объяснение
  почему окно 45 секунд не обязано покрывать smoke и почему guard не трогает
  nftables.service, семантическая проверка эффективного firewall;
- docs/11: разделы A5e/A5f для новых unit-тестов и серверные сценарии D1e
  (guard доходит до дедлайна), D1f (конкурентные операции), D1g (успешная
  операция не оставляет следов транзакции); матрица и acceptance criteria
  дополнены;
- docs/12: разбор отказов "уже выполняется другая операция" и
  firewall_guard_fired;
- docs/13: строки журнала guard в таблице recovery, новый раздел 8a про замок
  операций;
- docs/14 и purge-v0.sh: очистка /run/hy2xs, замка операций и candidate-файлов
  firewall — /run это tmpfs, но очистка не имеет права требовать перезагрузки;
- README: защита от потери доступа при смене firewall и раздел "Одна операция
  за раз";
- CHANGELOG: шестой проход.
This commit is contained in:
2026-08-31 01:31:15 +05:00
parent 50ec4d9717
commit 39139e95f7
8 changed files with 532 additions and 11 deletions
+66
View File
@@ -156,6 +156,72 @@ hy2xs-orchestrator repair \
Флаг обязателен и осознан: без него `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` — читайте
состояние как снимок незавершённой транзакции, а не как итог.
### Установка отказала с `firewall_guard_fired`
```text
automatic firewall rollback has already fired
phase: firewall_guard_fired
```
Это означает: автоматический откат firewall сработал раньше, чем операция успела
снять guard. Сервер жив и доступен, но работает на **прежнем** firewall, а не на
том, который сгенерировала операция. Именно поэтому фиксация успеха запрещена,
даже если smoke успел сойтись, — иначе сервер считался бы настроенным с чужими
правилами, что особенно дорого при смене порта Hysteria, SSH или ACME.
Окно guard — 45 секунд, и оно не обязано покрывать 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