Skip to content
forestsnetPublic

About

HTTP-агент для AmneziaWG: протокол 3.1, свои сигнатурные пакеты, API совместим с amnezia-wg-easy

Resources

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

awg-agent

Агент для AmneziaWG и WireGuard. Поднимает интерфейс, раздаёт конфиги, считает трафик, режет скорость и показывает, кто подключён.

Docker Протокол Лицензия


Что умеет

Протокол 3.1 HeaderProtectionKey, RandomTrailers, DisableCookies — полный набор, который понимают тулзы в образе
Сигнатурные пакеты Настоящие CPS-датаграммы под DNS и QUIC, у каждого клиента свои
Лимиты трафика Квота с автосбросом раз в день, неделю, месяц или год
Шейпер скорости Потолок на клиента через tc: HTB на скачивание, policer на отдачу
Сроки действия Дата, после которой конфиг перестаёт работать
История расхода Приём и отдача по дням, без дырок на днях простоя
Два режима AmneziaWG с обфускацией или обычный WireGuard — одним переключателем
Веб-панель Тёмная и светлая тема, живая статистика, QR, переименование в строке

Зачем свой, если есть amnezia-wg-easy

Три причины, и все нашлись на живых установках.

Протокол. Тулзы в образе умеют 3.1, а панель писала в конфиг только набор 2.0 — сервер поднимался заведомо слабее, чем мог.

Сигнатурные пакеты. I1..I5 — это датаграмма перед хендшейком, похожая на обычный DNS-запрос или QUIC Initial: фильтру проще пропустить знакомый протокол, чем разбирать непонятный шум. Панель подставляла туда случайные числа (I1 = 104), которых тулзы не понимают.

Лицензия. amnezia-wg-easy — потомок wg-easy 14 под CC-BY-NC-SA, то есть NonCommercial. Продавать на нём сервис нельзя.

Один агент на два протокола

Переключатель VPN_PROTO — и тот же контейнер работает как AmneziaWG или как обычный WireGuard:

-e VPN_PROTO=awg   # по умолчанию: вся обфускация, протокол до 3.1
-e VPN_PROTO=wg    # обычный WireGuard, ни одной лишней строки в конфиге

В режиме wg строк Jc, S1..S4, H1..H4, I1..I5 и HeaderProtectionKey в конфиге нет вовсе — wg-quick на незнакомом параметре падает, а не игнорирует его. Команды идут через wg/wg-quick вместо awg/awg-quick, панель показывает «WireGuard» вместо «AWG 3.1», всё остальное — лимиты, скорость, сроки, история, API — работает одинаково.

Отличий по сути ровно два: набор строк в конфиге и имя утилиты. Оба бинарника лежат в одном образе, поэтому это переключатель, а не второй образ и не второй репозиторий.

Быстрый старт

docker run -d --name awg-agent \
  -e PASSWORD_HASH="$(htpasswd -nbB admin 'пароль' | cut -d: -f2)" \
  -e WG_HOST=1.2.3.4 \
  -v /opt/awg:/etc/amnezia/amneziawg \
  -p 51820:51820/udp -p 51821:51821/tcp \
  --cap-add NET_ADMIN --cap-add SYS_MODULE --cap-add NET_RAW \
  --sysctl net.ipv4.ip_forward=1 --device /dev/net/tun \
  ghcr.io/forestsnet/awg-agent:latest

Панель — на http://1.2.3.4:51821, пароль тот, из которого считали хэш.

Осторожно с compose. В docker-compose.yml доллары в bcrypt-хэше экранируются удвоением ($$2y$$05$$…). Иначе интерполяция съест куски хэша, в контейнер уедет огрызок — и не подойдёт ни один пароль.

Совместимость и переезд

Ручки повторяют amnezia-wg-easy, поэтому клиент, написанный под неё, работает с агентом без правок:

