Files
Py-3XUI-multiserver/README.md
T
SashegDev 255caeb069 fix: server health ping, rate limiting, security hardening, DA backoff
- 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
2026-06-16 21:23:03 +00:00

251 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 серверов