Merge origin/main into the 0.5.0 redesign
ci / check (push) Failing after 6m27s

Brings in 0.4.8: connection log, uninstall and update from the awg-ctrl menu,
secret panel path, stealth hub and sen:// port, random I1/MTU on install.
install.sh keeps the new output (spinner, cards, outdated-install wipe) on top
of the reorganised installer; an outdated install can be reinstalled with a
wipe or removed. The connection log tab uses the MD3 segmented button; its
metrics-token scrape snippet moved to Settings.
This commit is contained in:
2026-10-29 23:07:03 +03:00
40 changed files with 3639 additions and 780 deletions
+312 -73
View File
@@ -1,7 +1,30 @@
# Forgetting
Панель управления и API для VPN на базе [AmneziaWG](https://github.com/amnezia-vpn/amneziawg-linux-kernel-module)
Панель управления и 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/` — в нём все роли;
@@ -9,17 +32,34 @@
| Компонент | Роль |
|---|---|
| **awg-ctrl** (`awg-control ctrl`) | Привилегированный REST-бэкенд. Управляет пирами через `awg`/`awg-quick`, ведёт SQLite-базу, генерирует `vpn://` ключи. Слушает только loopback. |
| **awg-ui** (`awg-control ui`) | Веб-панель (фронтенд на React, `awg-ui/dist`), аутентификация (JWT), прокси к awg-ctrl, публичный API `/api/v1` и своя база его ключей. Единственный сервис, доступный по сети. |
| **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 …`) | Менеджер процессов: `start`/`stop`/`restart`/`status`, смена логина/пароля панели, интерактивное TUI-меню. |
| **cli** (`awg-ctrl …`) | Менеджер процессов, логин и пароль панели, привязка нод, перенос панели, мониторинг, TUI-меню. |
Внутренняя связь awg-ui → awg-ctrl подписывается асимметрично (Ed25519): приватный ключ только у awg-ui, awg-ctrl проверяет подпись публичным.
Внутренняя связь 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`)
- **ОС**: 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).
- **Виртуализация**: KVM или железо (не OpenVZ/LXC/Docker/WSL — нужен загружаемый
kernel-модуль AmneziaWG).
- **Заголовки ядра** под текущее ядро (для сборки DKMS-модуля).
- **Архитектура**: x86_64 или aarch64.
@@ -34,34 +74,186 @@ bash <(curl -Ls https://raw.githubusercontent.com/maeneko/forgetting/main/instal
```
Не от root — через `sudo bash -c`: `sudo bash <(curl …)` падает с
`/dev/fd/63: No such file or directory`, потому что `sudo` закрывает дескриптор
с текстом скрипта.
`/dev/fd/63: No such file or directory`, потому что `sudo` закрывает дескриптор с текстом скрипта.
```bash
sudo bash -c "$(curl -Ls https://raw.githubusercontent.com/maeneko/forgetting/main/install.sh)"
```
Установщик по умолчанию ставит сервер на AmneziaWG 3.1. Если модуль в ядре
старше 3.x или ядро старше 5.5, установка остановится с диагностикой — поставить
на 2.0 можно так:
Установщик спросит роль сервера:
```bash
AWG_GEN=auto bash <(curl -Ls https://raw.githubusercontent.com/maeneko/forgetting/main/install.sh)
```
| Роль | Что ставится |
|---|---|
| **Панель + VPN** (`standalone`) | Всё на одной машине; к ней можно подключать ноды |
| **Только панель** (`core`) | Панель без VPN; серверы подключаются к ней нодами |
| **Нода** (`node`) | VPN без панели; подключается к панели по строке `awgjoin://…` («Добавить сервер» в панели) |
Перевести такой сервер на 3.1 позже можно кнопкой «Перейти на AWG 3.1» в панели —
переустановка не нужна, пользователи сохраняются, ключи перевыпускаются
автоматически.
В конце установщик печатает адрес панели **вместе с секретным путём** —
`http://IP:PORT/<путь>/` — логин и пароль. Без пути панель отвечает `404`, поэтому адрес
сохраните целиком; позже его покажут `awg-ctrl status` и `awg-ctrl credentials`.
Источник архива и имя панели не зашиты в код — задаются переменными
`REPO_BASE`, `REPO_FALLBACK`, `ARCHIVE_URL`, `BRAND` перед запуском установщика
(подробности — в комментариях `install.sh` и в `.env`).
Порты на самом сервере установщик открывает сам (UFW, firewalld или iptables — что
активно). **Фаервол хостера** (панель провайдера, security group) отсюда не виден: открой там
порты из итога установки, иначе клиенты не подключатся, а на сервере всё будет выглядеть
исправным.
### Панель за nginx
Сервер ставится на AmneziaWG 3.1. Если модуль в ядре старше 3.x или ядро старше 5.5,
установка остановится с диагностикой — поставить на 2.0 можно через `AWG_GEN=auto`. Перевести
сервер на 3.1 позже можно кнопкой «Перейти на AWG 3.1» в панели: пользователи сохраняются,
ключи перевыпускаются автоматически.
Панель работает и в корне домена, и в любой подпапке — пути в ней относительные,
настраивать сам префикс не нужно. Пример для `https://example.com/forgetting/`
(`39708` — `PORT` панели из `/opt/awg-control/cli/cli.env`):
### Параметры установщика
Задаются переменными окружения перед запуском, например
`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 '<awgjoin://…>'`. Агент ноды сам подключается к хабу панели по 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/; }
@@ -74,28 +266,28 @@ location ^~ /forgetting/ {
}
```
Адрес клиента панель берёт из `X-Forwarded-For` / `X-Real-IP` только от доверенного
прокси: с этой же машины (loopback) — всегда, с других адресов — если они перечислены
в `TRUST_PROXY` в `cli.env` (IP или сети через запятую, например
`TRUST_PROXY=10.0.0.5,192.168.1.0/24`), после чего `awg-ctrl restart`. От остальных
заголовки игнорируются. Без этого все входы шли бы с адреса прокси и делили один
лимит неудачных попыток.
nginx на этой же машине ходит к панели по loopback, поэтому секретный путь ему не нужен —
подпапка в nginx и становится путём панели (выберите неочевидную). Адрес клиента панель берёт
из `X-Forwarded-For` / `X-Real-IP` только от доверенного прокси: с loopback — всегда, с других
адресов — если они перечислены в `TRUST_PROXY`. Без этого все входы шли бы с адреса прокси и
делили один лимит неудачных попыток.
Сам порт панели после этого можно закрыть снаружи (`ufw deny <PORT>/tcp`) — порты
подписки (`SUB_PORT`) и хаба нод (`NODE_PORT`) должны оставаться открытыми: клиенты
и ноды ходят на них напрямую, не через nginx.
Сам порт панели после этого можно закрыть снаружи (`ufw deny <UI_PORT>/tcp`). Порты подписки
(`SUB_PORT`) и хаба нод (`NODE_PORT`) должны оставаться открытыми: клиенты и ноды ходят на них
напрямую, не через nginx.
## API
Наружу смотрят две независимые поверхности. **Публичный API** `/api/v1` —
стабильный контракт для сторонних программ, авторизация по API-ключу.
**Роуты панели** — всё, что умеет веб-интерфейс, авторизация по JWT. Базовый
URL в обоих случаях — адрес сервера и порт, выбранный при установке.
Наружу смотрят две независимые поверхности. **Публичный API** `/api/v1` — стабильный контракт
для сторонних программ, авторизация по API-ключу. **Роуты панели** — всё, что умеет
веб-интерфейс, авторизация по JWT.
В примерах `HOST:PORT` — адрес панели **вместе с секретным путём**:
`http://203.0.113.9:39708/k3f9xQ2mTq8vLw0a`. Без пути снаружи любой запрос получает `404`.
> [!WARNING]
> Роуты панели отдают приватные поля пиров (`vpn_key`, `psk_key`) — это не
> публичный контракт, в отличие от `/api/v1`. Не выставляй её порт в интернет
> без TLS и ограничения по адресам.
> Роуты панели отдают приватные поля пиров (`vpn_key`, `psk_key`) — это не публичный контракт,
> в отличие от `/api/v1`. По голому HTTP они идут открытым текстом: ставьте панель за TLS.
### Публичный API (`/api/v1`)
@@ -117,22 +309,20 @@ curl -s -X POST http://HOST:PORT/api/v1/users \
| `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: у пира нет изменяемых полей, «перевыпустить»
ключ = удалить и создать заново.
`gen` — поколение AmneziaWG, на параметрах которого собран ключ (`"2"` или `"3.1"`). Операции
только CRD: у пира нет изменяемых полей, «перевыпустить» ключ = удалить и создать заново.
У ключа — **набор серверов**, которые он видит (панель → «API-ключи» → шестерёнка;
`0` — сервер самой панели, остальные — подключённые ноды). Пользователи `vpn://`
заводятся на сервере ключа по умолчанию; другой сервер из набора выбирается
заголовком `X-Server-Id: <id>`, сервер вне набора → `404`.
У ключа — **набор серверов**, которые он видит (панель → «API-ключи» → шестерёнка; `0` — сервер
самой панели, остальные — подключённые ноды). Пользователи `vpn://` заводятся на сервере ключа
по умолчанию; другой сервер из набора выбирается заголовком `X-Server-Id: <id>`, сервер вне
набора → `404`.
#### Мастер-ключи `sen://`
Мастер-ключ — ссылка `sen://…` для приложения SenAWG с лимитом устройств: каждое
устройство само регистрируется по ссылке и получает пира на всех серверах ключа,
его приватный ключ сервер не видит. API-ключ видит и меняет мастер-ключ, только
если **все** его серверы входят в набор API-ключа; остальные для него не
существуют (`404`).
Мастер-ключ — ссылка `sen://…` для приложения SenAWG с лимитом устройств: каждое устройство
само регистрируется по ссылке и получает пира на всех серверах ключа, его приватный ключ сервер
не видит. API-ключ видит и меняет мастер-ключ, только если **все** его серверы входят в набор
API-ключа; остальные для него не существуют (`404`).
```bash
curl -s -X POST http://HOST:PORT/api/v1/masterkeys \
@@ -160,12 +350,11 @@ curl -s -X POST http://HOST:PORT/api/v1/masterkeys \
| `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`. Подробно — `awg-ui/API.md`.
Ключи лежат в собственной БД панели (`/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`.
### Авторизация панели
@@ -180,8 +369,9 @@ TOKEN=$(curl -s -X POST http://HOST:PORT/login \
| `POST /login` | `{ "token": "<JWT>" }`, срок 24 часа. `401` — неверная пара; `429` — больше 5 неудачных попыток с одного IP за 15 минут |
| `POST /logout` | `{ "ok": true }`, токен отзывается немедленно |
Дальше в каждый запрос: `Authorization: Bearer $TOKEN`. Ниже — роуты, которые
дёргает сама панель; в отличие от `/api/v1` их форма может меняться.
Дальше в каждый запрос: `Authorization: Bearer $TOKEN`; сервер — заголовком `X-Server-Id`
(без него — сервер по умолчанию). Ниже — роуты, которые дёргает сама панель; в отличие от
`/api/v1` их форма может меняться.
### Состояние
@@ -191,8 +381,8 @@ TOKEN=$(curl -s -X POST http://HOST:PORT/login \
| `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`, а не хранится отдельно.
`gen` — поколение AmneziaWG, на параметрах которого сейчас работает интерфейс: `"2"` или
`"3.1"`. Оно выводится из `awg1.conf`, а не хранится отдельно.
### Пользователи
@@ -212,8 +402,16 @@ curl -s -X POST http://HOST:PORT/api/users \
```
Имея `vpn_key`, конфиг `.conf` можно получить локально: убрать префикс `vpn://`,
base64url-декодировать, отбросить первые 4 байта (длина), распаковать
`zlib inflate` → JSON; текст конфига лежит в `last_config.config`.
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=<id>` — следующая страница, `q` — поиск по имени, владельцу, IP. `501` — сервер старой версии |
Без имён `sen://` тот же журнал отдаёт awg-ctrl: `GET /api/connlog`.
### Интерфейс AWG
@@ -223,10 +421,9 @@ base64url-декодировать, отбросить первые 4 байта
| `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.
`POST /awg/upgrade` отвечает `409`, если сервер уже на 3.1, модуль в ядре не 3.x или ядро
старше 5.5, и `500` с текстом причины, если ядро не приняло 3.1-конфиг — в этом случае прежний
конфиг возвращается из копии, а интерфейс поднимается обратно на 2.0.
### Мастер-ключи
@@ -245,24 +442,66 @@ base64url-декодировать, отбросить первые 4 байта
| `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
awg-ctrl credentials [user|pass]
awg-ctrl start|stop|restart|status # status показывает и адрес панели с путём
awg-ctrl credentials [user|pass] # логин и пароль панели, адрес
awg-ctrl join '<awgjoin://…>' [--no-panel] / unjoin
awg-ctrl rehome <хост[:порт]> # нода: панель переехала на новый адрес
awg-ctrl migrate export [файл] # перенос панели на отдельный сервер без VPN
awg-ctrl migrate import <архив> # (порядок — docs/node-protocol.md, «Перенос панели»)
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