The integration was half-committed: server.ts imported ./ma7 and the profile tab was documented, but neither file ever reached the repository, so awg-ui could not start at all — `Error: Cannot find module './ma7'`. Everything that referenced it is gone: the import and the /ui/ma7/profile route, the three MA7_* variables the CLI passed to awg-ui, the installer prompts and their cli.env block, and the README section. The panel login prompt goes back to defaulting to admin instead of the MA7 subscriber login. awg-ui type-checks, builds and starts again.
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
| Компонент | Роль |
|
||||
|---|---|
|
||||
| **awg-ctrl** | Привилегированный REST-бэкенд. Управляет пирами через `awg`/`awg-quick`, ведёт SQLite-базу, генерирует `vpn://` ключи. Слушает только loopback. |
|
||||
| **awg-ui** | Express + React. Веб-панель, аутентификация (JWT), интеграция с API MA7, прокси к awg-ctrl. Единственный сервис, доступный по сети. |
|
||||
| **awg-ui** | Express + React. Веб-панель, аутентификация (JWT), прокси к awg-ctrl. Единственный сервис, доступный по сети. |
|
||||
| **cli** | Менеджер процессов: `start`/`stop`/`restart`/`status`, смена логина/пароля панели, интерактивное TUI-меню. |
|
||||
|
||||
Внутренняя связь awg-ui → awg-ctrl подписывается асимметрично (Ed25519): приватный ключ только у awg-ui, awg-ctrl проверяет подпись публичным.
|
||||
@@ -94,38 +94,5 @@ awg-ctrl
|
||||
systemctl start|stop|restart awg-control
|
||||
```
|
||||
|
||||
## Интеграция с MA7
|
||||
|
||||
Этот сервер персональный — принадлежит одному абоненту MA7 (сервиса
|
||||
VPN-подписки). Панель ничем не управляет в MA7 и не отдаёт наружу свой API:
|
||||
вкладка «Профиль» только читает баланс своего владельца. Секрет MA7 живёт
|
||||
только в `awg-ui` (`cli.env`) и в браузер не попадает — фронт ходит на свой
|
||||
бэкенд, тот читает MA7.
|
||||
|
||||
Нужные переменные (задаются при установке или руками в `cli.env`):
|
||||
|
||||
| Переменная | Назначение |
|
||||
|---|---|
|
||||
| `MA7_API_BASE_URL` | адрес API MA7, например `https://amnesia.ma7neko.ru` |
|
||||
| `MA7_JWT_SECRET` | секрет, которым панель сама подписывает `X-Bot-Token` (эндпоинта, выдающего токен, в MA7 нет) |
|
||||
| `MA7_LOGIN` | логин владельца этого сервера в MA7 (`ma7_xxxxxx`) |
|
||||
|
||||
Вкладка «Профиль» просто показывает текущий баланс `MA7_LOGIN`. При установке
|
||||
`MA7_LOGIN` заодно предлагается как логин панели (`UI_USER`) — сервер
|
||||
персональный, и заказчик входит тем же именем, что знает по MA7; при желании
|
||||
логин панели можно задать свой. Это разные переменные: `UI_USER` решает, кого
|
||||
пускать в панель, `MA7_LOGIN` — чей баланс показывать.
|
||||
|
||||
`MA7_LOGIN` задаётся один раз при установке и **не редактируется из панели**: у панели
|
||||
один общий пароль (`UI_USER`/`UI_PASS`) без отдельного клиентского входа, а
|
||||
`MA7_JWT_SECRET` умеет искать в MA7 любого абонента — редактируемый в браузере
|
||||
логин превратил бы личный баланс в инструмент подглядывания за чужими.
|
||||
|
||||
> [!WARNING]
|
||||
> `MA7_JWT_SECRET` — мастер-ключ ко всему MA7: с ним можно читать данные любых
|
||||
> пользователей MA7, не только этого сервера. Токены MA7 без срока действия,
|
||||
> отзыва нет — «отобрать доступ» значит одновременно сменить `MA7_JWT_SECRET`
|
||||
> во всех сервисах MA7 и здесь, в `cli.env`.
|
||||
|
||||
## Лицензия
|
||||
[MIT](LICENSE) © 2026 Ivan Vasilev
|
||||
|
||||
@@ -6,7 +6,6 @@ import path from "path";
|
||||
import fs from "fs";
|
||||
import crypto from "crypto";
|
||||
import axios from "axios";
|
||||
import { ma7 } from "./ma7";
|
||||
|
||||
const app = express();
|
||||
const PORT = Number(process.env.PORT) || 8080;
|
||||
@@ -154,38 +153,6 @@ async function ctrl(method: string, urlPath: string, body?: unknown) {
|
||||
});
|
||||
}
|
||||
|
||||
// ── Интеграция с MA7 (вкладка «Профиль») ───────────────────────────────────
|
||||
// Сервер персональный — панель показывает баланс своего единственного
|
||||
// владельца, не список абонентов. Логин владельца (MA7_LOGIN) — часть
|
||||
// конфигурации сервера, задаётся один раз при установке (cli.env), а НЕ
|
||||
// вводится через UI: бот-токен MA7_JWT_SECRET умеет искать любого абонента
|
||||
// MA7, и если бы логин можно было менять прямо в панели, любой, кто знает
|
||||
// пароль от панели, мог бы подсматривать баланс чужих абонентов — панель
|
||||
// защищена одним общим UI_USER/UI_PASS, отдельного «клиентского» входа нет.
|
||||
const MA7_LOGIN = process.env.MA7_LOGIN ?? "";
|
||||
|
||||
app.get("/ui/ma7/profile", requireAuth, async (_req: Request, res: Response) => {
|
||||
if (!/^[a-zA-Z0-9_-]{1,32}$/.test(MA7_LOGIN)) {
|
||||
res.status(500).json({ error: "MA7_LOGIN не задан — укажи его в cli.env" }); return;
|
||||
}
|
||||
try {
|
||||
const r = await ma7.get("/api/admin/users", { params: { q: MA7_LOGIN, limit: 1 } });
|
||||
if (r.status >= 400) {
|
||||
const msg = r.status === 401 ? "MA7: неверный MA7_JWT_SECRET"
|
||||
: r.status === 403 ? "MA7: не та роль в токене"
|
||||
: (r.data as { error?: string })?.error ?? "Ошибка MA7";
|
||||
res.status(r.status === 401 || r.status === 403 ? 502 : r.status).json({ error: msg });
|
||||
return;
|
||||
}
|
||||
const user = (r.data.users as { login: string; balance: number }[] ?? [])
|
||||
.find(u => u.login === MA7_LOGIN);
|
||||
if (!user) { res.status(404).json({ error: "MA7_LOGIN не найден в MA7" }); return; }
|
||||
res.json({ login: user.login, balance: user.balance });
|
||||
} catch {
|
||||
res.status(502).json({ error: "MA7 недоступен" });
|
||||
}
|
||||
});
|
||||
|
||||
app.use(["/api", "/health", "/awg"], requireAuth, proxy);
|
||||
|
||||
app.use(express.static(path.join(__dirname, "dist")));
|
||||
|
||||
@@ -90,13 +90,6 @@ const SERVICES = {
|
||||
UI_USER: process.env.UI_USER ?? "admin",
|
||||
UI_PASS: process.env.UI_PASS ?? "",
|
||||
JWT_SECRET: process.env.JWT_SECRET ?? "",
|
||||
// Интеграция с API MA7 (см. awg-ui/ma7.ts) — секрет чужого сервиса,
|
||||
// не совпадает с UI_* / JWT_SECRET этой панели. MA7_LOGIN — логин
|
||||
// владельца этого персонального сервера в MA7 ('ma7_xxxxxx'):
|
||||
// задаётся один раз при установке, в панели не редактируется.
|
||||
MA7_API_BASE_URL: process.env.MA7_API_BASE_URL ?? "",
|
||||
MA7_JWT_SECRET: process.env.MA7_JWT_SECRET ?? "",
|
||||
MA7_LOGIN: process.env.MA7_LOGIN ?? "",
|
||||
},
|
||||
pidFile: "/tmp/awg-ui.pid",
|
||||
logFile: path.join(LOGS, "awg-ui.log"),
|
||||
|
||||
+2
-25
@@ -398,26 +398,8 @@ AWGCTRL_PORT=$(( (RANDOM % 22768) + 32768 ))
|
||||
read -rp " UI port (Enter — случайный): " UI_PORT
|
||||
UI_PORT="${UI_PORT:-$(( (RANDOM % 22768) + 32768 ))}"
|
||||
|
||||
# Интеграция с API MA7 (см. README.md, раздел «Интеграция с MA7») — этот
|
||||
# сервер персональный, панель показывает баланс своего единственного
|
||||
# владельца. Спрашивается ДО логина панели: логин MA7 по умолчанию становится
|
||||
# и логином панели (см. ниже). Пустые значения допустимы — вкладку «Профиль»
|
||||
# можно настроить позже вручную в cli.env.
|
||||
echo
|
||||
echo -e "${BLD}Интеграция с MA7 (Enter — оставить пустым и настроить позже):${NC}"
|
||||
read -rp " MA7 API base URL [https://amnesia.ma7neko.ru]: " MA7_API_BASE_URL
|
||||
MA7_API_BASE_URL="${MA7_API_BASE_URL:-https://amnesia.ma7neko.ru}"
|
||||
read -rsp " MA7 JWT_SECRET (мастер-ключ MA7, см. предупреждение в README): " MA7_JWT_SECRET; echo
|
||||
read -rp " Логин заказчика в MA7 (ma7_xxxxxx, владелец этого сервера): " MA7_LOGIN
|
||||
|
||||
echo
|
||||
# Логин панели по умолчанию = логин заказчика в MA7: сервер персональный, и
|
||||
# заказчик входит тем же именем, что знает по MA7. Если MA7_LOGIN не задан —
|
||||
# откатываемся на admin. UI_USER и MA7_LOGIN остаются разными переменными:
|
||||
# первая решает, кого пускать в панель, вторая — чей баланс показывать.
|
||||
UI_USER_DEFAULT="${MA7_LOGIN:-admin}"
|
||||
read -rp " UI логин [$UI_USER_DEFAULT]: " UI_USER
|
||||
UI_USER="${UI_USER:-$UI_USER_DEFAULT}"
|
||||
read -rp " UI логин [admin]: " UI_USER
|
||||
UI_USER="${UI_USER:-admin}"
|
||||
|
||||
SUGGESTED_PASS=$(tr -dc 'a-zA-Z0-9' < /dev/urandom | head -c 12 2>/dev/null || openssl rand -hex 6)
|
||||
read -rsp " UI пароль [$SUGGESTED_PASS]: " UI_PASS; echo
|
||||
@@ -896,11 +878,6 @@ UI_PORT=${UI_PORT}
|
||||
UI_USER=${UI_USER}
|
||||
UI_PASS=${UI_PASS}
|
||||
JWT_SECRET=${JWT_SECRET}
|
||||
|
||||
# ── интеграция с MA7 (секрет чужого сервиса, не путать с JWT_SECRET выше) ──
|
||||
MA7_API_BASE_URL=${MA7_API_BASE_URL}
|
||||
MA7_JWT_SECRET=${MA7_JWT_SECRET}
|
||||
MA7_LOGIN=${MA7_LOGIN}
|
||||
ENV
|
||||
|
||||
# Имя продукта попадает в cli.env, только если оператор задал его явно
|
||||
|
||||
Reference in New Issue
Block a user