Метод Путь
POST /api/session
GET DELETE /api/session
GET POST /api/wireguard/client
DELETE /api/wireguard/client/{key}
POST /api/wireguard/client/{key}/enable · /disable
PUT /api/wireguard/client/{key}/name
GET /api/wireguard/client/{key}/configuration · /qrcode.svg
GET /api/health · /api/release
GET /api/backup · /api/backup/archive
POST /api/backup/restore

{key} — id или имя: клиент ищется и так, и так. Кука сессии называется connect.sid, как её ждут старые клиенты: новые сборки панели переименовали её в wg0.sid, и всё, что искало прежнее имя, молча переставало видеть сессию.

Своё сверх amnezia-wg-easy: блокировщик, контакты, логи

Агент несёт встроенный торрент-блокировщик (btguard, nft) и, по желанию, персональные логи клиента. Управление — по API:

Метод Путь Что
GET PUT /api/torrent конфиг блокировщика: enabled (вкл/выкл nft сразу), webhook_url/webhook_secret, node_name, block_duration — бан за торрент, сек (0 — без бана). В ответе GET ещё banned (кто в бане и сколько осталось) и nftAvailable. Секрет наружу не отдаётся — только webhookSecretSet
PUT /api/wireguard/client/{key}/torrent исключить клиента из блокировки: {"exempt": true}; «без бана»: {"noban": true} — торрент режется и уходит в отчёт, но бана нет. Поле, которого нет в запросе, не меняется
POST /api/wireguard/client/{key}/unban снять бан за торрент досрочно
PUT /api/wireguard/client/{key}/contact контакты для отчётов: {"telegram_id": "…", "email": "…"}
PUT /api/wireguard/client/{key}/log хранить персональный лог: {"enabled": true}
GET /api/wireguard/client/{key}/log?limit=N хвост лога (свежие сверху) + logEnabled + sizeBytes
DELETE /api/wireguard/client/{key}/log очистить логи клиента

Глобально включить блокировщик — PUT /api/torrent {"enabled": true}, выключить — false (nft-таблица снимается сразу). Отчёты о торренте уходят вебхуком в формате Remnawave — см. WEBHOOKS.md.

Бан за торрент — как в Remnawave: поймали на торренте, и клиент на block_duration секунд теряет весь доступ (трафик режется в обе стороны), снять досрочно — POST …/unban. 0 — без бана, режутся только торрент-пакеты. Отдельному клиенту бан можно выключить флагом noban, не трогая срок у остальных. Бан — по адресу клиента в туннеле, а не по внешнему: внешний у мобильных операторов общий на сотни людей (CGNAT) и меняется со сменой сети. Правила — в таблице inet: торрент ловится и по IPv6 (у клиента ULA-адрес, при глобальном v6 у хоста трафик уходит через NAT66).

Персональные логи (по галочке на клиенте) пишутся пофайлово в JSONL (<WG_PATH>/user-logs/<id>.log): сессии подключений, торрент-хиты и срезы трафика (rx/tx + дельта раз в USERLOG_SNAPSHOT_SECONDS). Ротация — в процессе, по размеру (USERLOG_MAX_BYTES, USERLOG_KEEP сжатых поколений .gz): агент живёт в контейнере, host-logrotate ему недоступен. По умолчанию логи выключены — включаются на конкретном клиенте (в панели — галочка «Хранить логи» + просмотр/скачивание/очистка).

Переезд одной командой

Скрипт переносит клиентов, ключи, PSK и параметры обфускации из amnezia-wg-easy и поднимает агент на том же порту и в той же подсети — уже розданные конфиги продолжают работать, перевыпускать не нужно:

# разведать (ничего не меняет)
curl -fsSL https://raw.githubusercontent.com/forestsnet/awg-agent/HEAD/migrate-from-wg-easy.sh | sudo bash -s -- --dry-run

# мигрировать
curl -fsSL https://raw.githubusercontent.com/forestsnet/awg-agent/HEAD/migrate-from-wg-easy.sh | sudo bash

