255caeb069
- Server health ping every 30s — dead servers excluded from all API calls, auto-recovered when back online - Fixed py3xui client.delete() argument order (inbound_id first, client_uuid second) - Rate limiting on /admin/login (max 10 attempts per 60s per IP) - XSS prevention: HTML-escape all user-controlled fields in templates - CSP headers on all HTML pages (CSP, X-Frame-Options, X-Content-Type-Options) - CORS restricted to configured host instead of wildcard - Secure + SameSite=Lax cookie flags for admin_token - Bind to 127.0.0.1 instead of 0.0.0.0 - Async locks for global state (last_donation_id) - Graceful shutdown: all background tasks cancelled on exit - DA polling exponential backoff: 1s→max 1h, immediate 1h on 401
251 lines
11 KiB
Markdown
251 lines
11 KiB
Markdown
# ZernProxy Manager (Py-3XUI-multiserver)
|
||
|
||
Объединение нескольких серверов 3x-UI в единую VPN-подписку. Soft-tariff система с веб-панелью, донатной оплатой, Happ и WDTT совместимостью.
|
||
|
||
## Architecture
|
||
|
||
```
|
||
servers.conf ──→ 3x-UI сервера (API + sub URL + status URL)
|
||
settings.conf ──→ тарифы, платежи, админка, auto-propagate
|
||
users.db ───────→ пользователи (SQLite)
|
||
↓
|
||
aggregator.py ──→ FastAPI сервер
|
||
↓
|
||
/sub/{id} ────→ подписка для клиента (VLESS / Trojan / VMess / Shadowsocks)
|
||
/admin/* ─────→ веб-панель управления
|
||
/admin/api/propagate/{server} → синхронизация пользователей между серверами
|
||
/ ────────────→ статус-пейдж с health-check серверов
|
||
```
|
||
|
||
**Soft-tariff**: пользователь создаётся на всех inbounds один раз, tier (free/test/paid) меняется локально в БД, без дёргания 3x-UI API.
|
||
|
||
## Features
|
||
|
||
- **Multi-server**: объединение любого количества 3x-UI серверов
|
||
- **Multi-inbound**: поддержка нескольких inbound на сервере (разные протоколы)
|
||
- **Soft-tariff**: free / test / paid tier без пересоздания клиентов
|
||
- **Propagate**: синхронизация пользователей между серверами по strict majority (>50%)
|
||
- **Auto-propagate**: фоновая синхронизация по расписанию (по умолчанию 30 мин)
|
||
- **DonationAlerts**: приём платежей с поиском донатера по ID/username
|
||
- **Веб-панель**: дашборд со статистикой, CRUD пользователей, просмотр трафика, QR-коды
|
||
- **Статус-пейдж**: публичная страница с health-check серверов (CPU/RAM/Disk/Net/I/O)
|
||
- **Happ.su / WDTT совместимость**: заголовки profile-title, sub-expire, announce, hide-settings
|
||
- **Абсолютный expire**: `tariff_end_at` (Unix timestamp) — дни считаются динамически, автопонижение tier при истечении
|
||
- **In-memory кеш**: ссылки кешируются на 7 дней, трафик — на 2 минуты
|
||
- **ShortID ротация**: автоматическая смена shortId на серверах каждые N часов
|
||
- **MOTD**: объявления из motd.txt или settings
|
||
- **Health scoring**: взвешенная оценка здоровья сервера (CPU 30%, RAM 25%, Disk 20%, Net 15%, I/O 10%)
|
||
- **Server health ping**: фоновый пинг всех серверов каждые 30с — dead сервера автоматически исключаются из подписки, трафика, ротации shortid и админки
|
||
- **Auto-recovery**: при возвращении сервера в сеть он снова включается в работу без перезагрузки
|
||
- **Rate limiting**: не более 10 попыток входа в админку за 60с с одного IP
|
||
- **DA polling backoff**: при ошибках DonationAlerts API интервал удваивается до 1ч, при 401 — сразу 1ч ожидания
|
||
- **CSP + XSS protection**: Content-Security-Policy на всех HTML-страницах, экранирование user-контролируемых полей
|
||
- **Graceful shutdown**: все фоновые задачи корректно завершаются при остановке
|
||
- **Trojan support**: создание клиентов на Trojan инбаундах (password вместо uuid)
|
||
|
||
## wdtt (Zern-BlackOut) Compatibility
|
||
|
||
Проект полностью совместим с клиентом **Zern-BlackOut** (форк wdtt), доступным по адресу:
|
||
[https://git.swe.zernmc.ru/sasheg/Zern-BlackOut](https://git.swe.zernmc.ru/sasheg/Zern-BlackOut)
|
||
|
||
> **Важно:** Клиент wdtt был модифицирован для совместимости с данным агрегатором.
|
||
> Использование оригинального wdtt без модификаций может привести к некорректной работе.
|
||
> Репозиторий Zern-BlackOut находится в активной разработке для полной интеграции.
|
||
|
||
Поддерживаемые возможности:
|
||
- VLESS + XTLS Vision / Reality
|
||
- Trojan + HTTP + Reality
|
||
- Автообновление подписки
|
||
- Отображение expire-даты и лимитов трафика
|
||
- Hide-settings, announce, support-url через subscription-заголовки
|
||
|
||
## Quick Start
|
||
|
||
```bash
|
||
pip install fastapi uvicorn httpx py3xui qrcode[pil] Pillow
|
||
git clone ssh://git@git.swe.zernmc.ru:2222/sasheg/Py-3XUI-multiserver.git
|
||
cd Py-3XUI-multiserver
|
||
# настроить servers.conf, settings.conf (см. ниже)
|
||
python3 aggregator.py
|
||
```
|
||
|
||
Для production: `screen -S aggregator python3 aggregator.py`
|
||
|
||
## Configuration
|
||
|
||
### servers.conf
|
||
|
||
```json
|
||
{
|
||
"servers": [
|
||
{
|
||
"name": "ru-1",
|
||
"subscription_url": "http://panel.domain.com:2096",
|
||
"sub_path": "/sub/{sub_id}",
|
||
"country": "RU",
|
||
"status_url": "http://domain.com:18765/status?secret=...",
|
||
"inbounds": [
|
||
{
|
||
"id": 1,
|
||
"name": "Reality",
|
||
"api_host": "http://panel.domain.com:22881/PATH",
|
||
"api_user": "admin",
|
||
"api_pass": "password",
|
||
"is_free": true
|
||
},
|
||
{
|
||
"id": 2,
|
||
"name": "Trojan-Inbound",
|
||
"protocol": "trojan",
|
||
"api_host": "http://panel.domain.com:22881/PATH",
|
||
"api_user": "admin",
|
||
"api_pass": "password",
|
||
"is_free": false
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
| Field | Type | Description |
|
||
|-------|------|-------------|
|
||
| `name` | string | Unique server name |
|
||
| `subscription_url` | string | Base URL for subscription links |
|
||
| `sub_path` | string | Sub endpoint path (`/sub/{sub_id}`) |
|
||
| `country` | string | 2-letter country code (RU, DE, PL, SE...) |
|
||
| `status_url` | string | URL for server health data (3x-UI status endpoint) |
|
||
| `inbounds[].id` | int | Inbound ID in 3x-UI |
|
||
| `inbounds[].name` | string | Display name |
|
||
| `inbounds[].protocol` | string | *(optional)* protocol type (`trojan`) |
|
||
| `inbounds[].api_host` | string | 3x-UI API URL for this inbound |
|
||
| `inbounds[].api_user` | string | API login |
|
||
| `inbounds[].api_pass` | string | API password |
|
||
| `inbounds[].is_free` | bool | Доступен ли inbound для free-тарифа |
|
||
|
||
### settings.conf
|
||
|
||
```json
|
||
{
|
||
"general": {
|
||
"title": "MyVPN",
|
||
"host": "conn.example.com",
|
||
"support_url": "https://t.me/support"
|
||
},
|
||
"announcement": "base64 enc or plain text",
|
||
"shortid_rotation_hours": 11,
|
||
"tiers": {
|
||
"free": {
|
||
"name": "Free",
|
||
"traffic_limit_gb": 0
|
||
},
|
||
"test": {
|
||
"name": "Test",
|
||
"traffic_limit_gb": 5,
|
||
"prices": {
|
||
"test": 50
|
||
},
|
||
"days": {
|
||
"test": 7
|
||
}
|
||
},
|
||
"paid": {
|
||
"name": "Premium",
|
||
"traffic_limit_gb": 0,
|
||
"prices": {
|
||
"monthly": 150,
|
||
"yearly": 990
|
||
},
|
||
"days": {
|
||
"monthly": 30,
|
||
"yearly": 365
|
||
}
|
||
}
|
||
},
|
||
"payments": {
|
||
"donationalerts": {
|
||
"enabled": true,
|
||
"api_token": "...",
|
||
"webhook_secret": "...",
|
||
"url": "https://www.donationalerts.com/r/you"
|
||
}
|
||
},
|
||
"admin": {
|
||
"username": "admin",
|
||
"password": "secure_password"
|
||
},
|
||
"auto_propagate": {
|
||
"enabled": true,
|
||
"interval_minutes": 30
|
||
}
|
||
}
|
||
```
|
||
|
||
### Tiers
|
||
|
||
| Tier | Servers | Traffic | Price | Duration |
|
||
|------|---------|---------|-------|----------|
|
||
| free | inbounds with `is_free: true` | unlimited (0 = ∞) | free | forever |
|
||
| test | all inbounds | 5 GB | 50₽ | 7 days |
|
||
| paid | all inbounds | unlimited | 150₽/30d, 990₽/365d | configurable |
|
||
|
||
### Propagate
|
||
|
||
При добавлении нового сервера или инбаунда пользователи НЕ создаются автоматически на нём.
|
||
Для синхронизации используется **strict majority**: пользователь добавляется на целевой сервер только если он существует на >50% других активных серверов.
|
||
|
||
```bash
|
||
curl -X POST http://localhost:8000/admin/api/propagate/{server_name} -b "admin_token=..."
|
||
```
|
||
|
||
В админ-панели кнопка 🔄 на каждом сервере. Auto-propagate запускается фоном каждые N минут.
|
||
|
||
## Endpoints
|
||
|
||
| Path | Method | Description |
|
||
|------|--------|-------------|
|
||
| `/sub/{id}` | GET | Subscription (format: base64/json/raw) |
|
||
| `/sub/{id}` | GET (HTML) | Web page with QR and info |
|
||
| `/admin/login` | GET/POST | Admin auth |
|
||
| `/admin/users` | GET | User management panel |
|
||
| `/admin/dashboard` | GET | Stats dashboard with server health |
|
||
| `/admin/api/users` | POST | Create user |
|
||
| `/admin/api/users/update` | POST | Update user |
|
||
| `/admin/api/users/delete` | POST | Delete user (with 3x-UI) |
|
||
| `/admin/api/reload` | POST | Reload configs + clear cache |
|
||
| `/admin/api/propagate/{server}` | POST | Sync users to server |
|
||
| `/admin/api/rotate-shortids` | GET | Rotate short IDs on all servers |
|
||
| `/` | GET | Landing page with server status |
|
||
|
||
## DonationAlerts
|
||
|
||
Polling-based с adaptive backoff. Amount mapping from `settings.conf → tiers → prices`:
|
||
- Any configured amount → corresponding tier + days
|
||
- Donor identified by `id` or `username` from donation message
|
||
- `tariff_end_at` extends dynamically (MAX of current end, now + days)
|
||
- При ошибках API интервал удваивается (1 → 2 → 4 → ... → 3600с)
|
||
- При 401 Unauthorized — сразу 1ч ожидания (токен невалиден)
|
||
- После успешного ответа интервал сбрасывается к базовому
|
||
|
||
## Happ.su / WDTT Support
|
||
|
||
Subscription response includes compatible headers:
|
||
- `Profile-Title`, `Profile-Update-Interval`, `Profile-Web-Page-Url`
|
||
- `Subscription-Userinfo` (upload/download/total/expire)
|
||
- `Announce`, `Support-Url`, `Hide-Settings`
|
||
- `Sub-Expire`, `Sub-Expire-Button-Link`
|
||
|
||
## Tech
|
||
|
||
- **FastAPI** + uvicorn (bind на `127.0.0.1` — за reverse proxy)
|
||
- **SQLite** (users only)
|
||
- **py3xui** — 3x-UI API client
|
||
- **httpx** — async HTTP for sub links + server health pings
|
||
- **qrcode** — QR generation
|
||
- **Plus Jakarta Sans / JetBrains Mono** — UI typography
|
||
- **Glassmorphism** — design system
|
||
- **CSP + Security headers** — Content-Security-Policy, X-Frame-Options, X-Content-Type-Options на всех страницах
|
||
- **CORS** — ограничен до домена из конфига
|
||
- **Rate limiting** — in-memory на `/admin/login`
|
||
- **Server health** — фоновый пинг каждые 30с, автоисключение dead серверов
|