Covers all six places the version is written down: .env, the copy install.sh keeps because it prints its banner and builds the release URL before the archive with .env exists, the three package.json files, and the /ui/brand example in the README. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11 KiB
Forgetting
Панель управления и API для VPN на базе AmneziaWG
Warning
Данный проект предназначен только для использования в законных целях.
Архитектура
Три компонента, устанавливаются в /opt/awg-control/:
| Компонент | Роль |
|---|---|
| awg-ctrl | Привилегированный REST-бэкенд. Управляет пирами через awg/awg-quick, ведёт SQLite-базу, генерирует vpn:// ключи. Слушает только loopback. |
| awg-ui | Express + React. Веб-панель, аутентификация (JWT), прокси к awg-ctrl, публичный API /api/v1 и своя база его ключей. Единственный сервис, доступный по сети. |
| cli | Менеджер процессов: start/stop/restart/status, смена логина/пароля панели, интерактивное TUI-меню. |
Внутренняя связь awg-ui → awg-ctrl подписывается асимметрично (Ed25519): приватный ключ только у awg-ui, awg-ctrl проверяет подпись публичным.
Требования
- ОС: 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-модуля).
- Node.js 20.x (ставится установщиком автоматически).
Совместимость установщик проверяет сам перед любыми изменениями.
Установка
На сервере, от root:
bash <(curl -Ls https://git.ma7neko.ru/maeneko/forgetting/raw/branch/main/install.sh)
Установщик по умолчанию ставит сервер на AmneziaWG 3.1. Если модуль в ядре старше 3.x или ядро старше 5.5, установка остановится с диагностикой — поставить на 2.0 можно так:
AWG_GEN=auto bash <(curl -Ls https://git.ma7neko.ru/maeneko/forgetting/raw/branch/main/install.sh)
Перевести такой сервер на 3.1 позже можно кнопкой «Перейти на AWG 3.1» в панели — переустановка не нужна, пользователи сохраняются, ключи перевыпускаются автоматически.
Источник архива и имя панели не зашиты в код — задаются переменными
REPO_BASE, REPO_FALLBACK, ARCHIVE_URL, BRAND перед запуском установщика
(подробности — в комментариях install.sh и в .env).
API
Наружу смотрят две независимые поверхности. Публичный API /api/v1 —
стабильный контракт для сторонних программ, авторизация по API-ключу.
Роуты панели — всё, что умеет веб-интерфейс, авторизация по JWT. Базовый
URL в обоих случаях — адрес сервера и порт, выбранный при установке.
Warning
Роуты панели отдают приватные поля пиров (
vpn_key,psk_key) — это не публичный контракт, в отличие от/api/v1. Не выставляй её порт в интернет без TLS и ограничения по адресам.
Публичный API (/api/v1)
Ключ создаётся во вкладке «API-ключи» и уходит в заголовке X-Api-Key.
Открытое значение показывается один раз при создании — на сервере лежит только
SHA-256 и префикс для показа в списке. Наружу отдаются только публичные поля:
psk_key и pub_key не покидают сервер.
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: у пира нет изменяемых полей, «перевыпустить»
ключ = удалить и создать заново.
| Код | Когда |
|---|---|
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 и
DELETE /ui/apikeys/:id.
Авторизация панели
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. Ниже — роуты, которые
дёргает сама панель; в отличие от /api/v1 их форма может меняться.
Состояние
| Метод | Ответ |
|---|---|
GET /ui/brand |
{ "brand": "Forgetting", "channel": "Beta", "version": "0.2.2" } — без авторизации, панель берёт отсюда вордмарк на экране логина |
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 — путь к снимку базы, снятому перед проходом |
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.
Интерфейс 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.
Управление
awg-ctrl
Через systemd (автозапуск на загрузке):
systemctl start|stop|restart awg-control
Лицензия
MIT © 2026 Ivan Vasilev