508 lines
42 KiB
Markdown
508 lines
42 KiB
Markdown
# 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 '<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/; }
|
||
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 <UI_PORT>/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: <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": "<JWT>" }`, срок 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.5.1" }` — имя и версия панели для вордмарка; за авторизацией, чтобы сканер не опознал панель |
|
||
| `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=<id>` — следующая страница, `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 '<awgjoin://…>' [--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
|