Files
AWG2_harness/docs/CONFIGURATION.md
T
founder e9f0e29f75 refactor: one ownership model for every file written under /etc [P1]
Two modules wrote into /etc/sysctl.d and did it differently. sysctl_tuning refused to
touch a file whose first line lacked its marker and cleaned up on disable. swap wrote
/etc/sysctl.d/99-awg2-harness.conf with a different marker, no ownership check at all -
an administrator's file at that path was silently clobbered - and never removed it when
the module was turned off, so vm.swappiness stayed pinned forever.

New module managed_file.rs holds the rules once:

- a read that fails is never an absent file;
- ownership is Absent / Managed / Foreign, decided by the marker on the first line;
- write refuses Foreign, skips when the content already matches, and is otherwise atomic:
  temp sibling created with create_new, sync_all, mode 0644, rename, directory flush;
- remove refuses Foreign and treats Absent as success.

Both modules now go through it. sysctl_tuning loses its private write_atomic,
ensure_managed_or_absent and remove_managed_sysctl_file_at.

The swappiness file moves to /etc/sysctl.d/90-awg2-harness-swap.conf with the marker
"# Managed by awg2-harness swap", matching the 90- prefix every other managed file uses.
The old path is removed on first run of the new version, but only when it still carries
the previous "# Managed by awg2-harness" marker; a foreign file there is left alone and
does not fail the migration, since the current file lives elsewhere.

Disable semantics are now explicit: swap.enabled = false removes the managed file and
does nothing else. Runtime vm.swappiness is deliberately not reset - picking a value
would be inventing a policy - and active swap is not deactivated, because merely turning
a feature off should not trigger a destructive action. Previously ensure_swap returned
early and left the persisted file behind.

Also replaced a vacuous test: swappiness_conf_render_is_stable asserted that a string
literal equals a format! of the same literal, touching no production code at all. It now
exercises render_swappiness_conf, and a companion test proves the rendered file is
recognised as managed by the same marker check that guards overwriting it - otherwise
every reconcile would refuse to touch its own output.

Twelve tests: the three ownership states, refusal to overwrite and refusal to remove a
foreign file with content asserted intact, idempotent write, stale temp handling, an
unreadable path reported as an error, plus legacy migration in all three cases - ours,
foreign, absent.

cargo test --workspace: 148 + 4 passed. clippy -D warnings clean.
2026-08-06 22:50:19 +05:00

16 KiB
Raw Blame History

Конфигурация AWG2 Harness

AWG2 Harness разрабатывался для self-hosted AmneziaWG/AWG 2.0 и совместимого legacy-сценария AmneziaWG/AWG 1.5. Конфиг живёт в /etc/awg2-harness/config.toml. Он общий для всего пакета и должен содержать только эксплуатационно значимые параметры.

Создание runtime config

Пакет намеренно не создаёт /etc/awg2-harness/config.toml автоматически. Это снижает риск случайно применить firewall с неверным SSH-портом или неверным Amnezia-портом.

После установки .deb создайте runtime config вручную:

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

Перед первым apply-firewall/apply-limits оператор обязан сверить:

  • host_firewall.ssh_port — должен совпадать с реально слушающим sshd портом;
  • host_firewall.amnezia_port — должен совпадать с published Docker/Amnezia UDP port;
  • amnezia.peer_ipv4_cidr — должен совпадать с реальной peer-подсетью;
  • anti_torrent.peer_ipv4_cidr — обычно тот же CIDR.

Discovery-команды:

docker port amnezia-awg2 2>/dev/null || docker port amnezia-awg3 2>/dev/null || docker port amnezia-awg

C="$(docker ps --format '{{.Names}}' | grep -E '^(amnezia-awg2|amnezia-awg3|amnezia-awg)$' | head -n1)"
docker exec "$C" ip -4 addr
docker exec "$C" awg show

Рекомендуемый config.toml

[amnezia]
container_names = ["amnezia-awg2", "amnezia-awg3", "amnezia-awg"]
interface_candidates = ["awg0", "wg0", "amnezia0", "tun0"]
interface_patterns = ["awg*", "wg*", "amnezia*", "tun*"]
wg_binaries = ["awg", "wg"]
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"

[sysctl_tuning]
enabled = true
apply_on_daemon_start = true
ip_forward = true
tcp_bbr = "auto"          # off | auto | require
default_qdisc = "fq"      # none | fq | fq_codel
rmem_max = 67108864
wmem_max = 67108864
netdev_backlog_enabled = true
netdev_max_backlog = 50000
udp_rmem_min_enabled = true
udp_rmem_min = 8192

