fix(admin): дать отзыву доступа вторую попытку, а лимиту устройств — порядок снимков
Предыдущий проход сделал правильным порядок «сначала долговременная запись, потом разрыв сессии» и правильно запретил откат при неудаче разрыва. Способа прийти к согласованному состоянию ПОТОМ он не дал: у двух операций повтор не работал вовсе. Импорт, заменивший auth_id: после неудавшегося /kick старое значение не хранится нигде, повтор того же файла читает из базы уже новое и рвёт его, а cron пропускал незнакомый authID молча — dao.ListPeer просто не возвращала строку. Живая сессия оставалась навсегда. Снижение maxDevices: повтор формы даёт 1 < 1 -> false, разрыва больше нет. Лимит устройств в политику доступа не входит и входить не должен — это свойство сессий, — поэтому механизма схождения у него не было. enforcePeerAccess стал сверкой живых сессий: обход идёт по каждому authID из /online. Нет строки в базе -> kick; peerAccessDenied -> kick; непригодный maxDevices -> kick; устройств больше разрешённого -> kick. Отказ базы при этом не рвёт ничего. Ни таблицы отложенных операций, ни очереди retry: список живых сессий уже есть, и это /online. Отдельно закрыт второй TOCTOU лимита устройств. Учёт выданных разрешений закрыл сравнение двух одинаковых снимков, но сетевой запрос выполнялся вне блокировки, поэтому снимки приходили в резервацию в произвольном порядке и устаревший откатывал lastOnline назад, возвращая уже занятое место. Это не data race — память защищена мьютексом, и -race здесь молчит принципиально. Последовательность «прочитать /online -> занять место» выполняется под замком по authId; глобальный замок не годится, внутри идёт сетевой запрос. Учёт разрешений больше не растёт бесконечно: запись снималась только на ветке отказа, поэтому в карте копились удалённые пиры и переписанные импортом идентификаторы. Уборка идёт по фактической картине подключений. Гейты приёмки доращены под все три инварианта и проверены в обе стороны. Go 1.26.7 -> 1.26.8. Документация приведена в соответствие в двух местах, где описывала снятую архитектуру. Разбор: docs/acceptance/2026-09-02-v1.0.0-rc3-preflight-findings.md
This commit is contained in:
@@ -37,13 +37,15 @@
|
||||
17. `nft -c -f /etc/nftables.conf` проходит после apply
|
||||
18. пароль admin и `con_pass` не перезаписываются при рестарте `hy2xs-admin`
|
||||
19. остановка/рестарт UI не останавливает `hysteria-server`
|
||||
20. traffic accounting/kick ориентируются на systemd status; ключа `HYSTERIA2_ENABLE` в базе больше нет
|
||||
20. traffic accounting/kick обращаются к Traffic Stats API напрямую и **не** используют systemd status как гейт принятия решений; ключа `HYSTERIA2_ENABLE` в базе больше нет
|
||||
21. `/etc/hysteria/config.yaml` имеет `0640 hysteria:hy2xs-admin`
|
||||
22. `hy2xs-admin` может читать `/etc/hysteria/config.yaml`, но не может писать
|
||||
23. смена расписания сброса трафика применяется **без** перезапуска `hy2xs-admin`, и число джоб планировщика не растёт
|
||||
24. невалидное cron-выражение отклоняется API, а значение в базе не меняется
|
||||
25. `systemctl restart hy2xs-admin` завершает сервис штатно: планировщик остановлен до закрытия SQLite, в журнале нет `database is closed`
|
||||
26. `govulncheck ./...` на графе релиза не находит вызываемых уязвимостей
|
||||
27. живая сессия, которой в базе больше ничего не соответствует (пир удалён либо его `auth_id` заменён импортом, а разрыв в тот момент не удался), завершается очередным циклом учёта — не позднее 30 секунд
|
||||
28. превышение `maxDevices` живыми сессиями устраняется тем же циклом: после неудавшегося разрыва при снижении лимита повтор формы даёт успех без `/kick`, и единственный механизм схождения здесь — cron
|
||||
|
||||
## C1. Семантический smoke конфига
|
||||
|
||||
|
||||
Reference in New Issue
Block a user