Панель управления whitelist для WARP Relay серверов.
Бесплатный хостинг на Vercel + Supabase.
Telegram Bot ──HTTP──▶ Vercel (FastAPI) ──HTTP──▶ Relay Agent 1
Supabase (PostgreSQL) ────▶ Relay Agent 2
▲ ────▶ Relay Agent N
│
Клиент по ссылке
(определяется IPv4)
| Компонент | Где | Стоимость |
|---|---|---|
| API-панель | Vercel serverless | Бесплатно |
| База данных | Supabase PostgreSQL | Бесплатно (500 MB) |
| Relay Agent | На каждом relay-сервере | VPS |
| Telegram Bot | Сервер | VPS |
Supabase:
- Создать проект на supabase.com
- SQL Editor → вставить
supabase_schema.sql→ Run - Скопировать Project URL и service_role key
Vercel — деплой одной кнопкой:
После деплоя → Settings → Environment Variables → задать:
| Переменная | Значение |
|---|---|
SUPABASE_URL |
https://xxx.supabase.co |
SUPABASE_KEY |
eyJ...service-role-key... |
ENCRYPTION_KEY |
Сгенерировать: python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" |
API_KEY |
Любой секретный ключ для бота |
AGENT_SECRET |
Общий секрет для relay-агентов |
MAX_ACTIVATIONS_PER_DAY |
10 (0 = без лимита) |
→ Redeploy чтобы подхватить переменные.
ssh root@RELAY_IP
git clone https://github.com/openwarpkit/warp-relay-panel.git /opt/warp-relay-panel
bash /opt/warp-relay-panel/relay-agent/setup_relay.shСкрипт спросит Agent secret (тот же что AGENT_SECRET на Vercel) и порт (по умолчанию 7580).
Автоматически настроит: timezone МСК, iptables, ipset, systemd, автовосстановление при перезагрузке.
PANEL="https://your-project.vercel.app"
KEY="your-api-key"
curl -X POST ${PANEL}/api/relays \
-H "X-API-Key: ${KEY}" \
-H "Content-Type: application/json" \
-d '{"name": "FI-Helsinki", "host": "1.2.3.4", "agent_port": 7580}'curl -X POST ${PANEL}/api/clients \
-H "X-API-Key: ${KEY}" \
-H "Content-Type: application/json" \
-d '{"label": "Иван", "note": "подписчик"}'
# Ответ: {"id": 1, "token": "a1b2c3d4e5f67890", ...}
# Ссылка: https://your-project.vercel.app/activate/a1b2c3d4e5f67890curl -X POST ${PANEL}/api/relays/sync-all -H "X-API-Key: ${KEY}"Обновление работает через API (fire-and-forget): панель отправляет команду, агент подтверждает получение и обновляется в фоне.
# Обновить все relay:
curl -X POST ${PANEL}/api/relays/update-all -H "X-API-Key: ${KEY}"
# Обновить один:
curl -X POST ${PANEL}/api/relays/{id}/update -H "X-API-Key: ${KEY}"Ответ сразу сообщает какие relay приняли команду, а какие недоступны:
{
"FI-Helsinki": {"accepted": true, "message": "Update started in background"},
"DE-Frankfurt": {"error": "[DE-Frankfurt] timeout: POST /update"}
}Проверить результат обновления — через /health каждого relay:
curl -X GET ${PANEL}/api/relays/{id}/health -H "X-API-Key: ${KEY}"
# → "last_update": {"ok": true, "new_version": "1.2.1", "finished_at": "..."}При каждом запуске агент (через ExecStartPre) проверяет и восстанавливает ipset и iptables правила из сохранённых конфигов.
Ручная установка relay (без git)
scp -r relay-agent root@RELAY_IP:/tmp/
ssh root@RELAY_IP "bash /tmp/relay-agent/setup_relay.sh"В этом режиме обновление через API не работает — только вручную через scp.
Ручной деплой Vercel (без кнопки)
cd warp-relay-panel
npm i -g vercel
vercel
# Задать переменные в Dashboard → Settings → Environment Variables
vercel --prodENCRYPTION_KEY используется для шифрования IP-адресов клиентов в базе данных (Fernet AES-128-CBC).
⚠️ Если сменитьENCRYPTION_KEY— все ранее зашифрованные IP станут нечитаемыми. Клиенты будут отображаться с ошибкойdecrypt_error, активации продолжат работать (будут записываться новые IP с новым ключом), но история будет потеряна.
Правила:
- Сгенерировать один раз:
python3 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())" - Сохранить в надёжном месте (password manager)
- Никогда не менять после начала работы с клиентами
- Не коммитить в git
Агент слушает на порту 7580 по HTTP. Защита:
# Если есть фиксированный IP панели:
ufw allow from PANEL_IP to any port 7580
ufw deny 7580Если фиксированного IP нет (Vercel serverless) — защита через AGENT_SECRET.
Все IP-адреса хранятся зашифрованными (Fernet). Для поиска используется SHA-256 хэш. IP-бан лист тоже зашифрован. Даже при утечке базы — IP не раскрываются.
Все /api/* эндпоинты требуют заголовок X-API-Key.
| Метод | Путь | Описание |
|---|---|---|
POST |
/api/clients |
Создать {"label":"...", "note":"..."} |
GET |
/api/clients |
Список всех (?include_blocked=false) |
GET |
/api/clients/{id} |
Детали клиента |
GET |
/api/clients/{id}/logs |
История активаций |
GET |
/api/clients/{id}/traffic |
Трафик клиента со всех relay |
PATCH |
/api/clients/{id}/block |
Блокировать {"blocked": true} |
DELETE |
/api/clients/{id} |
Удалить (+ убрать IP с relay) |
Общий IP: при блокировке/удалении клиента IP удаляется с relay только если никто другой на этом IP не сидит.
| Метод | Путь | Описание |
|---|---|---|
POST |
/api/relays |
Добавить relay |
GET |
/api/relays |
Список |
DELETE |
/api/relays/{id} |
Удалить |
PATCH |
/api/relays/{id}/toggle |
Вкл/выкл {"active": false} |
GET |
/api/relays/{id}/health |
Здоровье + last_update |
GET |
/api/relays/{id}/stats |
Статистика (клиенты, трафик, порты) |
GET |
/api/relays/{id}/traffic |
Потребление трафика по IP |
POST |
/api/relays/{id}/sync |
Синхронизировать whitelist |
POST |
/api/relays/{id}/update |
Обновить агент (fire-and-forget) |
POST |
/api/relays/sync-all |
Синхронизировать все relay |
POST |
/api/relays/update-all |
Обновить все relay |
GET |
/api/relays/health-all |
Проверить все relay |
| Метод | Путь | Описание |
|---|---|---|
POST |
/api/blacklist |
Забанить {"ip":"1.2.3.4", "reason":"..."} |
GET |
/api/blacklist |
Список всех банов |
GET |
/api/blacklist/check/{ip} |
Проверить IP |
DELETE |
/api/blacklist/{id} |
Разбанить по ID |
DELETE |
/api/blacklist/by-ip |
Разбанить {"ip":"1.2.3.4"} |
IP-бан блокирует активацию для ЛЮБОГО клиента с этим IP. Клиенты не блокируются — могут активироваться с другого IP.
| Метод | Путь | Описание |
|---|---|---|
GET |
/api/traffic |
Трафик со всех relay (по IP) |
GET |
/api/stats |
Общая статистика |
GET |
/activate/{token} |
Активация по ссылке (публичный) |
GET |
/health |
Healthcheck |
Порт 7580. Все эндпоинты (кроме /health) требуют X-Agent-Key.
| Метод | Путь | Описание |
|---|---|---|
POST |
/whitelist/update |
{"new_ip":"...", "old_ip":"...", "client_id": 1} |
POST |
/whitelist/remove |
{"ip":"..."} |
POST |
/whitelist/sync |
{"clients":[{"ip":"...", "client_id": 1}]} |
GET |
/whitelist/list |
Текущий ipset |
GET |
/traffic |
Трафик по IP за месяц (сброс по МСК) |
GET |
/traffic/{ip} |
Конкретный IP + clients_on_ip |
POST |
/traffic/reset |
Принудительный сброс |
GET |
/health |
Система + last_update (без авторизации) |
GET |
/stats |
Клиенты, порты, сессии, трафик |
GET |
/refcount |
Маппинг IP → client_ids |
POST |
/update |
Самообновление (fire-and-forget) |
Пример для aiogram 3
import aiohttp
PANEL_URL = "https://your-project.vercel.app"
API_KEY = "your-api-key"
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
async def create_client(label: str) -> dict:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{PANEL_URL}/api/clients",
headers=HEADERS,
json={"label": label},
) as resp:
return await resp.json()
async def get_activate_url(token: str) -> str:
return f"{PANEL_URL}/activate/{token}"
async def get_client_info(client_id: int) -> dict:
async with aiohttp.ClientSession() as session:
async with session.get(
f"{PANEL_URL}/api/clients/{client_id}",
headers=HEADERS,
) as resp:
return await resp.json()
async def ban_ip(ip: str, reason: str = "") -> dict:
async with aiohttp.ClientSession() as session:
async with session.post(
f"{PANEL_URL}/api/blacklist",
headers=HEADERS,
json={"ip": ip, "reason": reason},
) as resp:
return await resp.json()warp-relay-panel/
├── api/ # Vercel serverless
│ ├── index.py # FastAPI (роуты, активация, блэклист)
│ ├── database.py # Supabase операции
│ ├── relay_client.py # HTTP-клиент для relay-агентов
│ └── crypto.py # Шифрование IP (Fernet)
├── relay-agent/ # Ставится на каждый relay
│ ├── agent.py # FastAPI агент
│ ├── setup_relay.sh # Полная установка
│ ├── update.sh # Ручное обновление (fallback)
│ ├── ensure_rules.sh # Восстановление iptables/ipset
│ ├── requirements.txt
│ └── .env.example
├── supabase_schema.sql # SQL для создания таблиц
├── vercel.json # Конфигурация Vercel
├── requirements.txt # Python зависимости (Vercel)
└── .env.example # Переменные окружения
- Фоновая синхронизация whitelist через
/whitelist/sync - Статус последней синхронизации в
/health→last_sync(ok,synced,clients,in_progress) - Пагинация выборок из Supabase (
list_clients,list_ip_bans,get_all_active_ips) - Исправлено удаление осиротевших IP из ipset (
RefCountMap.remove_client— когда refcount=0, но IP остался в map) - Таймаут HTTP-запросов на
/whitelist/syncподнят до 30 сек (страховка для больших payload) - В ответе
full_syncтеперь{total_clients, skipped_banned, relays: {...}}вместо плоского словаря
- Обновление relay через API (fire-and-forget, без таймаутов Vercel)
- Статус последнего обновления в
/health→last_update - Timezone МСК на всех relay (трафик сбрасывается по московскому времени)
- Документация
ENCRYPTION_KEY
- IP-блэклист (хард-бан по IP)
- Защита общих IP (refcount на панели и агенте)
- Мониторинг трафика по IP (conntrack accounting)
- Фильтрация ботов (Telegram preview и др.)
- Автовосстановление iptables/ipset при перезагрузке
- Самообновление relay через API
- Исправлен
ipset destroy→ipset flush
- Первый релиз