[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

Пояснения

amnezia.container_names — имена контейнеров, которые считаются Amnezia/AWG. Порядок важен, сопоставление точное. По текущему production-снимку основной вариант — amnezia-awg2, fallback — amnezia-awg. amnezia-awg3 перечислен на будущее: в AmneziaVPN 5.0 отдельного типа контейнера под AWG 3.0 нет, клиент по-прежнему знает только Awg и Awg2.

amnezia.interface_candidates — точные имена интерфейса, которые пробуются первыми.

amnezia.interface_patterns — fallback-паттерны для устойчивости к обновлениям Amnezia. Они нужны для снижения риска переименования интерфейса между awg0, wg0, amnezia0, tun0.

amnezia.wg_binaries — утилита чтения peer state внутри контейнера, пробуется по порядку до первого успеха. Default — ["awg", "wg"]. В официальном образе AmneziaWG реальный бинарь /usr/bin/awg, а /usr/bin/wg — symlink на него, поэтому awg идёт первым; wg остаётся для контейнеров с чистым WireGuard. Значения выполняются внутри контейнера и валидируются как имена команд: непустые, без пробелов и без разделителей пути. Параметр опциональный — конфиги, созданные до его появления, продолжают работать с default.

Хостовый wireguard-tools не нужен и не проверяется в preflight. Peer state читается только через docker exec: AmneziaWG — userspace-реализация, её UAPI-сокет находится в RUNSTATEDIR/amneziawg/ внутри mount namespace контейнера, а nsenter -n входит только в network namespace, поэтому хостовый wg до него не достаёт.

amnezia.peer_ipv4_cidr — подсеть VPN peers. Production default — 10.8.1.0/24; значение конфигурируемое.

traffic.peer_limit — per-peer лимит в обе стороны. Текущий production default — 50mbit.

traffic.root_rate — верхний HTB-класс, в текущем production default — 500mbit.

traffic.reconcile_interval_secs — период фоновой проверки. Он нужен не вместо Docker events, а вместе с ними.

traffic.container_ready_timeout_secs — сколько ждать появления интерфейса после старта контейнера.

traffic.class_start_id — первый class id. Production default — 10, что даёт классы 1:10, 2:10 и дальше.

traffic.ifb_name — имя IFB-интерфейса для ingress shaping. Production default — ifb0. Этот параметр можно оставить в конфиге, потому что бинарь реально создаёт/удаляет этот интерфейс.

sysctl_tuning — отдельный host-level модуль управляемого сетевого baseline через sysctl:

  • persistent-файл: /etc/sysctl.d/90-awg2-harness-vpn.conf;
  • модуль swap отдельно управляет /etc/sysctl.d/90-awg2-harness-swap.conf (только vm.swappiness); прежний путь /etc/sysctl.d/99-awg2-harness.conf удаляется при первом запуске новой версии, если несёт старый маркер;
  • дополнительный managed-файл /etc/modules-load.d/90-awg2-harness-bbr.conf создаётся только при фактически активном BBR и удаляется, когда BBR неактивен;
  • оба managed-файла несут в первой строке маркер # Managed by awg2-harness sysctl_tuning; файл без маркера не перезаписывается и не удаляется, вместо этого возвращается ошибка;
  • net.ipv4.tcp_congestion_control и net.core.default_qdisc управляются только при активном BBR;
  • runtime apply: только sysctl -w и только для drifted keys;
  • на установке .deb модуль не применяется;
  • на старте daemon модуль применяется, если enabled=true и apply_on_daemon_start=true;
  • tcp_bbr = auto не валит запуск при недоступном BBR (warning), require — валит;
  • для sysctl_tuning: modprobe обязателен только для tcp_bbr = "require".

Отдельно для всего пакета:

  • traffic-модуль требует modprobe для IFB при traffic.enabled = true.

Совместимость обновлений:

  • если в существующем runtime-конфиге отсутствует блок [sysctl_tuning], он не включается автоматически (enabled=false по default для missing block);
  • для новых установок рекомендуется явный блок [sysctl_tuning] из примера, где enabled=true.

Ограничения модуля:

  • не использует /etc/sysctl.conf;
  • не запускает sysctl --system;
  • не меняет nftables, Docker bridge и container-local sysctl;
  • не управляет net.ipv4.udp_wmem_min (в Linux документации параметр указан как не имеющий эффекта).

При drift orchestrator всегда делает controlled rebuild owned topology.

anti_torrent.enabled — включает nftables connection-count guard против high-peer-count P2P-нагрузки. Это отдельный AWG2 Harness module и часть текущего production-контракта.

host_firewall.enabled — включает host-level INPUT firewall в отдельной AWG2-owned таблице inet awg2_host_firewall.

host_firewall.ssh_port — SSH порт хоста, который обязательно должен быть открыт. Production default/example: 2323. Значение обязано совпадать с реально слушающим sshd портом до применения apply-firewall.

host_firewall.amnezia_port — внешний UDP/TCP порт Amnezia, который AWG2 открывает в host INPUT (в зависимости от host_firewall.amnezia_protocols). В текущей целевой конфигурации default — 443; значение должно совпадать с реально опубликованным Amnezia UDP/TCP port.

Важно: host_firewall.amnezia_port — это transport-порт AmneziaWG/AWG (например, 443/udp). Это не порт AmneziaDNS.

  • Не задавайте amnezia_port = 53.
  • Не добавляйте DNS/53 в amnezia_protocols.
  • Не открывайте 53/tcp или 53/udp в AWG2 host firewall для ожидаемого production-профиля.

В expected production layout AmneziaDNS работает как внутренний Docker/VPN сервис и не публикуется на public host interface.

Для диагностического блока Docker/Amnezia port path в status приоритет сопоставления такой: сначала ищется published UDP binding с HostPort == host_firewall.amnezia_port, затем fallback на ключ "<amnezia_port>/udp" как container port, затем (если UDP binding ровно один) берётся он. Если UDP bindings несколько и однозначного совпадения нет, статус показывает published_udp_port: unknown.

Диагностика FORWARD в этом блоке является best-effort: сначала используется iptables -S FORWARD, затем fallback через nft list ruleset; результаты только отображаются в status и не изменяют host FORWARD/Docker NAT.

host_firewall.amnezia_protocols — протоколы для порта Amnezia (udp, tcp или оба).

Важно: awg2-harness не управляет host FORWARD/Docker NAT-цепочками. Проект сознательно не добавляет универсальные правила в FORWARD и не меняет Docker-managed forwarding path.

host_firewall.allow_icmp — разрешать ICMP/ICMPv6 на входе.

host_firewall.default_input_policy — политика INPUT chain (drop или accept). Для production рекомендуется drop.

anti_torrent.max_connections_per_peer — максимум одновременных conntrack flows на один VPN peer IP. Текущий пример использует 150: это мягче, чем строгий 100, и обычно безопаснее как первый production baseline. 100 можно использовать как более жёсткий режим; 200/250 — более permissive варианты.

anti_torrent.peer_ipv4_cidr — подсеть, на которую распространяется правило. Обычно должна совпадать с amnezia.peer_ipv4_cidr.

anti_torrent.apply_in_amnezia_netns — выбор namespace для применения anti-torrent правил:

  • true (default): применять в network namespace контейнера Amnezia;
  • false: применять в host namespace.

Для production рекомендуется true, если peer source IP (10.8.1.x) видны именно внутри netns контейнера.

apply-firewall в текущей модели делает оба reconcile: host firewall (host namespace) и anti-torrent firewall (configured namespace).

reboot.enabled — включает плановую перезагрузку.

reboot.window_start_utc / reboot.window_end_utc — UTC-окно плановой перезагрузки. Production default/example — 01:00–02:00 UTC.

swap.enabled — включает setup-задачу swap.

swap.path, swap.size_mb, swap.priority, swap.swappiness — параметры managed swap. При swap.enabled=true пакет приводит swap-state к exclusive состоянию: активен ровно один swap (swap.path), остальные выключаются и swap-entries в /etc/fstab пересобираются в одну managed строку. Рекомендуемый low priority — 1, swappiness = 1.

Ограничения swap.path: абсолютный, без компонентов . и .., без пробелов, управляющих символов и обратных слэшей, не из списка системных путей (/, /etc, /etc/fstab, /etc/passwd, /etc/shadow, /dev, /proc, /sys, /run, /boot) и не под /proc, /sys, /dev. Дополнительно на этапе применения проверяется, что ни один родительский каталог не является symlink.

Пакет создаёт рядом с swapfile маркер владения <swap.path>.awg2-harness-owned. Файл без этого маркера считается чужим: он никогда не пересоздаётся и не удаляется, вместо этого возвращается ошибка. Чужие активные swap только выключаются — их файлы пакет не удаляет.

Что не выносить в конфиг

Не нужно выносить:

  • handles 1:, 2:, ffff:;
  • имена внутренних Rust-модулей;
  • формат команд tc;
  • retry micro-tuning;
  • формат логов;
  • путь lock-файла, если нет эксплуатационной причины;
  • hardening systemd;
  • nftables hook/priority/table names, если нет осознанной причины.

Handles и структура HTB + fq_codel + ingress + ifb — это contract traffic module, а не настройка оператора.