This commit is contained in:
2026-08-27 01:39:06 +03:00
parent b097ccd5fb
commit 0dbb13f120
13 changed files with 129 additions and 499 deletions
+26 -91
View File
@@ -9,7 +9,7 @@
| Компонент | Роль |
|---|---|
| **awg-ctrl** | Привилегированный REST-бэкенд. Управляет пирами через `awg`/`awg-quick`, ведёт SQLite-базу, генерирует `vpn://` ключи. Слушает только loopback. |
| **awg-ui** | Express + React. Веб-панель, аутентификация (JWT), внешний API (`/api/v1`), прокси к awg-ctrl. Единственный сервис, доступный по сети. |
| **awg-ui** | Express + React. Веб-панель, аутентификация (JWT), интеграция с API MA7, прокси к awg-ctrl. Единственный сервис, доступный по сети. |
| **cli** | Менеджер процессов: `start`/`stop`/`restart`/`status`, смена логина/пароля панели, интерактивное TUI-меню. |
Внутренняя связь awg-ui → awg-ctrl подписывается асимметрично (Ed25519): приватный ключ только у awg-ui, awg-ctrl проверяет подпись публичным.
@@ -42,103 +42,38 @@ awg-ctrl
systemctl start|stop|restart awg-control
```
## 1. Публичный API (`/api/v1`)
## Интеграция с MA7
Авторизация по API-ключу. Ответы содержат только публичные поля — приватные
ключи и PSK наружу не отдаются.
Этот сервер персональный — принадлежит одному абоненту MA7 (сервиса
VPN-подписки). Панель ничем не управляет в MA7 и не отдаёт наружу свой API:
вкладка «Профиль» только читает баланс своего владельца. Секрет MA7 живёт
только в `awg-ui` (`cli.env`) и в браузер не попадает — фронт ходит на свой
бэкенд, тот читает MA7.
### Авторизация
Нужные переменные (задаются при установке или руками в `cli.env`):
Каждый запрос обязан содержать заголовок:
```
X-Api-Key: awgk_<...>
```
Ключ создаётся в панели (вкладка «API-ключи») или через `POST /ui/apikeys`
(раздел 3). Открытое значение ключа показывается **один раз** при создании —
сохрани его сразу, дальше доступен только хэш.
Ответы при проблемах с ключом:
| Код | Когда |
| Переменная | Назначение |
|---|---|
| `401 {"error":"API key required"}` | заголовок отсутствует или не начинается с `awgk_` |
| `401 {"error":"Invalid API key"}` | ключ не найден (удалён/неверный) |
| `MA7_API_BASE_URL` | адрес API MA7, например `https://amnesia.ma7neko.ru` |
| `MA7_JWT_SECRET` | секрет, которым панель сама подписывает `X-Bot-Token` (эндпоинта, выдающего токен, в MA7 нет) |
| `MA7_LOGIN` | логин владельца этого сервера в MA7 (`ma7_xxxxxx`) |
> Ключ привязан к серверу (`server_id`). Сейчас сервер один — поле всегда `0`
Вкладка «Профиль» просто показывает текущий баланс `MA7_LOGIN`. При установке
`MA7_LOGIN` заодно предлагается как логин панели (`UI_USER`) — сервер
персональный, и заказчик входит тем же именем, что знает по MA7; при желании
логин панели можно задать свой. Это разные переменные: `UI_USER` решает, кого
пускать в панель, `MA7_LOGIN` — чей баланс показывать.
### `POST /api/v1/users` — создать пользователя
`MA7_LOGIN` задаётся один раз при установке и **не редактируется из панели**: у панели
один общий пароль (`UI_USER`/`UI_PASS`) без отдельного клиентского входа, а
`MA7_JWT_SECRET` умеет искать в MA7 любого абонента — редактируемый в браузере
логин превратил бы личный баланс в инструмент подглядывания за чужими.
Создаёт VPN-пира и возвращает готовый `vpn://` ключ.
> [!WARNING]
> `MA7_JWT_SECRET` — мастер-ключ ко всему MA7: с ним можно читать данные любых
> пользователей MA7, не только этого сервера. Токены MA7 без срока действия,
> отзыва нет — «отобрать доступ» значит одновременно сменить `MA7_JWT_SECRET`
> во всех сервисах MA7 и здесь, в `cli.env`.
**Тело:** `{ "name": "<имя>" }`
Имя: `a–z A–Z 0–9 _ -`, 1–32 символа.
**`201 Created`:**
```json
{
"name": "alice",
"ip": "10.9.0.2",
"vpn_key": "vpn://AAAI..."
}
```
| Код ошибки | Причина |
|---|---|
| `400` | имя не прошло валидацию |
| `409` | пользователь с таким именем уже есть |
```bash
curl -X POST http://HOST:PORT/api/v1/users \
-H "X-Api-Key: awgk_xxx" \
-H "Content-Type: application/json" \
-d '{"name":"alice"}'
```
### `GET /api/v1/users` — список пользователей
Пиры с трафиком и статусом онлайн.
**`200 OK`:**
```json
{
"users": [
{ "name": "alice", "ip": "10.9.0.2", "online": true, "lastHandshake": 1718800000, "rx": 142400000, "tx": 9800000 },
{ "name": "bob", "ip": "10.9.0.3", "online": false, "lastHandshake": 0, "rx": 0, "tx": 0 }
]
}
```
- `lastHandshake` — unix-секунды последнего рукопожатия (`0` — не было).
- `rx`/`tx` — байты принято/отдано.
```bash
curl http://HOST:PORT/api/v1/users -H "X-Api-Key: awgk_xxx"
```
### `GET /api/v1/users/:name` — получить ключ пользователя
**`200 OK`:**
```json
{ "name": "alice", "ip": "10.9.0.2", "vpn_key": "vpn://AAAI..." }
```
| Код ошибки | Причина |
|---|---|
| `404` | пользователь не найден |
```bash
curl http://HOST:PORT/api/v1/users/alice -H "X-Api-Key: awgk_xxx"
```
> Имея `vpn_key`, конфиг `.conf` можно получить локально: убрать префикс
> `vpn://`, base64url-декодировать, отбросить первые 4 байта (длина), распаковать
> `zlib inflate` → JSON; текст конфига лежит в `last_config.config`.
### `DELETE /api/v1/users/:name` — удалить пользователя
**`200 OK`:**
```json
{ "success": true, "name": "alice" }
```
```bash
curl -X DELETE http://HOST:PORT/api/v1/users/alice -H "X-Api-Key: awgk_xxx"
```
## Лицензия
[MIT](LICENSE) © 2026 Ivan Vasilev