# AWG2 Harness `AWG2 Harness` — host-side orchestrator для выделенного Debian-хоста с self-hosted AmneziaWG. Основной target релиза 0.3.1 — AmneziaWG 3.1; AWG 3.0/2.x сохраняются как regression compatibility. Пакет управляет инфраструктурным состоянием вокруг Amnezia: per-peer traffic shaping, IFB ingress shaping, nftables host firewall, anti-torrent connection guard, managed swap, диагностикой drift и плановым reboot. Релиз source-validated по официальной документации и исходникам AWG 3.1; фактическая live-приёмка выполняется после production deployment. Harness не заменяет AmneziaVPN, не генерирует ключи клиентов и не мигрирует AWG 2.0 на 3.1. Amnezia сначала ставится официальным способом, после этого устанавливается AWG2 Harness. Точная upstream-база зафиксирована в [`docs/UPSTREAM_COMPATIBILITY.md`](docs/UPSTREAM_COMPATIBILITY.md). ## Что ставит пакет ```text /usr/sbin/awg2-harness # основной orchestrator /usr/sbin/awg2-reboot-scheduler # отдельный reboot scheduler /lib/systemd/system/awg2-harness.service /lib/systemd/system/awg2-reboot-scheduler.service ``` `awg2-harness.service` запускает только daemon; `ensure-swap` выполняется первым этапом его собственной startup-последовательности, а не отдельным `ExecStartPre`. Дальше daemon в фоне держит drift recovery. Daemon слушает Docker events и дополнительно делает периодический reconcile для `traffic`/`firewall`/`swap`; `sysctl_tuning` применяется только на старте daemon (если включён) или вручную отдельной командой. `awg2-reboot-scheduler.service` отдельно читает общий config, выбирает случайное время в заданном UTC-окне и запрашивает reboot через `systemctl --no-block reboot`. Пакет **не** устанавливает Docker, **не** устанавливает AmneziaVPN, **не** включает сервисы автоматически и **не** создаёт боевой `/etc/awg2-harness/config.toml`. Это сделано намеренно: SSH, Amnezia и боевой config должны быть подготовлены оператором до включения firewall/reconcile. ## Репозиторий ```text awg2_harness/ ├── reboot_module/ # исходники awg2-reboot-scheduler ├── traffic_core/ # исходники awg2-harness ├── tools/ # build/package tooling ├── docs/ # эксплуатационная документация ├── config.example.toml # пример боевого конфига ├── awg2-harness.service # основной systemd unit └── awg2-reboot-scheduler.service # reboot scheduler unit ``` ## Runtime-план production-развёртывания Ниже целевой порядок установки. Его смысл простой: сначала безопасный SSH, потом официальная Amnezia, потом AWG2 Harness. ### 1. Подготовить Debian-хост Целевая платформа AWG2 Harness — только Debian 13 amd64 на KVM/полноценной VM или dedicated host. Минимальный baseline перед установкой Amnezia: ```bash apt update apt install -y openssh-server ca-certificates curl timedatectl set-timezone Etc/UTC ``` ### 2. Настроить SSH до установки Amnezia Production-пример использует порт `2323` и вход только по ключу. Сгенерируйте отдельную пару ключей для Amnezia на Windows (RSA PEM-формат): ```cmd ssh-keygen -t rsa -b 4096 -m PEM -f %USERPROFILE%\.ssh\amnezia_key ``` Дальше контракт строго такой: - на сервер добавляется **публичный** ключ: содержимое `%USERPROFILE%\.ssh\amnezia_key.pub` в `/root/.ssh/authorized_keys`; - в приложении AmneziaVPN в поле **Password or SSH private key** вставляется **приватный** ключ: содержимое `%USERPROFILE%\.ssh\amnezia_key` (без `.pub`). Критично: `.pub` никогда не вставляется в AmneziaVPN как private key. ```bash install -d -m 0700 /root/.ssh nano /root/.ssh/authorized_keys chmod 0600 /root/.ssh/authorized_keys ``` Затем включите отдельный drop-in для порта и key-only login: ```bash install -d -m 0755 /etc/ssh/sshd_config.d cat >/etc/ssh/sshd_config.d/99-awg2-production.conf <<'EOF' Port 2323 PermitRootLogin prohibit-password PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes EOF sshd -t systemctl reload ssh 2>/dev/null || systemctl reload sshd ss -ltnp | grep ':2323' ``` Критично: не закрывайте старую SSH-сессию, пока не проверили новый вход с другого терминала: ```bash ssh -i ~/.ssh/amnezia_key -p 2323 root@ ``` Windows-проверки: ```cmd type %USERPROFILE%\.ssh\amnezia_key.pub type %USERPROFILE%\.ssh\amnezia_key ssh -i %USERPROFILE%\.ssh\amnezia_key -p 2323 root@ ``` Поля в интерфейсе AmneziaVPN: ```text Server IP address [:port] :2323 SSH Username root Password or SSH private key содержимое %USERPROFILE%\.ssh\amnezia_key На сервер содержимое %USERPROFILE%\.ssh\amnezia_key.pub в /root/.ssh/authorized_keys ``` Если сервер уже переведён на 2323 случайно и нужно временно вернуть 22 для тестов, поменяйте `Port 2323` на `Port 22`, выполните `sshd -t`, `systemctl reload ssh` и проверьте `ss -ltnp | grep ':22'`. ### 3. Установить AmneziaVPN официальным способом AWG2 Harness не устанавливает Amnezia. Используйте официальный self-hosted flow AmneziaVPN: 1. Установите актуальное приложение AmneziaVPN на рабочую машину. 2. В приложении выберите self-hosted server. 3. Укажите публичный IPv4 сервера, SSH-порт, SSH-пользователя и приватный ключ. 4. Установите AmneziaWG 3.1. Для self-hosted deployment требуется AmneziaVPN 5.0.1.5 или новее. 5. После установки убедитесь, что контейнер поднят и UDP-порт опубликован. Проверка на сервере: ```bash docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' C="$(docker ps --format '{{.Names}}' | grep -E '^(amnezia-awg2|amnezia-awg)$' | head -n1)" echo "container=$C" docker port "$C" docker exec "$C" awg show ``` Ожидаемые имена контейнера по умолчанию: основной `amnezia-awg2`, затем legacy fallback `amnezia-awg`. Ожидаемый интерфейс внутри container netns — `awg0`; дополнительные fallback-кандидаты описаны в `config.example.toml`. Любое другое корректное имя можно указать вручную в config. ### Совместимость с AmneziaWG 3.1 Канонические версии, commit SHA и официальные ссылки приведены в [`docs/UPSTREAM_COMPATIBILITY.md`](docs/UPSTREAM_COMPATIBILITY.md). Релиз 0.3.1 source-validated against AWG 3.1; до отдельной live-приёмки это не означает «протестировано на production AWG 3.1». Обвязка не реализует AWG на уровне пакетов: `tc`, IFB и `nftables` работают с inner-IP уже после декапсуляции, поэтому header protection, content padding, RandomTrailers и DisableCookies её не касаются. Peer state читается через `docker exec` официальным `awg` внутри контейнера; `/usr/bin/wg` в reference image является symlink на него. Это единая container-side точка доступа независимо от того, использован kernel `amneziawg` или fallback `amneziawg-go`, и она не привязывает harness к host-side tools. Порядок задаётся `amnezia.wg_binaries` (по умолчанию `["awg", "wg"]`); host `wireguard-tools` не требуется. Smoke-проверка после обновления контейнера: ```bash C="$(docker ps --format '{{.Names}}' | grep -E '^(amnezia-awg2|amnezia-awg)$' | head -n1)" docker exec "$C" readlink -f /usr/bin/wg # ожидается /usr/bin/awg docker exec "$C" awg --version docker exec "$C" ip -o link show awg0 docker exec "$C" ip -d link show awg0 docker exec "$C" awg show all allowed-ips docker port "$C" # Структура dump без раскрытия значений: docker exec "$C" awg show all dump | awk -F '\t' '{ print "row=" NR, "fields=" NF, "interface=" $1 }' ``` Для reference tools AWG 3.1 ожидаемо: `/usr/bin/wg` указывает на `/usr/bin/awg`, интерфейс `awg0` существует, peer-адреса отображаются, серверный порт опубликован по UDP, interface-row содержит 30 полей, peer-row — 9. Исторически WireGuard/AWG 2.x/AWG 3.0/AWG 3.1 tools печатают interface-row шириной 5/14/28/30 полей соответственно; текущий peer-row содержит 9. Это только диагностика: ширина зависит от producer tools, а не от версии параметров туннеля. По количеству колонок harness не определяет версию и не классифицирует peer. Полный вывод `awg show all dump` передавать нельзя: во второй колонке interface-строки находится приватный ключ устройства. ### 3.1 Установить AmneziaDNS (обязательный production-шаг) Для production-профиля AWG 3.1 установите AmneziaDNS из приложения AmneziaVPN после установки AmneziaWG: ```text AmneziaVPN: Servers → → Services → AmneziaDNS → Install ``` Затем включите использование DNS для подключения: ```text Settings → Connection → DNS servers → Use AmneziaDNS ``` Security note: В ожидаемой production-схеме AmneziaDNS слушает `53/tcp` и `53/udp` внутри Docker и не публикуется на host interface. Не открывайте публично `53/tcp` или `53/udp` в host firewall; DNS должен быть доступен только через VPN-path. ### 4. Собрать пакет на build-machine На production-хосте не нужны Rust, cargo и source tree. Сборка делается отдельно. ```bash cd awg2_harness ./tools/build.sh --prepare-build-env --bootstrap-rust --yes ./tools/build.sh --package (cd dist && sha256sum -c SHA256SUMS) dpkg-deb -I dist/awg2-harness_0.3.1_amd64.deb dpkg-deb -c dist/awg2-harness_0.3.1_amd64.deb | sort ``` Release package берёт версию из `traffic_core/Cargo.toml`. `--version` предназначен только для осознанного Debian packaging override и не меняет версию crate. ### 5. Перенести `.deb` и config на production Есть два production-сценария установки. #### Вариант A: config передаётся вместе с `.deb` ```bash scp -P 2323 dist/awg2-harness_*_amd64.deb root@:/root/ scp -P 2323 dist/SHA256SUMS root@:/root/ scp -P 2323 config.example.toml root@:/root/awg2-harness.config.toml ``` Перед установкой отредактируйте `/root/awg2-harness.config.toml`: SSH-порт должен совпадать с реальным sshd, `amnezia_port` — с опубликованным UDP-портом контейнера, `peer_ipv4_cidr` — с подсетью клиентов Amnezia. #### Вариант B (основной): config создаётся после установки пакета ```bash scp -P 2323 dist/awg2-harness_*_amd64.deb root@:/root/ scp -P 2323 dist/SHA256SUMS root@:/root/ ``` ### 6. Установить пакет и config Вариант A: ```bash cd /root sha256sum -c SHA256SUMS chmod 644 /root/awg2-harness_0.3.1_amd64.deb apt install ./awg2-harness_*_amd64.deb install -d -m 0755 /etc/awg2-harness install -m 0644 /root/awg2-harness.config.toml /etc/awg2-harness/config.toml ``` Пакет ставит пример config в `/usr/share/doc/awg2-harness/repo/config.example.toml`, но не копирует его в `/etc` автоматически. Вариант B (основной): ```bash cd /root sha256sum -c SHA256SUMS chmod 644 /root/awg2-harness_0.3.1_amd64.deb apt install ./awg2-harness_*_amd64.deb install -d -m 0755 /etc/awg2-harness cp /usr/share/doc/awg2-harness/repo/config.example.toml /etc/awg2-harness/config.toml nano /etc/awg2-harness/config.toml ``` В варианте B после `cp` обязательно проверьте перед первым `apply-firewall`: - `host_firewall.ssh_port` совпадает с реальным sshd-портом; - `host_firewall.amnezia_port` совпадает с published Docker UDP port; - `amnezia.peer_ipv4_cidr` и `anti_torrent.peer_ipv4_cidr` совпадают с реальной peer-подсетью. ### 7. Ручная preflight-проверка ```bash awg2-harness --config /etc/awg2-harness/config.toml validate-config awg2-harness --config /etc/awg2-harness/config.toml validate-runtime awg2-harness --config /etc/awg2-harness/config.toml ensure-swap awg2-harness --config /etc/awg2-harness/config.toml apply-firewall awg2-harness --config /etc/awg2-harness/config.toml apply-limits awg2-harness --config /etc/awg2-harness/config.toml status ``` Ожидаемый healthy state: ```text System: service: inactive # до enable --now нормально Traffic actual: drift: no Host firewall: drift: no Anti-torrent: drift: no Swap: drift: no ``` Если peers ещё нет, это здоровое desired state. `apply-limits` удалит оставшуюся AWG2-owned traffic topology, если раньше peers были, и завершится успешно. В штатном цикле daemon это не создаёт повторяющийся `info`-шум. ### 8. Включить systemd services ```bash systemctl daemon-reload systemctl enable --now awg2-harness.service systemctl enable --now awg2-reboot-scheduler.service systemctl status awg2-harness.service --no-pager -l systemctl status awg2-reboot-scheduler.service --no-pager -l journalctl -u awg2-harness.service --since '5 minutes ago' --no-pager journalctl -u awg2-reboot-scheduler.service --since '5 minutes ago' --no-pager ``` После 30–60 секунд: ```bash awg2-harness --config /etc/awg2-harness/config.toml status ``` Healthy daemon показывает `service: active`, `traffic=ok`, `firewall=ok`, `swap=ok` в recent events и пустой `last_error`. ### 9. Проверить после reboot ```bash reboot ``` После возврата: ```bash systemctl status docker --no-pager -l systemctl status awg2-harness.service --no-pager -l systemctl status awg2-reboot-scheduler.service --no-pager -l awg2-harness --config /etc/awg2-harness/config.toml status ``` Дополнительно можно проверить фактическую `tc` topology внутри network namespace Amnezia: ```bash C="$(docker ps --format '{{.Names}}' | grep -E '^(amnezia-awg2|amnezia-awg)$' | head -n1)" PID="$(docker inspect -f '{{.State.Pid}}' "$C")" nsenter -t "$PID" -n -- tc class show dev awg0 nsenter -t "$PID" -n -- tc class show dev ifb0 nsenter -t "$PID" -n -- nft list table inet awg2_harness nft list table inet awg2_host_firewall ``` Для default config на одном peer ожидаются классы `1:10` и `2:10` с `rate 50Mbit ceil 50Mbit`, root classes `1:1` и `2:1` с `500Mbit`, host firewall table `inet awg2_host_firewall` и anti-torrent table `inet awg2_harness` внутри Amnezia netns. ## CLI команды Все команды принимают `--config`. Если параметр не указан, используется `/etc/awg2-harness/config.toml`. ```bash awg2-harness --config /etc/awg2-harness/config.toml ``` `validate-config` проверяет TOML и смысловые ограничения: пустые списки, формат rate (`50mbit`, `500mbit`), CIDR, reboot window, swap path, SSH/Amnezia ports, protocol list. `validate-runtime` проверяет host tools, которые реально нужны выбранным модулям: `tc`, `ip`, `docker`, `modprobe`, `nsenter`, а также `nft`, `systemctl`, swap tooling при включённых firewall/reboot/swap. Хостовый `wg` в этот список не входит: peer state читается внутри контейнера через `docker exec` (`amnezia.wg_binaries`). `ensure-swap` приводит swap к desired state: создаёт/проверяет swapfile, включает его с заданным priority, применяет `vm.swappiness`, делает swap state exclusive и переписывает swap entries в `/etc/fstab` под управляемый swapfile. `apply-firewall` делает два reconcile: host INPUT firewall в таблице `inet awg2_host_firewall` и anti-torrent guard в выбранном namespace. Команда не управляет Docker NAT и host `FORWARD`. `apply-limits` находит Amnezia container, заходит в его network namespace, читает peers через container-side `awg`/`wg`, строит per-peer HTB/fq_codel классы на AWG/WG интерфейсе и IFB ingress path. Peer filters создаются classifier-ом `flower` с единым `pref 10`; legacy peer-`фильтры `u32` считаются drift и мигрируют через controlled rebuild. Healthy state не печатает сообщение на каждом цикле daemon. `status` печатает фактическую диагностику: сервис, контейнер, netns, интерфейс, peers, WireGuard runtime, traffic desired/actual, host firewall drift, Docker/Amnezia port path, anti-torrent counters, swap state, conntrack, recent events и planned reboot. `daemon` — режим для systemd. На старте делает startup reconcile, затем слушает Docker events и периодически сверяет traffic/swap/firewall. Вручную обычно не запускается, кроме отладки. `awg2-reboot-scheduler --config /etc/awg2-harness/config.toml` читает только секцию `[reboot]`, пишет план в `/run/awg2-harness/reboot.plan` и ждёт назначенного UTC-времени. ## Что происходит под капотом Traffic shaping реализован через `tc` в network namespace контейнера Amnezia. На egress используется root qdisc `htb 1:` на AWG/WG интерфейсе, root class `1:1`, per-peer classes `1:` и `fq_codel` под каждым peer class. Egress peer filter сопоставляет `dst_ip /32`, ingress peer filter — `src_ip /32`; оба используют `flower pref 10`. На ingress ставится `ingress ffff:` на AWG/WG интерфейс, входящие пакеты перенаправляются через `mirred` в `ifb0`, а там строится отдельная HTB topology `2:` с такими же per-peer лимитами. Только catch-all redirect в IFB остаётся `u32`. Вся `tc` topology после teardown передаётся одним fail-fast batch через `tc -batch -`, затем обязательно проверяется. Peer discovery берётся из WireGuard/AmneziaWG allowed IPs, а не из handshake state. Поэтому offline peer с `allowed ips: 10.8.1.1/32` тоже получает лимит заранее. Host firewall — отдельная AWG2-owned таблица `inet awg2_host_firewall`. Она открывает loopback, established/related, SSH-порт из конфига, Amnezia-порт по указанным протоколам и optionally ICMP/ICMPv6. Docker-managed `FORWARD` и NAT не трогаются. Anti-torrent guard — отдельная AWG2-owned таблица `inet awg2_harness` обычно внутри Amnezia netns. Правило ограничивает число новых conntrack flows per source VPN IP. Это не DPI и не распознавание BitTorrent-протокола; это грубый, дешёвый guard против сотен одновременных P2P-соединений. Swap module намеренно строгий: если `swap.enabled=true`, desired state — один управляемый swapfile. Старые swap entries удаляются из `/etc/fstab`, block-device swap выключается, но block device не удаляется. ## Production-границы ответственности AWG2 Harness владеет только собственным state: - `tc` topology на Amnezia interface и configured `ifb_name`; - nft table `inet awg2_host_firewall` в host namespace; - nft table `inet awg2_harness` для anti-torrent; - `/run/awg2-harness/*` runtime state; - managed swapfile и swap entries, если `swap.enabled=true`. AWG2 Harness не владеет: - установкой AmneziaVPN; - Docker daemon lifecycle; - Docker NAT/FORWARD chains; - генерацией client configs/keys; - политикой пользователя внутри Amnezia app; - сторонними firewall-таблицами. ## Документация - [`docs/INSTALLATION.md`](docs/INSTALLATION.md) — установка и preflight. - [`docs/UPSTREAM_COMPATIBILITY.md`](docs/UPSTREAM_COMPATIBILITY.md) — upstream-версии и контракт AWG 3.1. - [`docs/CONFIGURATION.md`](docs/CONFIGURATION.md) — параметры `config.toml`. - [`docs/ACCEPTANCE_CHECKLIST.md`](docs/ACCEPTANCE_CHECKLIST.md) — production checklist. - [`docs/ANTI_TORRENT.md`](docs/ANTI_TORRENT.md) — connection-count guard. - [`docs/PRODUCTION_EXAMPLE.md`](docs/PRODUCTION_EXAMPLE.md) — эталонный production-пример. - [`tools/README.md`](tools/README.md) — сборка и упаковка. - [`reboot_module/README.md`](reboot_module/README.md) — внешний reboot scheduler.