# откат на amnezia-wg-easy
curl -fsSL https://raw.githubusercontent.com/forestsnet/awg-agent/HEAD/migrate-from-wg-easy.sh | sudo bash -s -- --rollback

Скрипт сам находит старый контейнер (amnezia-wg-easy или wg-easy), переносит его окружение (порты, подсеть, пароль панели) и wg0.json вместе с параметрами обфускации (включая S3/S4), останавливает панель (не удаляя — для мгновенного отката) и поднимает агента в /opt/vpn-agent/<proto>-agent-N — так же, как его ставит бот, поэтому дальше работает «Обновить агент». Проверяет /api/health, число клиентов и вход по паролю, при сбое откатывается сам. Детали и ручная процедура — MIGRATION.md.

При первом старте агент читает wg0.json от amnezia-wg-easy или wg-easy и переносит клиентов вместе с ключами, адресами и параметрами обфускации (набор 2.0 как есть, с S3/S4, если они были) — подмена контейнера никого не отключает и конфиги не ломает.

Резервная копия

Метод Путь Что делает
GET /api/backup Состояние целиком: ключи сервера и клиентов
GET /api/backup/archive То же zip'ом — кнопкой из панели
POST /api/backup/restore Поднять состояние из копии

Приватные ключи существуют ровно в одном экземпляре — на самой машине. Дамп базы бота знает, у кого какой конфиг, но не знает ключей: без копии смерть VPS означает раздать новые конфиги всем, кто на нём сидел. Ходить за clients.json по SSH на каждый из десятка серверов не нужно — бот забирает состояние этой ручкой в свой общий бэкап.

Восстановление заменяет всё, что было на агенте, и проверяет совместимость до того, как что-то трогает: копия от AmneziaWG на обычном WireGuard и копия из другой подсети отвергаются с объяснением. Свой секрет сессий агент оставляет — иначе восстановление выкинуло бы из панели того, кто его и запустил.

Лимиты, скорость и сроки

Метод Путь Тело
PUT /api/wireguard/client/{key}/quota {"bytes": 10737418240, "period": "month"}
POST /api/wireguard/client/{key}/quota/reset —
PUT /api/wireguard/client/{key}/rate {"bps": 5000000}
PUT /api/wireguard/client/{key}/expires {"at": "2026-12-31T23:59:59"}
GET /api/wireguard/client/{key}/usage?days=30 —
GET /api/usage?days=30 —

bytes: 0 снимает лимит, bps: 0 — ограничение скорости, period: "none" — считать, но не сбрасывать, at: null — бессрочно.

Перебрал лимит или вышел срок — агент убирает пира из интерфейса сам, даже если панель закрыта. Начался новый период — возвращает. Выключенного руками лимиты не трогают: решение оператора сильнее расписания.

Почему считаем сами. awg show отдаёт счётчики пира с момента, как он появился в интерфейсе: рестарт контейнера или выключение клиента их обнуляют. Агент раз в QUOTA_TICK_SECONDS берёт прирост и складывает в своё поле — лимит нельзя сбросить, переподняв интерфейс.

Скорость режется tc прямо на wg-интерфейсе: скачивание — классами HTB по адресу назначения, отдача — policer'ом на ingress. Правила перестраиваются целиком, а не правятся по одному: diff-логика на tc — верный способ забыть снять чужое правило и потом гадать, почему у клиента скорость чужого тарифа.

Настройки

