diff --git a/README.md b/README.md index af93a1d..93e8abe 100644 --- a/README.md +++ b/README.md @@ -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,103 +74,180 @@ 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) отсюда не виден: открой там +порты из итога установки, иначе клиенты не подключатся, а на сервере всё будет выглядеть +исправным. -### Удаление +Сервер ставится на AmneziaWG 3.1. Если модуль в ядре старше 3.x или ядро старше 5.5, +установка остановится с диагностикой — поставить на 2.0 можно через `AWG_GEN=auto`. Перевести +сервер на 3.1 позже можно кнопкой «Перейти на AWG 3.1» в панели: пользователи сохраняются, +ключи перевыпускаются автоматически. -Тот же установщик: на сервере с установкой — пункт **3 «Удалить»**, без меню — -`UNINSTALL=y` (подтверждение «удалить» тогда не спрашивается): +### Параметры установщика -```bash -UNINSTALL=y bash <(curl -Ls https://raw.githubusercontent.com/maeneko/forgetting/main/install.sh) -``` +Задаются переменными окружения перед запуском, например +`ROLE=node JOIN='awgjoin://…' bash <(curl -Ls …/install.sh)`. Всё необязательно. -Удаляются сервисы и юнит systemd, интерфейс `awg1`, nft-таблицы шейпинга и блокировки -торрентов, открытые установщиком порты (UFW, firewalld или iptables), Prometheus из -`awg-ctrl monitor install`, `/opt/awg-control`, `/etc/amnezia` (пользователи, ключи, базы), -команда `awg-ctrl`, sysctl-файл и логи установки (в них пароль панели). Перед этим установщик -предлагает сохранить копию данных в `/root/forgetting-backup-<время>.tar.gz`. AmneziaWG -(пакеты, модуль, PPA) — по отдельному вопросу; общие зависимости (dkms, build-essential, -iptables, заголовки ядра) остаются. Ноду после удаления уберите и из списка серверов панели. +| Переменная | Что делает | +|---|---| +| `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_PREFIX` | свой секретный путь панели (8–64 символа `A–Z a–z 0–9 _ -`); `off` — без пути | +| `SUB_PORT`, `SUB_TLS`, `NODE_PORT` | порт и HTTPS подписки, порт хаба нод — без вопросов | +| `UNINSTALL` | `y` — удалить, см. ниже | +| `AMNEZIA_PPA_SUITE` | серия PPA для Debian (`focal`/`jammy`/`noble`) | +| `REPO_BASE`, `REPO_FALLBACK`, `ARCHIVE_URL`, `AWG_LOCAL_TARBALL` | откуда брать архив релиза (своё зеркало, локальный файл) | +| `BRAND`, `CHANNEL` | имя продукта в баннерах и панели | -### Секретный путь панели +### Обновление и удаление -Установщик выдаёт панели случайный путь: она открывается по `http://IP:PORT/<путь>/`, а на -любой другой адрес отвечает `404 page not found`, как пустой веб-сервер. Порт панели открыт -в интернет, и без пути сканер узнавал её по странице логина. Путь хранится в `UI_PREFIX` -в `/opt/awg-control/cli/cli.env`; полный адрес показывают `awg-ctrl status` и -`awg-ctrl credentials`. Внешнему API нужен тот же путь: `http://HOST:PORT/<путь>/api/v1/…` -(в примерах ниже `HOST:PORT` — вместе с путём). +Повторный запуск установщика на сервере с установкой предлагает: -- При переустановке с сохранением данных путь остаётся прежним. Свой путь — - `UI_PREFIX=<8–64 символа A–Z a–z 0–9 _ ->`, без пути — `UI_PREFIX=off` перед запуском - установщика (или правка `cli.env` и `awg-ctrl restart`). -- С этой же машины (loopback) путь не нужен: CLI, локальный Prometheus и nginx ниже - ходят как раньше. -- При переносе панели (вкладка «Перенос», `awg-ctrl migrate`) новая панель принимает и свой - путь, и путь старой (`UI_PREFIX_MOVED`) — закладки и API через ретранслятор продолжают - работать. Пока выдан код переноса, `POST /migrate/v1/import` принимается и без пути. +1. **Обновить** — новая версия, настройки, пользователи и роль сохраняются. `cli.env` не + меняется, поэтому секретного пути у установки, где его не было, не появится — включить: + `UI_PREFIX=<путь>` в `cli.env` и `awg-ctrl restart` (или переустановка с сохранением данных, + она выдаст путь сама). +2. **Переустановить** — с нуля; пользователей и ключ сервера можно сохранить. Тогда сохраняются + и порт, параметры обфускации, MTU и секретный путь — они зашиты в выданные ключи и ссылки. +3. **Удалить** — полностью (без меню: `UNINSTALL=y`, подтверждение «удалить» тогда не + спрашивается). -### Порт подписки sen:// +Удаление снимает сервисы и юнит 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, заголовки ядра) остаются. Ноду +после удаления уберите и из списка серверов панели. -Подписка отвечает по протоколу только тем, кто доказал знание секрета: `peek`/`register` — -с секретом мастер-ключа из ссылки, остальные запросы — с подписью ключом устройства. На всё -прочее (другие пути и методы, битое тело, чужой секрет, подобранный id устройства, неверная -подпись) — `404 page not found`, как пустой веб-сервер. Удалённому устройству `410` уходит, -только если запрос подписан его ключом (они хранятся в `revoked_devices`); устройства, -удалённые до этой версии, получают тот же тихий 404. Самоподписанные сертификаты подписки -(`SUB_TLS=on`) и хаба нод создаются со случайным именем вместо общих `sen` / `awg-node`: -клиенты и ноды сверяют ключ, а не имя, так что прежние сертификаты продолжают работать. +## Несколько серверов -### Порт хаба нод +**Подключить сервер.** В панели «Добавить сервер» выдаёт строку `awgjoin://…` (действует час). +На новом сервере — установщик с ролью «Нода» и этой строкой, или на уже установленном — +`awg-ctrl join ''`. Агент ноды сам подключается к хабу панели по WSS; доверие — +закреплённый отпечаток сертификата хаба и ключ ноды. В панели нода появляется в списке +серверов, ею управляют так же, как своим. `awg-ctrl unjoin` — отвязать. -Агент ноды подписывает своим ключом уже сам запрос на WebSocket (заголовки `X-Node-Id`, -`X-Node-Ts`, `X-Node-Sig`, при первой привязке — ещё секрет из строки `awgjoin://`). Хаб -проверяет подпись до апгрейда и без неё отвечает `404 page not found`, как пустой сервер: -одним подключением к `/node/v1` его больше не узнать. Агенты прежних версий подписи не шлют — -хаб пускает их, пока в панели есть нода, ни разу не подключавшаяся по-новому, и пишет об этом -в лог. Обновили все ноды — путь без подписи закрывается сам. `HUB_LEGACY=on` / `off` в -`cli.env` (и `awg-ctrl restart`) — принудительно. Часы ноды могут расходиться с панелью до -суток. +**Перенести панель на отдельный сервер** (например, без VPN). Вкладка «Перенос» или CLI: -### Внешний Prometheus +1. На новом сервере — установка с ролью «Только панель». +2. На новой панели — код переноса `awgmove://…`; на старой — вставить его (или + `awg-ctrl migrate export` на старом и `awg-ctrl migrate import <архив>` на новом). +3. Старый сервер становится нодой #0 новой панели и пересылает туда свои прежние порты: + ссылки `sen://`, ноды и внешний API продолжают работать, закладки панели — тоже (новая + панель принимает и свой секретный путь, и старый). +4. Когда вкладка «Перенос» покажет, что новый адрес знают все устройства и ноды, старый + сервер можно выключать. Ноды, переехавшие вручную, — `awg-ctrl rehome <хост[:порт]>`. -Prometheus на ноде («Подключить Prometheus» у панели без своего VPN) от секретного пути не -зависит: он забирает метрики у агента на `127.0.0.1`, а тот — у панели внутри TLS-соединения -с хабом. Снаружи этот трафик не отличить от обычной связи ноды с панелью. +`awg-ctrl migrate cancel` — отменить на старом сервере, пока перенос не завершён. -Для стороннего сервера мониторинга — вкладка «Монитор» → «Внешний Prometheus»: выдать токен «только метрики» и скопировать готовый -фрагмент `prometheus.yml`. Адрес в нём берётся от страницы панели, поэтому уже содержит -секретный путь (`metrics_path: /<путь>/metrics`) или подпапку nginx. Без пути `/metrics` -снаружи отвечает 404. Панель по голому HTTP передаёт токен и метрики открытым текстом — -внешний скрейп лучше вести через nginx с TLS или по VPN. +## Защита от обнаружения -### Панель за nginx +Раньше у всех установок были одинаковые порт, параметры обфускации и ответы на запросы — одно +правило DPI находило их все. Теперь общего у серверов нет: -Панель работает и в корне домена, и в любой подпапке — пути в ней относительные, -настраивать сам префикс не нужно. Пример для `https://example.com/forgetting/` -(`39708` — `PORT` панели из `/opt/awg-control/cli/cli.env`): +**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/; } @@ -143,35 +260,34 @@ 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 /tcp`) — порты -подписки (`SUB_PORT`) и хаба нод (`NODE_PORT`) должны оставаться открытыми: клиенты -и ноды ходят на них напрямую, не через nginx. +Сам порт панели после этого можно закрыть снаружи (`ufw deny /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`) -Ключ создаётся во вкладке «API-ключи» и уходит в заголовке `X-Api-Key`. -Открытое значение показывается один раз при создании — на сервере лежит только -SHA-256 и префикс для показа в списке. Наружу отдаются только публичные поля: -`psk_key` и `pub_key` не покидают сервер. +Ключ создаётся во вкладке «API-ключи» и уходит в заголовке `X-Api-Key`. Открытое значение +показывается один раз при создании — на сервере лежит только SHA-256 и префикс для показа в +списке. Наружу отдаются только публичные поля: `psk_key` и `pub_key` не покидают сервер. ```bash curl -s -X POST http://HOST:PORT/api/v1/users \ @@ -186,22 +302,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: `, сервер вне набора → `404`. +У ключа — **набор серверов**, которые он видит (панель → «API-ключи» → шестерёнка; `0` — сервер +самой панели, остальные — подключённые ноды). Пользователи `vpn://` заводятся на сервере ключа +по умолчанию; другой сервер из набора выбирается заголовком `X-Server-Id: `, сервер вне +набора → `404`. #### Мастер-ключи `sen://` -Мастер-ключ — ссылка `sen://…` для приложения SenAWG с лимитом устройств: каждое -устройство само регистрируется по ссылке и получает пира на всех серверах ключа, -его приватный ключ сервер не видит. API-ключ видит и меняет мастер-ключ, только -если **все** его серверы входят в набор API-ключа; остальные для него не -существуют (`404`). +Мастер-ключ — ссылка `sen://…` для приложения SenAWG с лимитом устройств: каждое устройство +само регистрируется по ссылке и получает пира на всех серверах ключа, его приватный ключ сервер +не видит. API-ключ видит и меняет мастер-ключ, только если **все** его серверы входят в набор +API-ключа; остальные для него не существуют (`404`). ```bash curl -s -X POST http://HOST:PORT/api/v1/masterkeys \ @@ -229,12 +343,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`. ### Авторизация панели @@ -249,19 +362,20 @@ TOKEN=$(curl -s -X POST http://HOST:PORT/login \ | `POST /login` | `{ "token": "" }`, срок 24 часа. `401` — неверная пара; `429` — больше 5 неудачных попыток с одного IP за 15 минут | | `POST /logout` | `{ "ok": true }`, токен отзывается немедленно | -Дальше в каждый запрос: `Authorization: Bearer $TOKEN`. Ниже — роуты, которые -дёргает сама панель; в отличие от `/api/v1` их форма может меняться. +Дальше в каждый запрос: `Authorization: Bearer $TOKEN`; сервер — заголовком `X-Server-Id` +(без него — сервер по умолчанию). Ниже — роуты, которые дёргает сама панель; в отличие от +`/api/v1` их форма может меняться. ### Состояние | Метод | Ответ | |---|---| -| `GET /ui/brand` | `{ "brand": "Forgetting", "channel": "Beta", "version": "0.4.7" }` — требует JWT панели (без авторизации выдавал бы панель сканерам). Вордмарк в шапке после входа | +| `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`, а не хранится отдельно. +`gen` — поколение AmneziaWG, на параметрах которого сейчас работает интерфейс: `"2"` или +`"3.1"`. Оно выводится из `awg1.conf`, а не хранится отдельно. ### Пользователи @@ -281,8 +395,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=` — следующая страница, `q` — поиск по имени, владельцу, IP. `501` — сервер старой версии | + +Без имён `sen://` тот же журнал отдаёт awg-ctrl: `GET /api/connlog`. ### Интерфейс AWG @@ -292,10 +414,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. ### Мастер-ключи @@ -314,32 +435,27 @@ base64url-декодировать, отбросить первые 4 байта | `DELETE /ui/devices/:id` | Отвязать устройство | | `POST /ui/devices/:id/rekey`, `/psk` | Смена ключа / PSK у устройства | -## Журнал подключений +### Метрики -Вкладка «Подключения» показывает по выбранному серверу: - -- **События** — кто подключился к VPN и отключился, когда, сколько длилась сессия и сколько - скачал и отдал. Поиск — по имени пользователя, мастер-ключу, устройству или внутреннему IP. -- **Кто когда был** — все пользователи и устройства sen:// сервера: онлайн сейчас, когда были на - связи последний раз и кто не подключался ни разу с начала журнала. - -Журнал ведёт awg-ctrl каждого сервера (`conn_log` в `users.db`, 90 дней). У WireGuard нет событий -подключения, поэтому раз в 30 с он сверяет время handshake и счётчики трафика: «подключился» — -время первого handshake, «отключился» — последняя активность (точность около 30 с). Перезапуск -awg-ctrl сессии не рвёт, падение интерфейса отключением не считается. Внешний IP клиентов не -записывается. Тот же журнал по API: `GET /api/connlog?limit=&before=&q=`. Серверы на прежней -версии журнала не ведут — вкладка попросит их обновить. +| Метод | Что делает | +|---|---| +| `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 '' [--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 ``` @@ -349,5 +465,22 @@ awg-ctrl migrate cancel 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` — выключить периодический аудит пиров | + ## Лицензия [MIT](LICENSE) © 2026 Ivan Vasilev