docs: unify language contract to Russian and reconcile the spec with the code
The docs folder had drifted into a mixed-language contract: IMPLEMENTATION_SPEC.md was entirely in English while the rest was Russian with English headings. Prose is now Russian throughout; English is kept only where it is technically load-bearing (commands, tool output, config keys, file/module/table names, tc handles) and for terms with no settled Russian equivalent in this context (drift, reconcile, peer, namespace, handshake). docs/README.md now states that contract explicitly. Rewriting the spec surfaced several places where it described intent rather than the code. Corrected against traffic_core/src: - module layout: the spec showed traffic/ and firewall/ as directories with model/inspect/apply submodules. They are single files, and sysctl_tuning.rs was missing from the tree entirely. - data model: Peer was documented as carrying public key, handshake and counters. It carries only ipv4; runtime state lives in a separate PeerRuntime, and there is no desired-state struct at all - it is derived inline from config plus sorted peers. RuntimeTarget.netns_path is a String, not a PathBuf. - interface detection: the spec listed a third step taking the first non-loopback interface with an IPv4 address. No such fallback exists; detection bails with an explicit error, which is safer and is now documented as deliberate. - reconcile: the pseudo-flow folded firewall into reconcile_traffic. They are two independent reconciles with separate locks. - locking: only traffic.lock was documented. There are three locks, and sysctl_tuning.lock has a 30s timeout rather than 10s. Stale-lock recovery via /proc/<pid> was undocumented. - sysctl_tuning: the module also manages /etc/modules-load.d/90-awg2-harness-bbr.conf when BBR is active, and refuses to touch either managed file without its marker. Both docs and the acceptance checklist claimed only the sysctl.d file was written. - status contract: the documented output was outdated. All 15 printed sections are now listed in the order status.rs emits them, including the degraded path when no container is found, and the fact that the Network tuning block is printed between the System service line and the container lines. - swap: size was documented as a constant 2048 MB instead of swap.size_mb, and the /etc/fstab.awg2-harness.bak backup was undocumented. - apply/verify: the tolerated-error lists are now the actual closed sets from the code. Acceptance checklist gained checks for the daemon startup failure policy (sysctl/swap fatal, traffic/firewall not), lock contention and stale-lock recovery, degraded status, the modules-load file, and the marker guard. The stale host wg requirement was dropped from its host-tools item. Every path, table, counter and log string quoted in the docs was verified to exist in the source. cargo test --workspace: 110 + 4 passed.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Acceptance checklist перед production-выкладкой
|
||||
# Чек-лист приёмки перед production-выкладкой
|
||||
|
||||
## A. Базовая установка
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
- [ ] `docker ps` видит контейнер.
|
||||
- [ ] Можно получить PID контейнера.
|
||||
- [ ] `/proc/<pid>/ns/net` существует.
|
||||
- [ ] На хосте доступны `ip`, `tc`, `nsenter`, `modprobe`. Хостовый `wg` не требуется.
|
||||
- [ ] На хосте доступны `ip`, `tc`, `docker`, `nsenter`, `modprobe`. Хостовый `wg` не требуется и не проверяется.
|
||||
- [ ] `awg2-harness validate-config` проходит без ошибок.
|
||||
- [ ] `awg2-harness validate-runtime` проходит без ошибок и не требует установленного `wireguard-tools`.
|
||||
|
||||
## B. Peer discovery
|
||||
## B. Обнаружение peers
|
||||
|
||||
- [ ] `awg2-harness status` показывает найденный контейнер.
|
||||
- [ ] `awg2-harness status` показывает PID/netns контейнера.
|
||||
@@ -24,7 +25,7 @@
|
||||
- [ ] В секции `WireGuard clients` вывода `status` нет строки, соответствующей самому интерфейсу (столбец `Public key` не содержит приватный ключ устройства).
|
||||
- [ ] `docker exec "$C" readlink -f /usr/bin/wg` возвращает `/usr/bin/awg`.
|
||||
|
||||
## C. Traffic topology contract
|
||||
## C. Контракт топологии traffic
|
||||
|
||||
- [ ] `awg2-harness apply-limits` успешно завершается.
|
||||
- [ ] На AWG/WG-интерфейсе есть root qdisc `1: htb`.
|
||||
@@ -43,7 +44,7 @@
|
||||
- [ ] Class ids начинаются с `traffic.class_start_id`, default `10`.
|
||||
- [ ] Peer IP → class id mapping использует стабильный лексикографический контракт: IP сортируются как строки, не numeric IPv4.
|
||||
|
||||
## D. Idempotency and drift recovery
|
||||
## D. Идемпотентность и восстановление после drift
|
||||
|
||||
- [ ] Повторный `apply-limits` не создаёт дубли.
|
||||
- [ ] Повторный `apply-limits` не меняет healthy state без причины.
|
||||
@@ -58,30 +59,36 @@
|
||||
- [ ] После удаления peer устаревшие class/filter исчезают через rebuild.
|
||||
- [ ] После добавления peer появляются новые class/filter.
|
||||
|
||||
## E. Auto-healing lifecycle
|
||||
## E. Самовосстановление в жизненном цикле
|
||||
|
||||
- [ ] `docker restart amnezia-awg2` → лимиты восстанавливаются автоматически.
|
||||
- [ ] `systemctl restart docker` → после возвращения контейнера лимиты восстанавливаются автоматически.
|
||||
- [ ] `systemctl restart awg2-harness` → сервис сам находит контейнер и применяет/проверяет лимиты.
|
||||
- [ ] Reboot хоста → сервис стартует и восстанавливает лимиты.
|
||||
- [ ] Изменение имени интерфейса на допустимое fallback-имя не ломает detector.
|
||||
- [ ] Если контейнер временно отсутствует, daemon логирует ожидание и продолжает работу.
|
||||
- [ ] Если контейнер временно отсутствует, daemon логирует ошибку reconcile и продолжает работу.
|
||||
- [ ] На старте daemon сбой `sysctl_tuning` или `swap` завершает процесс, а сбой traffic/firewall только логируется — сервис остаётся живым.
|
||||
- [ ] После обрыва потока `docker events` он перезапускается автоматически (проверяется, например, `systemctl restart docker`).
|
||||
- [ ] Параллельный запуск `apply-limits` во время работы daemon не приводит к гонке: второй процесс ждёт блокировку до 10 секунд и затем сообщает `lock busy timeout`.
|
||||
- [ ] Устаревший lock-файл после `kill -9` не блокирует работу: следующий запуск перезахватывает `/run/awg2-harness/traffic.lock` по отсутствию `/proc/<pid>`.
|
||||
|
||||
## F. Status and observability
|
||||
## F. Статус и наблюдаемость
|
||||
|
||||
- [ ] `status` показывает desired peer limit из конфига, а не hardcoded 70.
|
||||
- [ ] `status` показывает egress state.
|
||||
- [ ] `status` показывает ingress/ifb state.
|
||||
- [ ] `status` показывает WireGuard clients (online/offline, latest handshake, RX/TX, endpoint).
|
||||
- [ ] `status` показывает missing/stale peer rules.
|
||||
- [ ] `status` явно показывает partial/drift state.
|
||||
- [ ] `status` показывает недостающие и устаревшие правила peers: в `Traffic actual` — строка `reason` (например, `missing egress class`, `egress per-peer class set mismatch`, `duplicate egress class flowid mapping detected`), в `peer_table` — колонка `drift` по каждому peer.
|
||||
- [ ] `status` явно показывает частичное состояние и drift, а не общий признак «всё хорошо».
|
||||
- [ ] `status` показывает tc filter counters по peer или `filter statistics: unavailable`.
|
||||
- [ ] Логи доступны через `journalctl -u awg2-harness`.
|
||||
- [ ] Ошибка отсутствия `tc`/`ip`/`wg` логируется явно.
|
||||
- [ ] Отсутствие обязательной host-утилиты (`tc`, `ip`, `docker`, `nsenter`) логируется явно с именем утилиты.
|
||||
- [ ] Последний reconcile result виден в status или journal.
|
||||
- [ ] `status` показывает последние события reconcile/firewall из `/run/awg2-harness/events.log`.
|
||||
- [ ] Если контейнер не найден, `status` не падает, а переходит в degraded-режим: `Peers`/`WireGuard clients` показывают `unavailable`, `Traffic actual` — `drift: unknown`, а секции host firewall, anti-torrent, swap и conntrack продолжают выводиться.
|
||||
- [ ] Секции деградируют независимо: ошибка чтения peer state не мешает вывести traffic, firewall и swap.
|
||||
|
||||
## G. Reboot scheduler
|
||||
## G. Планировщик перезагрузки
|
||||
|
||||
- [ ] Если `reboot.enabled = false`, сервис не планирует reboot.
|
||||
- [ ] Если `reboot.enabled = true`, в journal видна запланированная перезагрузка.
|
||||
@@ -90,28 +97,33 @@
|
||||
|
||||
## H. Swap
|
||||
|
||||
- [ ] `ensure-swap` создаёт `/swapfile` размером 2048 MB.
|
||||
- [ ] Права `/swapfile` — `0600`.
|
||||
- [ ] `ensure-swap` создаёт файл по пути `swap.path` (в примере `/swapfile`) размером `swap.size_mb` (в примере 2048 MB).
|
||||
- [ ] Права на swap-файл — `0600`.
|
||||
- [ ] Перед перезаписью `/etc/fstab` создана резервная копия `/etc/fstab.awg2-harness.bak`.
|
||||
- [ ] Если до запуска был активен другой swap, после `ensure-swap` он выключен.
|
||||
- [ ] После `ensure-swap` активен ровно один swap: `/swapfile`.
|
||||
- [ ] Все старые swap entries удалены из `/etc/fstab`.
|
||||
- [ ] Для block-device swap выполняется только `swapoff` и удаление `fstab` entry; block device не удаляется.
|
||||
- [ ] `vm.swappiness = 1` применён.
|
||||
- [ ] `vm.swappiness` из конфига применён в runtime и сохранён persistently.
|
||||
- [ ] Повторный `ensure-swap` ничего не пересоздаёт и не добавляет дублей.
|
||||
|
||||
## H2. Sysctl network tuning
|
||||
## H2. Сетевой baseline через sysctl
|
||||
|
||||
- [ ] `awg2-harness apply-network-tuning` успешно завершается.
|
||||
- [ ] `awg2-harness inspect-network-tuning` показывает `enabled: true` и `drift: no`.
|
||||
- [ ] Создаётся только `/etc/sysctl.d/90-awg2-harness-vpn.conf`.
|
||||
- [ ] В managed-файле есть marker `Managed by awg2-harness sysctl_tuning`.
|
||||
- [ ] Создаётся `/etc/sysctl.d/90-awg2-harness-vpn.conf`.
|
||||
- [ ] `/etc/modules-load.d/90-awg2-harness-bbr.conf` создан, если BBR активен, и отсутствует, если BBR неактивен.
|
||||
- [ ] `/etc/sysctl.conf` не изменён.
|
||||
- [ ] В обоих managed-файлах первая строка содержит marker `Managed by awg2-harness sysctl_tuning`.
|
||||
- [ ] Файл без marker по managed-пути не перезаписывается и не удаляется: команда завершается ошибкой.
|
||||
- [ ] В managed-файле отсутствует `udp_wmem_min`.
|
||||
- [ ] `net.ipv4.tcp_congestion_control` и `net.core.default_qdisc` присутствуют в managed-файле только при активном BBR.
|
||||
- [ ] Runtime apply меняет только drifted keys (`sysctl -w`), без `sysctl --system`.
|
||||
- [ ] При `tcp_bbr = auto` и недоступном BBR сервис не падает, в статусе есть warning.
|
||||
- [ ] При `tcp_bbr = require` и недоступном BBR команда/старт завершается с ошибкой.
|
||||
- [ ] При `enabled=false` managed-файл удаляется только если содержит managed marker.
|
||||
|
||||
## I. Anti-torrent nftables guard
|
||||
## I. Anti-torrent guard на nftables
|
||||
|
||||
- [ ] Если `anti_torrent.enabled = true`, в Amnezia netns есть `table inet awg2_harness`.
|
||||
- [ ] Есть chain `forward_antitorrent` с hook `forward` и policy `accept`.
|
||||
@@ -125,7 +137,7 @@
|
||||
- [ ] При `anti_torrent.enabled = false` удаляется/отключается только AWG2-owned nftables state; Docker/Amnezia rules не flush-ятся.
|
||||
- [ ] `status` показывает `nf_conntrack_count` / `nf_conntrack_max`, если sysctl доступны.
|
||||
|
||||
## J. Rollback
|
||||
## J. Откат
|
||||
|
||||
- [ ] Известно, как остановить и отключить `awg2-harness.service` и `awg2-reboot-scheduler.service`.
|
||||
- [ ] Известно, как удалить только AWG2-owned host firewall state (`inet awg2_host_firewall`).
|
||||
@@ -134,7 +146,7 @@
|
||||
- [ ] При необходимости rollback включает удаление пакета `awg2-harness` после остановки сервисов.
|
||||
- [ ] Rollback проверен на тестовом сервере.
|
||||
|
||||
## K. Packaging / release
|
||||
## K. Упаковка и релиз
|
||||
|
||||
- [ ] Package собирается на отдельной Debian 13 amd64 build-machine.
|
||||
- [ ] Production host не требует Rust/cargo/source tree.
|
||||
@@ -150,7 +162,7 @@
|
||||
- [ ] После ручной установки config `awg2-harness validate-config` проходит.
|
||||
- [ ] Сервис стартует только после явного `systemctl enable --now awg2-harness.service`.
|
||||
|
||||
## K2. Documentation / install UX
|
||||
## K2. Документация и UX установки
|
||||
|
||||
- [ ] README не утверждает, что AmneziaVPN ожидает public key.
|
||||
- [ ] Документация явно разделяет private key для AmneziaVPN и public key для `authorized_keys`.
|
||||
@@ -169,7 +181,7 @@
|
||||
- [ ] DNS-резолвинг у VPN-клиента работает при активном подключении.
|
||||
- [ ] AWG2 host firewall не открывает `53/tcp` и `53/udp`.
|
||||
|
||||
## L. Host firewall (ports/SSH safety)
|
||||
## L. Host firewall (порты и безопасность SSH)
|
||||
|
||||
- [ ] `awg2-harness validate-config` падает, если отсутствует секция `[host_firewall]`.
|
||||
- [ ] `apply-firewall` не блокирует SSH на `host_firewall.ssh_port` (default `2323`).
|
||||
|
||||
Reference in New Issue
Block a user