Переменная По умолчанию Смысл
PASSWORD_HASH — bcrypt-хэш пароля. Без него агент не стартует
VPN_PROTO awg awg — AmneziaWG, wg — обычный WireGuard
PORT 51821 Порт панели и API
WEBUI_HOST 0.0.0.0 Адрес, на котором слушает панель
WG_HOST — Адрес сервера для Endpoint в конфигах
WG_PORT 51820 UDP-порт
WG_CONFIG_PORT = WG_PORT Порт в конфиге клиента, если наружу проброшен другой
WG_INTERFACE wg0 Имя интерфейса
WG_DEVICE авто Интерфейс для NAT. Не задан — берём маршрут по умолчанию (в контейнере это eth0)
WG_SUBNET — Подсеть целиком: 10.8.0.0/16 — 65 534 адреса вместо 253
WG_DEFAULT_ADDRESS 10.8.0.x Подсеть /24, если WG_SUBNET не задана
WG_DEFAULT_DNS 1.1.1.1 DNS в конфиге клиента
WG_ALLOWED_IPS 0.0.0.0/0, ::/0 Что заворачивать в туннель
WG_PERSISTENT_KEEPALIVE 25 Keepalive
WG_MTU 1280 (awg) / 1420 (wg) MTU. Замер: WireGuard добавляет к пакету 72 байта, AmneziaWG 3.1 — 131, то есть потолки при пути 1500 это 1428 и 1369. У awg дефолтные 1420 выше потолка: крупные пакеты пропадают, а соединение при этом «есть»
AWG_PROTO 3 Поколение AmneziaWG: 2 или 3
AWG_RANDOM_TRAILERS 1 RandomTrailers (3.1)
AWG_DISABLE_COOKIES 0 DisableCookies (3.1)
QUOTA_TICK_SECONDS 10 Как часто считать трафик и применять лимиты
HISTORY_DAYS 90 Сколько дней хранить историю расхода
SHAPER_ENABLED 1 Резать скорость через tc
WG_AUTO_UP 1 Поднимать интерфейс при старте
TORRENT_ENABLED 1 Стартовое состояние блокировщика (дальше правится по API)
TORRENT_WEBHOOK_URL — Куда слать отчёты о торренте (пусто — только дроп)
TORRENT_WEBHOOK_SECRET — Секрет HMAC для подписи отчётов
TORRENT_NODE_NAME = WG_HOST Имя ноды в отчёте
TORRENT_BLOCK_DURATION 3600 Бан за торрент, сек (0 — без бана, только дроп торрента). Дальше правится по API
USERLOG_MAX_BYTES 5242880 Порог ротации персонального лога (5 МБ)
USERLOG_KEEP 5 Сколько сжатых поколений лога хранить
USERLOG_SNAPSHOT_SECONDS 900 Как часто писать срез трафика в лог

Клиент старее 3.0 к серверу с HeaderProtectionKey не подключится вовсе: заголовки шифрованы, пакеты молча отбрасываются. AWG_PROTO=3 имеет смысл там, где все клиенты свежие.

Протокол

Поколение Что добавляет
1.x Jc, Jmin, Jmax, S1, S2, H1..H4
2.0 S3, S4, сигнатурные пакеты I1..I5
3.0 HeaderProtectionKey — шифрование заголовков
3.1 RandomTrailers, DisableCookies

Совпадать у сервера и клиента обязаны Jc/Jmin/Jmax, S1..S4, H1..H4 и HeaderProtectionKey. Сигнатурные пакеты — нет: их шлёт инициатор, принимающая сторона просто отбрасывает. Поэтому каждому клиенту свой: одинаковый I1 у всех и есть та примета, по которой их вычисляют.

Разработка

python3 -m tests.test_agent          # проверки без докера
docker build -t awg-agent:dev .      # образ

Панель — три файла без сборщика (web/): один запрос на каждый, работает сразу. Иконки — lucide, вшиты путями: тянуть библиотеку с CDN на сервер клиента незачем.

Лицензия

MIT — берите, встраивайте, продавайте. Единственное условие: сохраняйте указание авторства, см. NOTICE.

Сделано в ForestsNet.

About

HTTP-агент для AmneziaWG: протокол 3.1, свои сигнатурные пакеты, API совместим с amnezia-wg-easy

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages