fix29: удалён legacy, введён Debian 13-only и обновлён rollback

This commit is contained in:
2026-05-12 17:26:41 +05:00
parent 69c737bcf4
commit 44a62cf634
23 changed files with 211 additions and 1316 deletions
+6 -4
View File
@@ -38,7 +38,7 @@
- [ ] Для каждого peer есть `fq_codel` под `2:<id>`.
- [ ] Для каждого peer есть ingress filter `src peer_ip -> 2:<id>`.
- [ ] Class ids начинаются с `traffic.class_start_id`, default `10`.
- [ ] Peer IP → class id mapping сохраняет bash parity: IP сортируются лексикографически как строки, не numeric IPv4.
- [ ] Peer IP → class id mapping использует стабильный лексикографический контракт: IP сортируются как строки, не numeric IPv4.
## D. Idempotency and drift recovery
@@ -112,9 +112,11 @@
## J. Rollback
- [ ] Старые bash-файлы сохранены до завершения миграции.
- [ ] Известно, как остановить `awg2-harness.service`.
- [ ] Известно, как вернуть `amnezia-tc.service` и `amnezia-tc-watcher.service`.
- [ ] Известно, как остановить и отключить `awg2-harness.service` и `awg2-reboot-scheduler.service`.
- [ ] Известно, как удалить только AWG2-owned host firewall state (`inet awg2_host_firewall`).
- [ ] Известно, как удалить только AWG2-owned anti-torrent state (`inet awg2_harness`) в выбранном namespace.
- [ ] Известно, как удалить AWG2-owned `tc` topology (`root`, `ingress`, `ifb0`) в netns Amnezia.
- [ ] При необходимости rollback включает удаление пакета `awg2-harness` после остановки сервисов.
- [ ] Rollback проверен на тестовом сервере.
## K. Packaging / release
+1 -1
View File
@@ -1,6 +1,6 @@
# Anti-torrent connection-count guard
Этот модуль не является частью `Amnezia limits (old bash)`. Это новое расширение Rust multi-orchestrator. Оно нормально вписывается в проект, потому что сервер выделенный, а бинарь уже владеет network state вокруг Amnezia: `tc`, `ifb0`, lifecycle recovery и диагностика.
Этот модуль — часть текущего production-контракта AWG2 Harness. Он вписывается в multi-orchestrator модель, потому что сервер выделенный, а бинарь уже владеет AWG2-owned network state вокруг Amnezia: `tc`, `ifb0`, lifecycle recovery и диагностика.
## 1. Цель
+2 -2
View File
@@ -290,7 +290,7 @@ The binary should not blindly flush unrelated host firewall rules, Docker NAT ru
## 11. Anti-torrent firewall guard
This is a new module, not part of old bash parity. It fits the multi-orchestrator model because the server is dedicated and the binary is allowed to own host/network rules required for the VPN product. The rule must still be scoped narrowly and observable.
This is part of the AWG2 production contract. It fits the multi-orchestrator model because the server is dedicated and the binary is allowed to own host/network rules required for the VPN product. The rule must still be scoped narrowly and observable.
Goal: limit concurrent conntrack flows per VPN peer IP. This is not torrent protocol detection. It is a generic connection-count guard that makes high-peer-count P2P workloads unattractive while leaving normal browsing, streaming, gaming and messaging traffic unaffected.
@@ -456,7 +456,7 @@ Unit tests:
- `wg show all allowed-ips` parser;
- `wg show` fallback parser;
- CIDR filtering;
- deterministic class assignment with lexicographic bash parity;
- deterministic class assignment with lexicographic class-id stability contract;
- nftables ruleset rendering and parsing for the AWG2-owned table;
- `tc` parser for qdisc/class/filter snapshots;
- drift diff;
-280
View File
@@ -1,280 +0,0 @@
# Миграция AWG2 Harness с bash-системы на Rust multi-orchestrator
## 1. Исправленный scope
AWG2 Harness не должен быть просто Rust-эквивалентом пары bash-скриптов. Цель шире: сделать единый production multi-orchestrator, который сначала приводит сервер и Amnezia network namespace к нужному состоянию, а затем постоянно следит за drift и возвращает систему в desired state.
Источник поведения — папка `Amnezia limits (old bash)/`. Всё, что там реально настроено и используется в production, должно быть явно перенесено в Rust-модель: обнаружение контейнера, обнаружение интерфейса, получение peers, `tc` rules, `ifb0`, ingress redirect, per-peer HTB classes, `fq_codel`, auto-healing, статусная диагностика и логи. Новые правила, которых не было в old bash, например anti-torrent через nftables, должны быть описаны отдельно как расширения, а не смешаны с parity contract.
Сервер выделенный. На нём нет соседних сервисов, которые надо защищать от наших сетевых правил. Поэтому бинарь может и должен владеть host/network rules, необходимыми для Amnezia. Ограничение только одно: не ломать саму Amnezia как продукт и не менять её конфиги без явной задачи. Но системные правила, созданные old bash для лимитов, становятся зоной ответственности Rust-бинаря.
## 2. Production-контекст
Целевой хост:
- Debian 13 amd64.
- SSH настроен заранее владельцем сервера.
- AmneziaWG/AWG 2.0 устанавливается официальным способом до установки AWG2 Harness.
- После Amnezia доставляется один Rust-бинарь и единый конфиг.
- Пакет ставится один раз; автообновления самого пакета не проектируются.
- Бинарь запускается от root через systemd, потому что ему нужны Docker/systemd/net-admin/swap возможности.
Система должна переживать:
- reboot хоста;
- restart Docker;
- restart контейнера Amnezia;
- update контейнера Amnezia;
- изменение имени интерфейса `awg0` / `wg0` / `amnezia0` / `tun0`;
- добавление peer;
- удаление peer;
- частично применённое или повреждённое `tc`-состояние;
- частично применённое или повреждённое AWG2-owned nftables-состояние.
## 3. Что именно делает старая bash-система
Старая система состоит из нескольких файлов, и все они важны.
`limit_peers.sh` — главный apply/reconcile-скрипт. Он:
- берёт lock через `/tmp/amnezia-tc.lock`, чтобы не было параллельных применений;
- ищет запущенный контейнер `amnezia-awg2`, затем fallback `amnezia-awg`;
- проверяет интерфейс `WG_IF`, по умолчанию `wg0`, и если его нет, авто-детектит `wg*`, `awg*`, `amnezia*`, `tun*`;
- получает client IP из `wg show all allowed-ips`, fallback — regex из `wg show`;
- фильтрует текущую production-подсеть `10.8.1.x`;
- проверяет существующие `tc filter` по hex-адресам client IP;
- если состояние не совпадает, полностью удаляет старую topology и создаёт новую;
- создаёт root HTB на WG/AWG-интерфейсе: `tc qdisc add dev <iface> root handle 1: htb`;
- создаёт root class `1:1` с `rate 1000mbit ceil 1000mbit`;
- загружает/создаёт `ifb0`;
- добавляет ingress qdisc `ffff:` на WG/AWG-интерфейс;
- добавляет ingress redirect через `mirred egress redirect dev ifb0`;
- создаёт root HTB на `ifb0`: `handle 2:`;
- создаёт root class `2:1` с `rate 1000mbit ceil 1000mbit`;
- для каждого peer создаёт egress class `1:<class_id>` с `rate LIMIT ceil LIMIT`;
- для каждого peer создаёт `fq_codel` под egress class;
- для каждого peer добавляет egress filter `match ip dst <peer_ip> flowid 1:<class_id>`;
- для каждого peer создаёт ingress class `2:<class_id>` с тем же лимитом;
- для каждого peer создаёт `fq_codel` под ingress class;
- для каждого peer добавляет ingress filter на `ifb0`: `match ip src <peer_ip> flowid 2:<class_id>`;
- начинает `class_id` с 10;
- пишет события в `/var/log/amnezia-tc.log`.
`docker_watcher.sh` слушает `docker events --filter event=start`. При старте `amnezia-awg2` или `amnezia-awg` он ждёт появления `awg0` до 20 секунд и запускает `limit_peers.sh`. В Rust это нужно исправить: watcher не должен ждать только `awg0`, он обязан использовать общий detector интерфейса.
`check-vpn-limits` — CLI-диагностика. Он показывает systemd статус, контейнер, peers из `wg show`, классы с лимитом 70 Mbit/s, filter stats и последние строки `/var/log/amnezia-tc.log`. Rust `status` должен заменить этот отчёт, но проверять шире: оба направления, `ifb0`, ingress redirect и соответствие desired state.
`amnezia-tc.service` — oneshot apply после Docker/network-online.
`amnezia-tc-watcher.service` — постоянно запущенный watcher Docker events.
`traffic_control-was.txt`, `wg-peers.txt`, `iproute2_a_core.txt`, `permissions.txt`, `Нюансы.txt` — production-снимки. Они фиксируют реальные условия: Debian 13 cloud kernel, Docker 26.1.5, iproute2 6.15.0, интерфейс `awg0`, 23 peer IP в `10.8.1.0/24`, HTB classes `1:10..1:32`, `fq_codel` leaves и enabled systemd units.
## 4. Desired state Rust-бинаря
Rust-бинарь должен иметь explicit desired state model. Нельзя ограничиться последовательным запуском команд “как в bash”. Нужно уметь сравнить expected и actual, а затем принять решение: no-op, incremental fix или full rebuild.
Базовый desired state для traffic limits:
```text
container: first running from configured names
netns: namespace of selected container
interface: detected AWG/WG interface inside that netns
peers: IPv4 /32 allowed IPs inside configured peer CIDR
root egress qdisc: <iface> root handle 1: htb
root egress class: 1:1 rate root_rate ceil root_rate
per-peer egress class: 1:<id> rate peer_limit ceil peer_limit
per-peer egress qdisc: fq_codel under 1:<id>
per-peer egress filter: dst peer_ip -> 1:<id>
ingress qdisc: <iface> handle ffff:
ingress redirect: <iface> ingress -> ifb0
ifb device: ifb0 up in the same namespace where shaping is applied
root ingress qdisc: ifb0 root handle 2: htb
root ingress class: 2:1 rate root_rate ceil root_rate
per-peer ingress class: 2:<id> rate peer_limit ceil peer_limit
per-peer ingress qdisc: fq_codel under 2:<id>
per-peer ingress filter: src peer_ip -> 2:<id>
```
Любое отклонение от этого состояния считается drift. Если drift простой, можно исправить точечно. Для v1 допустим и предпочтителен full rebuild всей AWG traffic topology, потому что он проще, предсказуемее и повторяет текущую production-модель.
## 5. Модель управления host/network state
Исправление к предыдущей версии документации: бинарь не должен рассматриваться как осторожная внешняя обёртка, которая только вызывает `docker exec` и не владеет правилами. Правильная модель — host-level orchestrator with namespace control.
Практический порядок для v1:
1. На хосте определить контейнер через Docker API или `docker inspect`.
2. Получить PID контейнера и путь к его network namespace: `/proc/<pid>/ns/net`.
3. Все операции `wg`, `ip`, `tc`, `ifb` выполнять в правильном namespace.
4. Для реализации можно выбрать один из двух путей:
- через `nsenter -t <pid> -n ...` / `ip netns exec`-эквивалент и системные команды;
- через netlink/native Rust libraries после входа в namespace.
5. `docker exec` допустим только как fallback или transitional adapter, но не как архитектурный центр. Причина: это делает нас зависимыми от shell/tools внутри контейнера и хуже соответствует модели “наш бинарь управляет состоянием сервера”.
Для первой production-версии нормальный компромисс: Rust-бинарь владеет orchestration, locking, state comparison, retry, status и lifecycle, а низкоуровневые `tc/ip/wg` операции выполняет через команды в namespace. Нативный netlink можно вводить после стабилизации, если это даст реальный выигрыш по надёжности или нагрузке.
## 6. Обязательные функции Rust-версии
Минимум для parity со старой системой:
1. `daemon` — основной systemd-процесс.
2. `apply-limits` — ручное применение/reconcile traffic limits.
3. `status` — диагностика уровня `check-vpn-limits`, но с проверкой обоих направлений и drift.
4. `validate-config` — проверка конфига без изменения системы.
5. `apply-firewall` — ручной reconcile AWG2-owned nftables state, если anti-torrent включён.
6. `ensure-swap` — идемпотентный setup swap-файла 2 ГБ.
7. Внешний reboot scheduler `awg2-reboot-scheduler` как отдельный production service.
8. Locking, чтобы одновременно не шли два apply/reconcile.
9. Docker event watcher.
10. Периодический reconciliation loop, потому что Docker events могут быть пропущены.
11. Start-up reconciliation: при запуске daemon сразу проверить и применить лимиты и AWG2-owned firewall state.
12. Graceful behavior, если контейнер временно отсутствует: логировать и продолжать loop, а не падать насмерть.
## 7. Подкоманды
```bash
awg2-harness daemon
awg2-harness apply-limits
awg2-harness apply-firewall
awg2-harness status
awg2-harness ensure-swap
awg2-harness validate-config
awg2-harness validate-runtime
```
`daemon` — основной production-режим. Он делает initial reconcile, слушает Docker events, периодически проверяет state и пишет runtime-логи в journald. Планирование reboot выполняется отдельным сервисом `awg2-reboot-scheduler`.
`apply-limits` — немедленный reconcile traffic limits. Команда должна быть безопасной для повторного запуска.
`apply-firewall` — немедленный reconcile nftables anti-torrent rules. Команда должна менять только AWG2-owned table/chain.
`status` — отчёт о текущем состоянии: контейнер, PID/netns, интерфейс, peers, egress topology, ingress/ifb topology, drift summary, последние ошибки.
`ensure-swap` — setup-задача, не постоянный loop.
`validate-config` — синтаксическая и семантическая проверка.
## 8. Что нужно исправить относительно bash
Bash работает, но в Rust нужно убрать известные слабые места.
Первое: единый detector. Сейчас `limit_peers.sh` умеет авто-детектить интерфейс, а `docker_watcher.sh` ждёт только `awg0`. В Rust detector должен быть один и использоваться везде.
Второе: проверка полного state. Старый skip decision в основном смотрит фильтры на WG-интерфейсе. Rust должен проверять также `ifb0`, ingress redirect, classes на `ifb0`, qdisc leaves и соответствие class_id ↔ peer_ip.
Третье: парсинг peers. Текущий regex жёстко ищет `10.8.1.x`. В Rust подсеть должна быть конфигурируемой, а парсинг `wg show all allowed-ips` — структурным.
Четвёртое: наблюдаемость. Status должен отличать “container отсутствует”, “interface отсутствует”, “wg не читается”, “tc отсутствует”, “rules partial”, “rules match desired”.
Пятое: namespace ownership. Не надо полагаться на shell внутри контейнера как на единственный способ управления. Бинарь должен уметь работать от host authority и заходить в namespace контейнера.
## 9. Единый конфиг
Конфиг должен жить в `/etc/awg2-harness/config.toml` и содержать только параметры, которые реально меняются оператором.
```toml
[amnezia]
container_names = ["amnezia-awg2", "amnezia-awg"]
interface_candidates = ["awg0", "wg0", "amnezia0", "tun0"]
interface_patterns = ["awg*", "wg*", "amnezia*", "tun*"]
peer_ipv4_cidr = "10.8.1.0/24"
[traffic]
enabled = true
peer_limit = "50mbit"
root_rate = "500mbit"
reconcile_interval_secs = 15
container_ready_timeout_secs = 120
class_start_id = 10
ifb_name = "ifb0"
[host_firewall]
enabled = true
ssh_port = 2323
amnezia_port = 443
amnezia_protocols = ["udp"]
allow_icmp = true
default_input_policy = "drop"
[anti_torrent]
enabled = true
max_connections_per_peer = 150
peer_ipv4_cidr = "10.8.1.0/24"
apply_in_amnezia_netns = true
[reboot]
enabled = true
window_start_utc = "01:00"
window_end_utc = "02:00"
[swap]
enabled = true
path = "/swapfile"
size_mb = 2048
priority = 1
swappiness = 1
```
`ifb_name` можно оставить в конфиге, потому что это имя системного интерфейса, которым владеет orchestrator. Handles `1:`, `2:`, `ffff:` лучше не выносить в конфиг: это implementation contract AWG traffic module, а не эксплуатационная настройка.
## 10. Anti-torrent extension
Anti-torrent не входит в старую bash-систему, но хорошо ложится в целевую модель multi-orchestrator. Бинарь уже владеет нужным network namespace и должен уметь применять, проверять и восстанавливать nftables state так же, как он восстанавливает `tc` topology.
Правильная v1-модель — не protocol detection и не strike-система, а простой per-peer connection-count guard через nftables `ct count`. Правило должно быть scoped к VPN peer CIDR и keyed by source peer IP через `meter`, иначе можно случайно получить общий лимит на всю подсеть.
Правило должно жить в отдельной AWG2-owned таблице, например `table inet awg2_harness`, и применяться в Amnezia network namespace. Нельзя делать `flush ruleset`; удалять можно только owned table/chain.
См. `docs/ANTI_TORRENT.md`.
## 11. Systemd-модель
Вместо двух старых units нужен один daemon-unit:
```ini
[Unit]
Description=AWG2 Harness multi-orchestrator
After=network-online.target docker.service
Wants=network-online.target
Requires=docker.service
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Type=simple
User=root
ExecStartPre=/usr/sbin/awg2-harness --config /etc/awg2-harness/config.toml ensure-swap
ExecStart=/usr/sbin/awg2-harness --config /etc/awg2-harness/config.toml daemon
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
```
Сервис должен запускаться от root. На выделенном сервере не нужно искусственно ограничивать его так, чтобы он не мог управлять сетью. Hardening можно добавлять после реальной проверки, но нельзя включать `PrivateNetwork`, `PrivateDevices` или слишком жёсткий `CapabilityBoundingSet`, если они ломают `tc`, `ifb`, Docker или reboot.
## 12. Reboot и swap
Логика reboot вынесена в отдельный production-бинарь `reboot_module` (`awg2-reboot-scheduler`). Окно перезагрузки задаётся в конфиге, включая корректные окна через полночь. Механика `systemctl --no-block reboot` сохраняется.
`ensure-swap` работает как exclusive reconciler: при `swap.enabled=true` пакет полностью владеет swap-state хоста. После reconcile активен ровно один swap (`swap.path`), все прочие активные swap entries выключены (`swapoff`), а все старые swap-строки в `/etc/fstab` заменены на одну managed-запись AWG2. Для block-device swap выполняются только `swapoff` и удаление записи из `fstab`; device не удаляется.
## 13. Acceptance критерий миграции
Миграция считается корректной только если Rust-бинарь на реальном Debian 13 хосте:
- создаёт ту же traffic-control topology, что старая bash-система;
- ограничивает каждого peer в обе стороны на `traffic.peer_limit`;
- после повторного запуска не создаёт дубли и не ломает state;
- после restart контейнера сам восстанавливает rules;
- после restart Docker сам восстанавливает rules;
- после reboot хоста сам восстанавливает rules;
- после добавления peer создаёт новый class/filter;
- после удаления peer убирает устаревший class/filter через rebuild;
- `status` показывает не только “есть 70Mbit classes”, а полную картину desired vs actual;
- swap и reboot работают как части одного пакета;
- anti-torrent, если включён, применяет per-peer flow limit и не затрагивает unrelated nftables rules.
-230
View File
@@ -1,230 +0,0 @@
# Поведение `Amnezia limits (old bash)`
Этот документ фиксирует фактический behavior старой bash-системы. Он нужен как migration contract: Rust-версия должна воспроизвести не только видимый лимит 70 Mbit/s, но и всю сетевую topology, lifecycle-логику и диагностику.
## 1. Файлы старой системы
```text
Amnezia limits (old bash)/
├── limit_peers.sh.txt # основной apply/reconcile traffic-control rules
├── docker_watcher.sh.txt # watcher Docker container start events
├── check-vpn-limits.txt # status/diagnostic CLI
├── amnezia-tc.service.txt # systemd oneshot apply
├── amnezia-tc-watcher.service.txt # systemd watcher
├── traffic_control-was.txt # production snapshot of tc qdisc/classes/filters
├── wg-peers.txt # production snapshot of wg peers
├── iproute2_a_core.txt # kernel/iproute2/docker/container facts
├── permissions.txt # installed paths and enabled units
└── Нюансы.txt # operational notes and known risks
```
## 2. Runtime environment from snapshots
Зафиксированное окружение:
- Debian 13 cloud amd64 kernel line: `6.12.57+deb13-cloud-amd64`.
- `tc` from `iproute2-6.15.0`.
- Docker `26.1.5+dfsg1`.
- Amnezia container name in snapshot: `amnezia-awg2`.
- Interface in snapshot: `awg0`.
- Systemd units `amnezia-tc.service` and `amnezia-tc-watcher.service` enabled.
- Scripts installed in `/opt/amnezia/` and executable by root.
## 3. Container discovery
`limit_peers.sh` searches running containers in this order:
```text
amnezia-awg2
amnezia-awg
```
`check-vpn-limits` uses the opposite order in its array:
```text
amnezia-awg
amnezia-awg2
```
Rust should normalize this into one configured ordered list. Recommended default: `amnezia-awg2`, then `amnezia-awg`, because `amnezia-awg2` is the current AWG 2.0 production name in the snapshot.
## 4. Interface discovery
`limit_peers.sh` starts from `WG_IF`, default `wg0`. If this interface is absent inside the container, it auto-detects:
```text
wg[0-9]+ | wg | awg[0-9]* | amnezia[0-9]* | tun[0-9]+
```
`check-vpn-limits` detects:
```text
wg[0-9]+ | wg | amnezia[0-9]* | tun[0-9]+ | veth[0-9]+
```
Operational note says the important risk is future Amnezia interface rename between `awg0`, `wg0`, `amnezia0`. Rust must use a single detector shared by apply, daemon, watcher and status.
## 5. Peer discovery
Primary source:
```bash
wg show all allowed-ips
```
The script extracts the third field and keeps IPv4 addresses matching:
```text
10.8.1.[0-9]
```
Fallback source:
```bash
wg show
```
Then regex extracts the same `10.8.1.x` addresses.
The production peer snapshot contains 23 peers in `10.8.1.0/24`, with a mix of online peers and peers without endpoint/handshake. Rust must shape all peers with allowed IPs, not only currently online peers.
## 6. Traffic-control topology
The old script owns both egress and ingress shaping.
Egress on the AWG/WG interface:
```text
<iface> root qdisc: handle 1: htb
<iface> root class: 1:1 rate 1000mbit ceil 1000mbit
per peer class: 1:<class_id> rate LIMIT ceil LIMIT
per peer qdisc: fq_codel under 1:<class_id>
per peer filter: match ip dst <peer_ip> flowid 1:<class_id>
```
Ingress is implemented through IFB:
```text
modprobe ifb
ip link add name ifb0 type ifb
ip link set dev ifb0 up
<iface> ingress qdisc: handle ffff:
<iface> ingress filter: action mirred egress redirect dev ifb0
ifb0 root qdisc: handle 2: htb
ifb0 root class: 2:1 rate 1000mbit ceil 1000mbit
per peer class: 2:<class_id> rate LIMIT ceil LIMIT
per peer qdisc: fq_codel under 2:<class_id>
per peer filter: match ip src <peer_ip> flowid 2:<class_id>
```
Default limit:
```text
LIMIT=70mbit
```
Default root rate:
```text
1000mbit
```
Default first class id:
```text
10
```
The snapshot `traffic_control-was.txt` shows classes `1:10` through `1:32`, which corresponds to 23 peers. Class assignment follows bash `sort -u`, so it is lexicographic string order, not numeric IPv4 order. For example, `10.8.1.10` sorts before `10.8.1.2`. Rust v1 must preserve this if strict parity with production class IDs is required.
## 7. Rebuild behavior
Before applying new rules, the script deletes old topology:
```bash
tc qdisc del dev <iface> root || true
tc qdisc del dev <iface> ingress || true
ip link del ifb0 || true
```
Then it recreates everything. This is important: the production model is full rebuild on detected mismatch. Rust v1 should keep this behavior unless a well-tested incremental diff is implemented later.
## 8. Drift detection in bash
The script reads existing filters:
```bash
tc filter show dev <iface> parent 1:
```
It extracts hex IP matches and compares count/content with expected peer IPs. If count differs or any peer IP is missing, it rebuilds rules.
Weakness: this only strongly verifies egress filters on `<iface>`. It does not fully verify `ifb0`, ingress redirect, qdisc leaves or per-peer ingress filters. Rust must improve this and treat any partial state as drift.
## 9. Docker watcher behavior
`docker_watcher.sh` listens to:
```bash
docker events --filter event=start --format '{{.Actor.Attributes.name}}'
```
If the started container is `amnezia-awg2` or `amnezia-awg`, it waits up to 20 seconds for:
```bash
ip link show awg0
```
Then it runs:
```bash
WG_IF=awg0 /opt/amnezia/limit_peers.sh
```
Weakness: hardcoded `awg0`. Rust watcher must reuse the same interface detector as `apply-limits`.
## 10. Status behavior
`check-vpn-limits` reports:
- status of `amnezia-tc.service`;
- status of `amnezia-tc.timer`, although timer file is not present in the archive;
- running container and Docker status string;
- connected clients from `wg show`;
- TC classes with `rate 70Mbit` on the AWG/WG interface;
- filter statistics from `tc -s filter show dev <iface> parent 1:`;
- last 3 events from `/var/log/amnezia-tc.log`.
Rust `status` must be at least as useful, but should not hardcode 70 Mbit/s; it should use config and display desired vs actual.
## 11. Operational guarantees stated in old notes
The old notes claim the system currently survives:
- host reboot;
- Docker restart;
- container restart;
- Amnezia update;
- user addition;
- user removal.
They also identify two breakage scenarios:
- Amnezia fully changes `wg show` output format;
- container loses `tc` or `iproute2` tools.
In the Rust architecture, the second risk should be reduced by host namespace control: `tc/ip` should preferably come from the host while operating in the container netns, rather than requiring them inside the container.
## 12. Migration contract
Rust migration is not accepted until all of the following are true:
- same peers are discovered as in old bash;
- same default limit is applied: 70 Mbit/s per peer;
- both egress and ingress shaping exist;
- `ifb0` exists and is up when ingress shaping is enabled;
- class ids are deterministic, start from 10 and use lexicographic bash `sort -u` peer ordering;
- full rebuild removes stale peer rules after peer deletion;
- repeated apply is safe;
- daemon recovers after Docker/container lifecycle events;
- status explains actual drift rather than printing only a success banner.
-2
View File
@@ -8,8 +8,6 @@
4. [`ACCEPTANCE_CHECKLIST.md`](ACCEPTANCE_CHECKLIST.md) — checklist перед production-ready статусом.
5. [`ANTI_TORRENT.md`](ANTI_TORRENT.md) — nftables connection-count guard.
6. [`IMPLEMENTATION_SPEC.md`](IMPLEMENTATION_SPEC.md) — архитектура и state reconciliation.
7. [`OLD_BASH_BEHAVIOR.md`](OLD_BASH_BEHAVIOR.md) — что делала старая bash-система.
8. [`MIGRATION.md`](MIGRATION.md) — контекст миграции с bash на Rust multi-orchestrator.
Отдельно: