# Forgetting Панель управления и API для VPN на базе [AmneziaWG](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module) > [!WARNING] > Данный проект предназначен только для использования в законных целях. - **VPN-пользователи** с ключами `vpn://` для AmneziaVPN и **мастер-ключи** `sen://` для SenAWG: одна ссылка на несколько устройств, каждое регистрируется само. - **Несколько серверов** под одной панелью: ноды подключаются к ней сами, панель можно вынести на отдельный сервер без VPN. - **Журнал подключений**: кто, когда и сколько был в VPN, кто давно не подключался. - **Мониторинг**: встроенный Prometheus и графики в панели, метрики для своего Prometheus. - **Защита от обнаружения**: параметры обфускации, порты и пути у каждого сервера свои, а открытые порты отвечают сканеру как пустой веб-сервер. ## Содержание - [Архитектура](#архитектура) - [Требования](#требования) - [Установка](#установка) · [Параметры установщика](#параметры-установщика) · [Обновление и удаление](#обновление-и-удаление) - [Несколько серверов](#несколько-серверов) - [Защита от обнаружения](#защита-от-обнаружения) - [Журнал подключений](#журнал-подключений) - [Мониторинг](#мониторинг) - [Панель за nginx](#панель-за-nginx) - [API](#api) - [Управление](#управление) · [Настройки `cli.env`](#настройки-clienv) ## Архитектура Один статический бинарник `awg-control` (Go) в `/opt/awg-control/` — в нём все роли; команда `awg-ctrl` — ссылка на него: | Компонент | Роль | |---|---| | **awg-ctrl** (`awg-control ctrl`) | Привилегированный REST-бэкенд. Управляет пирами через `awg`/`awg-quick`, ведёт SQLite-базу (`users.db`) и журнал подключений, генерирует `vpn://` ключи. Слушает только loopback. | | **awg-ui** (`awg-control ui`) | Веб-панель (React, `awg-ui/dist`), вход по JWT, прокси к awg-ctrl, публичный API `/api/v1`, подписка `sen://`, хаб нод и `/metrics`. База — `ui.db`. | | **awg-agent** (`awg-control agent`) | Только на ноде: исходящее WSS-соединение к панели, исполняет её запросы на локальном awg-ctrl. | | **cli** (`awg-ctrl …`) | Менеджер процессов, логин и пароль панели, привязка нод, перенос панели, мониторинг, TUI-меню. | Внутренняя связь awg-ui → awg-ctrl подписывается асимметрично (Ed25519): приватный ключ только у awg-ui, awg-ctrl проверяет подпись публичным. Данные — в `/etc/amnezia/amneziawg/` (вне каталога установки, переустановку переживают), настройки сервисов — в `/opt/awg-control/cli/cli.env`. Порты наружу (номера у каждого сервера случайные): | Порт | Что | Кто открывает | |---|---|---| | `SERVER_PORT`/udp | VPN-клиенты | везде, где есть VPN | | `UI_PORT`/tcp | панель и API, под секретным путём | панель | | `SUB_PORT`/tcp | подписка `sen://` (по умолчанию HTTPS) | панель | | `NODE_PORT`/tcp | хаб нод (WSS) | панель, если принимает ноды | Нода открывает только UDP для клиентов: к панели она подключается сама. ## Требования - **ОС**: Ubuntu 24.04+ (тесты проходили на Ubuntu 24.04.4 LTS) или Debian 11–13 (поддержка экспериментальная: PPA amnezia подключается вручную, серия подбирается по релизу Debian и переопределяется через `AMNEZIA_PPA_SUITE`). - **Права**: root. - **Виртуализация**: KVM или железо (не OpenVZ/LXC/Docker/WSL — нужен загружаемый kernel-модуль AmneziaWG). - **Заголовки ядра** под текущее ядро (для сборки DKMS-модуля). - **Архитектура**: x86_64 или aarch64. Совместимость установщик проверяет сам перед любыми изменениями. ## Установка На сервере, от root: ```bash bash <(curl -Ls https://raw.githubusercontent.com/maeneko/forgetting/main/install.sh) ``` Не от root — через `sudo bash -c`: `sudo bash <(curl …)` падает с `/dev/fd/63: No such file or directory`, потому что `sudo` закрывает дескриптор с текстом скрипта. ```bash sudo bash -c "$(curl -Ls https://raw.githubusercontent.com/maeneko/forgetting/main/install.sh)" ``` Установщик спросит роль сервера: | Роль | Что ставится | |---|---| | **Панель + VPN** (`standalone`) | Всё на одной машине; к ней можно подключать ноды | | **Только панель** (`core`) | Панель без VPN; серверы подключаются к ней нодами | | **Нода** (`node`) | VPN без панели; подключается к панели по строке `awgjoin://…` («Добавить сервер» в панели) | В конце установщик печатает адрес панели **вместе с секретным путём** — `http://IP:PORT/<путь>/` — логин и пароль. Без пути панель отвечает `404`, поэтому адрес сохраните целиком; позже его покажут `awg-ctrl status` и `awg-ctrl credentials`. Порты на самом сервере установщик открывает сам (UFW, firewalld или iptables — что активно). **Фаервол хостера** (панель провайдера, security group) отсюда не виден: открой там порты из итога установки, иначе клиенты не подключатся, а на сервере всё будет выглядеть исправным. Сервер ставится на AmneziaWG 3.1. Если модуль в ядре старше 3.x или ядро старше 5.5, установка остановится с диагностикой — поставить на 2.0 можно через `AWG_GEN=auto`. Перевести сервер на 3.1 позже можно кнопкой «Перейти на AWG 3.1» в панели: пользователи сохраняются, ключи перевыпускаются автоматически. ### Параметры установщика Задаются переменными окружения перед запуском, например `ROLE=node JOIN='awgjoin://…' bash <(curl -Ls …/install.sh)`. Всё необязательно. | Переменная | Что делает | |---|---| | `ROLE` | `standalone`, `core` или `node` — без вопроса | | `JOIN` | строка `awgjoin://…` для ноды | | `AWG_GEN` | `3.1` (по умолчанию), `auto` — 3.1, если ядро позволяет, иначе 2.0; `2.0` — не трогать модуль | | `AWG_PIN` | `y` (по умолчанию) — закрепить версию пакетов AmneziaWG (`apt-mark hold`) | | `AWG_PORT` | UDP-порт VPN вместо случайного | | `AWG_MTU` | MTU туннеля вместо случайного (1280–1500) | | `UI_PORT`, `UI_USER`, `UI_PASS` | порт, логин и пароль панели — без вопросов | | `UI_PREFIX` | свой секретный путь панели (8–64 символа `A–Z a–z 0–9 _ -`); `off` — без пути | | `SERVER_NAME` | имя VPN-сервера — без вопроса | | `SUB_PORT`, `SUB_TLS`, `NODE_PORT` | порт и HTTPS подписки, порт хаба нод — без вопросов | | `ACTION` | `update`, `reinstall` или `uninstall` — действие без меню | | `UNINSTALL` | `y` — удалить без меню и без подтверждения | | `AMNEZIA_PPA_SUITE` | серия PPA для Debian (`focal`/`jammy`/`noble`) | | `REPO_BASE`, `REPO_FALLBACK`, `ARCHIVE_URL`, `AWG_LOCAL_TARBALL` | откуда брать архив релиза (своё зеркало, локальный файл) | | `BRAND`, `CHANNEL` | имя продукта в баннерах и панели | ### Обновление и удаление Повторный запуск установщика на сервере с установкой — или пункты **7–9** меню `awg-ctrl`, или команды `awg-ctrl update | reinstall | uninstall` — предлагает: 1. **Обновить** — новая версия, настройки, пользователи и роль сохраняются. Установке без секретного пути панели он выдаётся, и установщик печатает новый адрес: закладки и адрес внешнего API нужно обновить. Оставить без пути — `UI_PREFIX=off` перед запуском или пустое `UI_PREFIX=` в `cli.env`. 2. **Переустановить** — с нуля; пользователей и ключ сервера можно сохранить. Тогда по умолчанию остаются и порты (VPN, панели, подписки, хаба), параметры обфускации, MTU, секретный путь, логин и пароль панели — они зашиты в выданные ключи, ссылки и закладки. Прежняя установка убирается только после ответа «да» на «Продолжить?». 3. **Удалить** — полностью (без меню: `UNINSTALL=y`, подтверждение «удалить» тогда не спрашивается). Удаление снимает сервисы и юнит systemd, интерфейс `awg1`, nft-таблицы шейпинга и блокировки торрентов, открытые установщиком порты, Prometheus из `awg-ctrl monitor install`, `/opt/awg-control`, `/etc/amnezia` (пользователи, ключи, базы), команду `awg-ctrl`, sysctl-файл и логи установки (в них пароль панели). Перед этим установщик предлагает сохранить копию данных в `/root/forgetting-backup-<время>.tar.gz`. AmneziaWG (пакеты, модуль, PPA) — по отдельному вопросу; общие зависимости (dkms, build-essential, iptables, заголовки ядра) остаются. Ноду после удаления уберите и из списка серверов панели. ## Несколько серверов **Подключить сервер.** В панели «Добавить сервер» выдаёт строку `awgjoin://…` (действует час). На новом сервере — установщик с ролью «Нода» и этой строкой, или на уже установленном — `awg-ctrl join ''`. Агент ноды сам подключается к хабу панели по WSS; доверие — закреплённый отпечаток сертификата хаба и ключ ноды. В панели нода появляется в списке серверов, ею управляют так же, как своим. `awg-ctrl unjoin` — отвязать. **Перенести панель на отдельный сервер** (например, без VPN). Вкладка «Перенос» или CLI: 1. На новом сервере — установка с ролью «Только панель». 2. На новой панели — код переноса `awgmove://…`; на старой — вставить его (или `awg-ctrl migrate export` на старом и `awg-ctrl migrate import <архив>` на новом). 3. Старый сервер становится нодой #0 новой панели и пересылает туда свои прежние порты: ссылки `sen://`, ноды и внешний API продолжают работать, закладки панели — тоже (новая панель принимает и свой секретный путь, и старый). 4. Когда вкладка «Перенос» покажет, что новый адрес знают все устройства и ноды, старый сервер можно выключать. Ноды, переехавшие вручную, — `awg-ctrl rehome <хост[:порт]>`. `awg-ctrl migrate cancel` — отменить на старом сервере, пока перенос не завершён. ## Защита от обнаружения Раньше у всех установок были одинаковые порт, параметры обфускации и ответы на запросы — одно правило DPI находило их все. Теперь общего у серверов нет: **VPN.** На новой установке случайны: - UDP-порт (20000–59999), если не задан `AWG_PORT`; - параметры обфускации `Jc/Jmin/Jmax`, `S1–S4` (размеры handshake-пакетов разные — по длине тип пакета не узнать), `H1–H4` (непересекающиеся диапазоны); - `I1` — пакет-маскировка перед handshake: DNS-запрос к случайному популярному домену (формат проверяется до записи — его принимают и kernel-модуль, и amneziawg-go); - MTU в диапазоне 1320–1376. При переустановке с сохранением данных всё это берётся из прежнего `awg1.conf`: параметры зашиты в выданные ключи. Поэтому **серверы, поставленные до рандомизации, сохраняют старые общие значения** — чтобы уйти от них, нужна переустановка без сохранения данных и раздача новых ключей. Исключение — `I1`: его сервер не сверяет, и если в конфиге его не было, при переустановке он генерируется новым, не ломая старые ключи. **Открытые порты** отвечают тому, кто не доказал право на ответ, как пустой веб-сервер на Go — `404 page not found`: | Порт | Кому отвечает по протоколу | |---|---| | Панель | запросам под секретным путём (`UI_PREFIX`); с loopback — и без него | | Подписка `sen://` | `peek`/`register` с секретом мастер-ключа из ссылки; остальным запросам — с подписью ключом устройства | | Хаб нод | агенту, подписавшему своим ключом сам запрос на WebSocket | Подробности: - `GET /ui/brand` (имя и версия) — только после входа; экран логина не показывает имя продукта. - Удалённому устройству подписка отвечает `410` (снять конфиг), только если запрос подписан его ключом (`revoked_devices`); устройства, удалённые до этой версии, получают тихий `404`. - Пока выдан код переноса, `POST /migrate/v1/import` принимается и без секретного пути. - Агенты нод прежних версий подписи не шлют: хаб пускает их, пока в панели есть нода, ни разу не подключавшаяся по-новому, и пишет об этом в лог. Обновили все ноды — путь без подписи закрывается сам (`HUB_LEGACY` в `cli.env` — принудительно). Часы ноды могут расходиться с панелью до суток. - Самоподписанные сертификаты подписки и хаба получают случайное имя вместо общих `sen` / `awg-node`. Клиенты и ноды сверяют ключ, а не имя; прежние сертификаты остаются со старым именем. **Чего это не закрывает.** Секретный путь и тихий `404` защищают от активного сканирования. Панель по умолчанию работает по голому HTTP — её трафик (пути, JSON, токены) виден в сети. Ставьте её за nginx с TLS ([ниже](#панель-за-nginx)) или заходите по VPN. Подписка `sen://` по умолчанию HTTPS, хаб — всегда TLS. ## Журнал подключений Вкладка «Подключения» показывает по выбранному серверу: - **События** — кто подключился к VPN и отключился, когда, сколько длилась сессия и сколько скачал и отдал. Поиск — по имени пользователя, мастер-ключу, устройству или внутреннему IP. - **Кто когда был** — все пользователи и устройства `sen://` сервера: онлайн сейчас, когда были на связи последний раз и кто не подключался ни разу с начала журнала. Журнал ведёт awg-ctrl каждого сервера (`conn_log` в `users.db`, 90 дней). У WireGuard нет событий подключения, поэтому раз в 30 с он сверяет время handshake и счётчики трафика: «подключился» — время первого handshake, «отключился» — последняя активность (точность около 30 с). Перезапуск awg-ctrl сессии не рвёт, падение интерфейса отключением не считается. Внешний IP клиентов не записывается. Серверы на прежней версии журнала не ведут — вкладка попросит их обновить. ## Мониторинг **Встроенный Prometheus.** Вкладка «Монитор» → «Подключить Prometheus»: панель просит awg-ctrl поставить его (`awg-ctrl monitor install`, только Debian/Ubuntu). Он слушает только `127.0.0.1:9090`, хранит 31 день и забирает метрики **всех серверов** панели. Графики — скорость, устройства онлайн, нагрузка, CPU и память — появляются через минуту-две. Удалить — там же или `awg-ctrl monitor remove`. У панели без своего VPN Prometheus ставится на ноду (выберите её в списке серверов). Он забирает метрики у агента на `127.0.0.1`, а агент — у панели внутри TLS-соединения с хабом; панель читает графики через того же агента. Снаружи этот трафик не отличить от обычной связи ноды с панелью, секретный путь ему не нужен. Агенты старше 0.4.6 этого не умеют. **Свой Prometheus.** Вкладка «Монитор» → «Внешний Prometheus»: выдать токен «только метрики» (он не открывает ни панель, ни API) и скопировать готовый фрагмент `prometheus.yml`. Адрес в нём берётся от страницы панели, поэтому уже содержит секретный путь (`metrics_path: /<путь>/metrics`) или подпапку nginx. По голому HTTP токен и метрики идут открытым текстом — скрейпьте через nginx с TLS или по VPN. Метрики по отдельным пирам выключены (`METRICS_PEERS=on` в `cli.env` — включить). ## Панель за nginx Панель работает и в корне домена, и в любой подпапке — пути в ней относительные. Пример для `https://example.com/forgetting/` (`39708` — `UI_PORT` из `/opt/awg-control/cli/cli.env`): ```nginx location = /forgetting { return 301 /forgetting/; } location ^~ /forgetting/ { proxy_pass http://127.0.0.1:39708/; # слэш в конце срезает префикс proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } ``` nginx на этой же машине ходит к панели по loopback, поэтому секретный путь ему не нужен — подпапка в nginx и становится путём панели (выберите неочевидную). Адрес клиента панель берёт из `X-Forwarded-For` / `X-Real-IP` только от доверенного прокси: с loopback — всегда, с других адресов — если они перечислены в `TRUST_PROXY`. Без этого все входы шли бы с адреса прокси и делили один лимит неудачных попыток. Сам порт панели после этого можно закрыть снаружи (`ufw deny /tcp`). Порты подписки (`SUB_PORT`) и хаба нод (`NODE_PORT`) должны оставаться открытыми: клиенты и ноды ходят на них напрямую, не через nginx. ## API Наружу смотрят две независимые поверхности. **Публичный API** `/api/v1` — стабильный контракт для сторонних программ, авторизация по API-ключу. **Роуты панели** — всё, что умеет веб-интерфейс, авторизация по JWT. В примерах `HOST:PORT` — адрес панели **вместе с секретным путём**: `http://203.0.113.9:39708/k3f9xQ2mTq8vLw0a`. Без пути снаружи любой запрос получает `404`. > [!WARNING] > Роуты панели отдают приватные поля пиров (`vpn_key`, `psk_key`) — это не публичный контракт, > в отличие от `/api/v1`. По голому HTTP они идут открытым текстом: ставьте панель за TLS. ### Публичный API (`/api/v1`) Ключ создаётся во вкладке «API-ключи» и уходит в заголовке `X-Api-Key`. Открытое значение показывается один раз при создании — на сервере лежит только SHA-256 и префикс для показа в списке. Наружу отдаются только публичные поля: `psk_key` и `pub_key` не покидают сервер. ```bash curl -s -X POST http://HOST:PORT/api/v1/users \ -H "X-Api-Key: awgk_xxx" -H "Content-Type: application/json" \ -d '{"name":"alice"}' | jq -r .vpn_key ``` | Метод | Что делает | |---|---| | `POST /api/v1/users` | Создаёт пира. Тело `{ "name": "alice" }`. `201` с `{ name, ip, gen, vpn_key }`; `400` — имя не прошло валидацию, `409` — уже есть | | `GET /api/v1/users` | Список со счётчиками: `{ users: [{ name, ip, key_gen, online, lastHandshake, rx, tx }] }` | | `GET /api/v1/users/:name` | `{ name, ip, gen, vpn_key }`; `404` — не найден | | `DELETE /api/v1/users/:name` | `{ "success": true, "name": "alice" }` | `gen` — поколение AmneziaWG, на параметрах которого собран ключ (`"2"` или `"3.1"`). Операции только CRD: у пира нет изменяемых полей, «перевыпустить» ключ = удалить и создать заново. У ключа — **набор серверов**, которые он видит (панель → «API-ключи» → шестерёнка; `0` — сервер самой панели, остальные — подключённые ноды). Пользователи `vpn://` заводятся на сервере ключа по умолчанию; другой сервер из набора выбирается заголовком `X-Server-Id: `, сервер вне набора → `404`. #### Мастер-ключи `sen://` Мастер-ключ — ссылка `sen://…` для приложения SenAWG с лимитом устройств: каждое устройство само регистрируется по ссылке и получает пира на всех серверах ключа, его приватный ключ сервер не видит. API-ключ видит и меняет мастер-ключ, только если **все** его серверы входят в набор API-ключа; остальные для него не существуют (`404`). ```bash curl -s -X POST http://HOST:PORT/api/v1/masterkeys \ -H "X-Api-Key: awgk_xxx" -H "Content-Type: application/json" \ -d '{"label":"Семья","device_limit":3}' | jq -r .link ``` | Метод | Что делает | |---|---| | `POST /api/v1/masterkeys` | Создаёт ключ. Тело `{ label, device_limit?, servers? }` (лимит 1–100, по умолчанию 3; без `servers` — весь набор API-ключа). `201` с `{ id, uuid, label, device_limit, devices, servers, created_at, link, tls }`; `403` — сервер вне набора, `503` — подписка не настроена (ключ не создаётся) | | `GET /api/v1/masterkeys` | Список: `{ keys: [{ id, uuid, label, device_limit, devices, servers, created_at, deleting? }] }`. `uuid` — постоянный идентификатор ключа: не меняется ни при перевыпуске ссылки, ни при правке метки. Везде, где в пути `:id`, можно передать и `uuid` | | `GET /api/v1/masterkeys/:id` | Ключ, ссылка и устройства одним запросом: те же поля + `link`, `tls`, `device_list` (`devices` — счётчик) | | `PATCH /api/v1/masterkeys/:id` | `{ label?, device_limit?, servers? }` — смена серверов сразу добавляет/снимает пиров устройств | | `POST /api/v1/masterkeys/:id/rotate` | Новая ссылка `{ id, link, tls }`; старая не принимает новые устройства, подключённые работают дальше | | `POST /api/v1/masterkeys/:id/rekey` | Все устройства сменят ключи WireGuard при следующем опросе (IP и PSK сохраняются) | | `DELETE /api/v1/masterkeys/:id` | `{ success, id, pending }` — ключ и устройства отключаются сразу; `pending` — серверы (ноды не в сети), которые ещё не сняли пиров, до тех пор ключ в списке с `deleting` | | `GET /api/v1/masterkeys/:id/devices` | `{ devices: [{ id, device_name, platform, version, online, lastHandshake, rx, tx, last_seen, servers_ok, servers_total, … }] }` — статистика по всем серверам ключа | | `DELETE /api/v1/masterkeys/:id/devices/:device` | Отвязывает устройство, место освобождается | | `POST /api/v1/masterkeys/:id/devices/:device/rekey` | Устройство сменит ключ при следующем опросе | | `POST /api/v1/masterkeys/:id/devices/:device/psk` | Новый PSK: `{ id, pending }` — `pending` серверов ждут ноду | | Код | Когда | |---|---| | `401 {"error":"API key required"}` | заголовка нет или он не начинается с `awgk_` | | `401 {"error":"Invalid API key"}` | ключ не найден — удалён или неверен | | `502 {"error":"awg-ctrl недоступен"}` | awg-ui посредничает, а awg-ctrl не отвечает | Ключи лежат в собственной БД панели (`/etc/amnezia/amneziawg/ui.db`), поэтому переустановка их не трогает. Сама awg-ctrl про внешние ключи ничего не знает: awg-ui ходит к ней своей внутренней авторизацией. Управлять ключами можно и без панели, по JWT: `GET/POST /ui/apikeys` (с полем `servers`), `PATCH /ui/apikeys/:id` `{ "servers": [0, 2] }` — сменить набор, не перевыпуская ключ, и `DELETE /ui/apikeys/:id`. ### Авторизация панели ```bash TOKEN=$(curl -s -X POST http://HOST:PORT/login \ -H "Content-Type: application/json" \ -d '{"user":"admin","pass":"***"}' | jq -r .token) ``` | Метод | Ответ и ошибки | |---|---| | `POST /login` | `{ "token": "" }`, срок 24 часа. `401` — неверная пара; `429` — больше 5 неудачных попыток с одного IP за 15 минут | | `POST /logout` | `{ "ok": true }`, токен отзывается немедленно | Дальше в каждый запрос: `Authorization: Bearer $TOKEN`; сервер — заголовком `X-Server-Id` (без него — сервер по умолчанию). Ниже — роуты, которые дёргает сама панель; в отличие от `/api/v1` их форма может меняться. ### Состояние | Метод | Ответ | |---|---| | `GET /ui/brand` | `{ "brand": "Forgetting", "channel": "Beta", "version": "0.4.7" }` | | `GET /health` | `{ "status", "server", "ip", "gen", "awg": { "status", "peers", "module", "tools" } }`. `200`, если интерфейс поднят, иначе `503` и `status: "degraded"` | | `GET /awg/status` | `{ "up": true, "peers": 3, "publicKey": "…" }` | `gen` — поколение AmneziaWG, на параметрах которого сейчас работает интерфейс: `"2"` или `"3.1"`. Оно выводится из `awg1.conf`, а не хранится отдельно. ### Пользователи | Метод | Что делает | |---|---| | `POST /api/users` | Создаёт пира. Тело `{ "name": "alice" }`, имя — `a–z A–Z 0–9 _ -`, до 32 символов. `201` с `{ name, ip, pub_key, psk_key, vpn_key, key_gen, vpn_key_prev }`; `400` — имя не прошло валидацию, `409` — уже есть | | `GET /api/users` | Список: `{ users: [{ name, ip, pub_key, vpn_key, key_gen, online, lastHandshake }] }` | | `GET /api/users/stats` | То же без ключей, но со счётчиками: `{ users: [{ name, ip, key_gen, online, lastHandshake, rx, tx }] }` | | `POST /api/users/:name` | Возвращает пользователя целиком, включая `vpn_key`. Метод именно `POST`, чтобы ключ не оседал в логах и истории как параметр `GET`. `404` — не найден | | `DELETE /api/users/:name` | `{ "success": true, "name": "alice" }` | | `POST /api/users/reissue` | Пересобирает `vpn://` всем на текущих параметрах: `{ total, reissued, regenerated: [], backup }`. IP и ключевая пара сохраняются, клиентам нужно заново импортировать ключ; `backup` — путь к снимку базы, снятому перед проходом | ```bash curl -s -X POST http://HOST:PORT/api/users \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{"name":"alice"}' | jq -r .vpn_key ``` Имея `vpn_key`, конфиг `.conf` можно получить локально: убрать префикс `vpn://`, base64url-декодировать, отбросить первые 4 байта (длина), распаковать `zlib inflate` → JSON; текст конфига лежит в `last_config.config`. ### Журнал подключений | Метод | Ответ | |---|---| | `GET /ui/connlog` | Журнал сервера из `X-Server-Id`: `{ events: [{ id, at, event, name, owner, ip, key?, device?, duration?, rx?, tx? }], peers: [{ name, owner, ip, online, last_seen, key?, device? }], since, keep_days }`. `event` — `connect` или `disconnect` (у него `duration`, `rx`, `tx` — со стороны сервера); `key`/`device` — мастер-ключ и устройство `sen://`. Параметры: `limit` (до 500), `before=` — следующая страница, `q` — поиск по имени, владельцу, IP. `501` — сервер старой версии | Без имён `sen://` тот же журнал отдаёт awg-ctrl: `GET /api/connlog`. ### Интерфейс AWG | Метод | Что делает | |---|---| | `POST /awg/start` | Поднимает интерфейс, если он лежит; возвращает его статус | | `POST /awg/restart` | `awg-quick down/up` + ресинк пиров, `{ "success": true }`. Соединения клиентов кратковременно рвутся | | `POST /awg/upgrade` | Переводит сервер с 2.0 на 3.1: правит `awg1.conf`, перезапускает интерфейс и перевыпускает все ключи. `{ gen, backup, bumpedS: [], reissue: { total, reissued, … } }` | `POST /awg/upgrade` отвечает `409`, если сервер уже на 3.1, модуль в ядре не 3.x или ядро старше 5.5, и `500` с текстом причины, если ядро не приняло 3.1-конфиг — в этом случае прежний конфиг возвращается из копии, а интерфейс поднимается обратно на 2.0. ### Мастер-ключи Те же операции, что в `/api/v1/masterkeys`, но без ограничения набором серверов. | Метод | Что делает | |---|---| | `GET /ui/masterkeys` | `{ enabled, tls, keys: [{ id, uuid, label, device_limit, devices, servers, created_at, deleting? }], defaults }`; `:id` в путях — id или `uuid`; `defaults` — серверы новых ключей или `null` | | `POST /ui/masterkeys` | `{ label, device_limit?, servers? }` → `201`; без `servers` — `defaults`, а нет их — сервер по умолчанию | | `POST /ui/masterkeys/servers` | «Серверы для всех»: `{ add?, remove?, defaults?, dry_run? }` — `add` появляются у всех живых ключей, `remove` снимаются у всех, прочие серверы ключей не трогаются; `defaults` (`null` — сбросить) — набор новых ключей. → `{ changed, total, hidden: [{ id, label, keys }] }`, `hidden` — ключи API, которые перестанут видеть столько мастер-ключей (сервер вне их области). `409`, если у ключа не останется серверов | | `PATCH /ui/masterkeys/:id` | `{ label?, device_limit?, servers? }` | | `GET /ui/masterkeys/:id/link` | `{ link, tls }` — ссылка `sen://…` | | `GET /ui/masterkeys/:id/devices` | Устройства со статусом и трафиком | | `POST /ui/masterkeys/:id/rotate`, `/rekey` | Новая ссылка / смена ключей у всех устройств | | `DELETE /ui/masterkeys/:id` | `{ success, id, pending }` | | `DELETE /ui/devices/:id` | Отвязать устройство | | `POST /ui/devices/:id/rekey`, `/psk` | Смена ключа / PSK у устройства | ### Метрики | Метод | Что делает | |---|---| | `GET /ui/metrics-token` | `{ exists, prefix?, created_at?, last_used?, per_peer }` — токен для своего Prometheus | | `POST /ui/metrics-token` | Выпустить или перевыпустить: `201` с `token` (показывается один раз); старый перестаёт работать | | `DELETE /ui/metrics-token` | Отозвать | | `GET /metrics` | Экспозиция Prometheus по всем серверам; `Authorization: Bearer awgm_…` | ## Управление ```bash awg-ctrl # меню awg-ctrl start|stop|restart|status # status показывает и адрес панели с путём awg-ctrl credentials [user|pass] # логин и пароль панели, адрес awg-ctrl join '' [--no-panel] / unjoin awg-ctrl rehome <хост[:порт]> # нода: панель переехала на новый адрес awg-ctrl peers [drop <область>] # пиры sen:// на этом сервере по панелям awg-ctrl monitor install|status|remove # встроенный Prometheus awg-ctrl migrate export [файл] # перенос панели (см. «Несколько серверов») awg-ctrl migrate import <архив> awg-ctrl migrate cancel awg-ctrl update | reinstall | uninstall # обновить, переустановить, удалить (через install.sh) awg-ctrl panel [path new|off|<путь>] # адрес панели; новый, свой или без секретного пути awg-ctrl log [<поиск>] [-n N] # журнал VPN-подключений этого сервера awg-ctrl log seen # кто когда был: онлайн, последний раз, не подключался awg-ctrl metrics [token|revoke] # токен и prometheus.yml для своего Prometheus awg-ctrl hub [legacy on|off|auto] # ноды хаба: кто с подписью, кто ещё старый awg-ctrl check # что на сервере выдаёт его сканеру и DPI ``` В меню `awg-ctrl` те же возможности — пункты 10–13: адрес и секретный путь панели, журнал, «кто когда был» и проверка скрытности. `update` и `reinstall` скачивают свежий установщик (`INSTALLER_URL` в `cli.env` — своё зеркало), `uninstall` берёт его копию из установки и работает без сети. Через systemd (автозапуск на загрузке): ```bash systemctl start|stop|restart awg-control ``` ### Настройки `cli.env` `/opt/awg-control/cli/cli.env` пишет установщик; после правки — `awg-ctrl restart`. Чаще всего нужны: | Переменная | Что делает | |---|---| | `UI_PORT`, `UI_USER`, `UI_PASS` | порт, логин и пароль панели (`awg-ctrl credentials`) | | `UI_PREFIX` | секретный путь панели; пусто — без пути | | `UI_PREFIX_MOVED` | путь панели, переехавшей сюда: принимается вместе со своим | | `SUB_PORT`, `SUB_TLS`, `SUB_HOST` | подписка `sen://`: порт, HTTPS, адрес в ссылках | | `NODE_PORT` | хаб нод; пусто — ноды не принимаются | | `HUB_LEGACY` | `on` / `off` — пускать ли агентов нод без подписи; пусто — пока не обновлены все | | `TRUST_PROXY` | IP или сети reverse-proxy, кроме loopback: от них верим `X-Forwarded-For` | | `METRICS_PEERS` | `on` — метрики по отдельным пирам | | `AUDIT` | `off` — выключить периодический аудит пиров | | `INSTALLER_URL` | откуда `awg-ctrl update`/`reinstall` берут установщик (своё зеркало) | ## Лицензия [MIT](LICENSE) © 2026 Ivan Vasilev