VPN·HUB·CRACK
MultiGate: прослойка подписок для Remnawave · VPN HUB CRACK
MultiGate: прослойка подписок для Remnawave
MultiGate: прослойка подписок для Remnawave
Подписка в Remnawave живёт по адресу панели: https://panel.example.com/api/sub/{shortUuid}.
Клиент ходит туда раз в несколько часов и забирает список узлов. Пока панель открывается
из России, всё хорошо.
А потом панель перестаёт открываться. Не узлы, а именно панель: она обычно спрятана за Cloudflare или стоит за границей. Узлы при этом живы, трафик идёт, но подписка не обновляется ни у кого. Клиент видит «не удалось обновить подписку» и идёт в поддержку.
⚠️ Отдельная неприятность: подписка перестаёт обновляться молча. Пока у клиента в приложении лежат старые конфиги, он ничего не замечает. Замечает он ровно в тот момент, когда вы поменяли узел, а до него это изменение не доехало.
MultiGate это прослойка, которая встаёт между клиентом и панелью и снимает эту зависимость. Плюс попутно отвечает на вопросы, которые панель не отвечает: с какого приложения пришёл клиент, сколько у него устройств и кто раздал одну подписку на всё общежитие.
Один бинарник без зависимостей, база SQLite в комплекте, лицензия MIT: github.com/qwe8nxtroud/multigate
📖 Незнакомые слова? Все термины простыми словами собраны в Словаре терминов.
Два режима работы
Зеркало. Прослойка ставится на отдельный сервер в России и проксирует запросы подписки на настоящий адрес панели. Панель не трогается вообще: ей всё равно, кто пришёл. Клиенты получают российский домен подписки, панель остаётся там, где стоит.
Панель. Прослойка ставится рядом с панелью и сама ходит в её API. Так доступно больше: она видит пользователя целиком (срок, трафик, сквады, лимит устройств), а не только тело ответа.
Режим переключается в админке и меняется на лету, перезапускать ничего не нужно.
Что видно в журнале
Главное, ради чего это ставится в первый же день. Каждый запрос подписки записывается с разбором клиента: приложение, его версия, ядро, платформа, устройство и что именно прослойка решила ему отдать.

Приложение и ядро определяются по User-Agent:
| Ядро | Приложения |
|---|---|
| xray | Happ, v2rayNG, Shadowrocket, V2Box, v2rayN, V2rayTun |
| mihomo | Clash Meta, Clash Verge, FlClash, FlClashX, Koala Clash |
| sing-box | sing-box, NekoBox, NekoRay, Hiddify, Streisand, Karing, Throne |
Часть приложений присылает заголовки со сведениями об устройстве, часть не присылает ничего. Для вторых прослойка собирает идентификатор из самого User-Agent: модель, версия ОС, версия приложения. Он стабилен между запросами одного устройства, и этого достаточно, чтобы увидеть шесть разных телефонов на одной подписке.
⚠️ Такой идентификатор не совпадает с настоящим HWID в панели. По нему можно блокировать у себя, но нельзя удалять устройство в панели: там его просто нет. В коде это разведено намеренно, чтобы никто случайно не удалил чужую запись.
Обзор и состояние

Здесь же видно, теряет ли журнал записи. Журнал пишется в фоне пачками, и если база не успевает, записи выбрасываются, а не задерживают выдачу подписки. Подписка важнее статистики, но знать об этом надо.
Блокировки
Блокировать можно пользователя целиком или отдельное устройство. Блокировки локальные: панель не трогается, в ней ничего не меняется.

Заблокированный клиент получает не ошибку, а нормальный ответ подписки с пустым списком узлов и говорящим названием профиля: «Подписка заблокирована». Это принципиальный момент.
⭐ Клиенты подписки не показывают код ошибки. На 403 они пишут «не удалось обновить», и человек идёт в поддержку с бесполезным «не работает». С кодом 200 и внятным названием профиля он видит причину прямо в приложении, и обращение в поддержку не нужно вовсе.
Так же ведёт себя и истёкшая подписка: название профиля меняется на «Срок подписки истёк».
Правила ответа под конкретные приложения
Панель отдаёт всем одинаковые заголовки подписки, хотя приложения понимают разное. Правила позволяют подменить название профиля, ссылку поддержки и интервал обновления для выбранного приложения, платформы или по совпадению с User-Agent.

Настройки

Все настройки живут в базе, а не в файле: меняются в админке и применяются сразу. Переменные окружения нужны только для первого запуска, чтобы контейнер поднялся без ручного мастера.
Токен панели в форме показывается маскированным и в HTML настоящим значением не попадает никогда.
Грейс для истёкших
Вместо того чтобы отключать истёкшую подписку намертво, можно перевести человека в отдельный сквад с ограниченным набором узлов: пусть зайдёт и оплатит, а не останется без связи вовсе.
🔴 Это единственная функция, которая меняет данные в живой панели. По умолчанию она выключена, и включать её стоит осознанно. Устроено так: снимок исходного состояния пользователя пишется в базу до первого обращения к панели, и если снимок сохранить не удалось, грейс не применяется вовсе. Без снимка нет отката.
Повторное применение защищено и проверкой в базе, и блокировкой в памяти: клиент опрашивает подписку по таймеру, и гонки тут реальные, а не теоретические. Возврат в исходное состояние идёт по расписанию и тоже идемпотентен: если панель была недоступна, запись останется в очереди и повторится на следующем проходе.
Маскировка
Если на адрес подписки зашли браузером, отдаётся обычная страница-заглушка: пустая, «технические работы» или парковка домена, на выбор. Ни слова про VPN, панель или подписку ни в теле, ни в заголовках. Смысл простой: адрес прослойки не должен выглядеть как сервис подписок при случайном заходе или сканировании.
Установка
Рядом с панелью, через Docker:
curl -fsSL https://raw.githubusercontent.com/qwe8nxtroud/multigate/main/deploy/docker-compose.yml -o docker-compose.yml
# впишите MULTIGATE_PANEL_TOKEN и MULTIGATE_ADMIN_PASSWORD
docker compose up -d
На отдельный сервер:
curl -fsSL https://raw.githubusercontent.com/qwe8nxtroud/multigate/main/deploy/install.sh -o install.sh
sudo bash install.sh
Установщик ставит один бинарник, заводит системного пользователя и юнит systemd. Он не трогает ваш веб-сервер: не сносит nginx, не выпускает сертификаты, не правит чужие конфигурации. Прослойка слушает localhost, а наружу её выставляет тот обратный прокси, который у вас уже есть. Готовые куски конфигурации для nginx и Caddy скрипт печатает в конце.
Обновление командой sudo bash install.sh --update. Если новая версия не поднялась,
установщик сам откатывается на предыдущую.
Что важно понимать перед установкой
🔴 Токен панели хранится в базе прослойки. Доступ к серверу прослойки равносилен доступу к токену панели. Выдавайте токен с ограниченными правами (на панели 2.8.0 и новее права задаются при создании токена) и не выставляйте админку в интернет без нужды.
Ещё несколько честных оговорок:
- Версия ранняя. Ставьте на тестовом контуре, прежде чем выпускать на клиентов.
- Совместимость проверена с панелями 2.7.4 и новее, включая ветку 3.x. Разница между версиями (в 2.x обращение по uuid, в 3.x по числовому id) спрятана внутри, но живьём клиент сверялся с панелью 2.8.0.
- SQLite по умолчанию. Для крупной установки переключается на MySQL той же командой, схема одна. Соединений два, писатель отдельно от читателей, поэтому журнал запросов не блокирует выдачу подписки.
- Один бинарник. Админка, страница-маскировка и схема базы встроены внутрь. Ни одной внешней ссылки: ни CDN, ни шрифтов Google. Прослойку ставят на серверы без выхода наружу, и любая внешняя зависимость там просто не загрузится.
Про ETag и залипшую подписку
Отдельная грабля, на которой легко потерять день. Если прослойка меняет тело ответа,
а ETag оставляет от панели, клиент при следующем запросе пришлёт If-None-Match,
получит 304 и продолжит пользоваться старой подпиской.
Симптом выглядит издевательски: в панели всё поменяно, а на устройстве по-прежнему старые
узлы. MultiGate срезает ETag из ответа и If-None-Match из запроса, так что эта ситуация
исключена.
Ссылки
- Исходники и релизы: github.com/qwe8nxtroud/multigate
- Устройство изнутри: docs/architecture.md