Перейти к содержимому

Публичный шлюз подписки

Шлюз позволяет выдавать ссылку подключения на домене магазина вместо прямой ссылки Remnawave Panel. Одна ссылка вида https://app.example.com/s/<token> открывает инструкции в браузере и отдаёт профиль подписки VPN-приложению. Пользователь может импортировать её в приложение и получать обновления через тот же адрес.

Minishop получает профиль из Remnawave Panel: формат ответа, лимиты HWID-устройств и правила выдачи Response Rules по-прежнему задаются в панели. Для работы импорта и обновлений должны быть доступны и магазин, и панель.

Обращение Ответ
Открытие /s/<token> в браузере Страница инструкций подключения.
Предпросмотр ссылки в Telegram и других поддерживаемых сервисах Та же страница инструкций.
Импорт или обновление /s/<token> в VPN-приложении Профиль подписки без JSON-обёртки API.
/s/<token>?view=page Явный выбор страницы инструкций.
/s/<token>?view=page&platform=android&app=happ Инструкция для выбранной ОС и приложения.
/s/<token>?view=subscription Явный выбор профиля подписки.
/s/<token>/<format> Профиль в формате stash, singbox, mihomo, json, v2ray-json или clash.

Без явного выбора шлюз определяет ответ по заголовкам запроса, включая User-Agent и Accept. Запрос профиля требует непустой User-Agent; запросы HEAD не поддерживаются. Суффикс формата нельзя сочетать с ?view=page.

Параметр platform выбирает ключ ОС из конфигурации инструкций, а необязательный app — идентификатор id приложения внутри этой ОС. Можно передать только platform. Например, /install?platform=android&app=happ открывает личную инструкцию, а /s/<token>?view=page&platform=android&app=happ — публичную. Используйте ключи и идентификаторы из своей конфигурации. Неизвестные значения не блокируют открытие: экран использует обычный выбор ОС и доступного приложения. Параметры относятся к отображению инструкции и не меняют права доступа или формат профиля подписки.

В админке откройте Система → Настройки → Инструкции подключения. Сначала проверьте выдачу профиля, затем переключите ссылки, которые магазин выдаёт пользователям.

Настройка По умолчанию Назначение
SUBSCRIPTION_GATEWAY_ENABLED True Переключатель «Выдавать подписку по публичной ссылке» разрешает приложениям импортировать и обновлять профиль через /s/. Отключение сохраняет страницу инструкций, но прекращает выдачу профиля.
SUBSCRIPTION_LINK_MODE panel Переключатель «Использовать встроенную в Minishop ссылку подписки» выбирает minishop вместо panel для ссылок в боте и инструкциях подключения. Требуется включённый шлюз.
SUBSCRIPTION_MINI_APP_URL Не задан Публичный базовый адрес вашего Web App для формирования /s/-ссылок. Шлюз не берёт его из присланного клиентом Host.
SUBSCRIPTION_GATEWAY_REWRITE_PROFILE_PAGE_URL False Заменяет заголовок profile-web-page-url ответа панели ссылкой на инструкции Minishop с ?view=page. Без этой настройки сохраняется адрес, заданный в панели.

Пример настроек .env после проверки шлюза:

SUBSCRIPTION_MINI_APP_URL=https://app.example.com
SUBSCRIPTION_GATEWAY_ENABLED=True
SUBSCRIPTION_LINK_MODE=minishop
SUBSCRIPTION_GATEWAY_REWRITE_PROFILE_PAGE_URL=False

После изменения .env пересоздайте контейнеры по инструкции развёртывания. Сохранённые настройки админки могут переопределять значения .env; проверьте фактические переключатели.

Режим minishop не заменяет автоматически адреса, уже импортированные в приложения. Прямые ссылки панели продолжают работать; для перехода на домен магазина пользователь должен заново импортировать выданную /s/-ссылку.

При обновлении установки, которая выдавала публичные инструкции до появления шлюза:

  1. Примените миграцию 0088_bind_install_share_to_panel_link до передачи запросов новой версии backend. Добавляемое поле допускает пустое значение и совместимо с предыдущей версией backend.

  2. Из доверенного окружения оператора с доступом к БД и панели выполните проверку:

    PYTHONPATH=backend python -m scripts.bind_install_share_tokens

    Проверьте счётчики verified и skipped. Для сохранения проверенных привязок выполните ту же команду с --apply. Она связывает существующие активные токены с текущей ссылкой панели. Пропущенные записи разбирайте вручную: публичный запрос не привязывает старый токен. Утилита не определяет, кому ранее могла стать известна ссылка; при сомнениях перевыпустите доступ.

  3. Обновите backend и конфигурацию Nginx во frontend с отдельным маршрутом /s/. При раздельном размещении frontend передаёт MINISHOP_EDGE_TOKEN; прямой доступ к защищённому маршруту backend должен быть закрыт. См. раздельное размещение frontend и backend.

  4. Убедитесь, что выдача подписки по публичной ссылке включена. Пока оставьте SUBSCRIPTION_LINK_MODE=panel и выполните проверки ниже. Старое значение .env или сохранённая настройка админки могут отключать шлюз, несмотря на True по умолчанию.

  5. После успешных проверок включите режим minishop в админке или .env.

Для новой установки перенос старых токенов не нужен.

Используйте действующую ссылку своей тестовой подписки и настоящий публичный адрес магазина, чтобы проверить всю цепочку прокси:

  1. Откройте ссылку в браузере и проверьте предпросмотр в Telegram: должны открываться инструкции подключения.
  2. Импортируйте ссылку в используемые VPN-приложения, затем обновите профиль повторно. Проверьте нужные форматы, учёт HWID-устройств и Response Rules панели.
  3. Сравните ответы прямой ссылки панели и шлюза с одинаковыми заголовками клиента: содержимое профиля и разрешённые заголовки должны соответствовать ответу панели.
  4. На тестовой подписке перевыпустите доступ: старая публичная ссылка должна перестать работать, новая — успешно импортироваться.
  5. Проверьте сбой связи с панелью через внешний адрес: клиент должен получить ошибку 5xx, а не HTML-страницу прокси со статусом 200.

Для ручного запроса профиля используйте GET, а не curl -I:

curl --fail-with-body -A 'Happ/1.0' \
  'https://app.example.com/s/<token>?view=subscription'

Замените адрес и <token> своими значениями. Ссылка даёт доступ к подписке: не публикуйте её в отчётах, скриншотах и сообщениях поддержки.

Статус Что проверить
400 Для профиля нужен User-Agent; view допускает одно значение page или subscription. Нельзя запрашивать страницу вместе с суффиксом формата.
404 Токен отсутствует, отозван или больше не соответствует текущей активной подписке и ссылке панели. Получите новую ссылку из аккаунта.
429 Превышена общая квота запросов публичных инструкций и подписки. Повторите запрос с учётом Retry-After.
502 Ошибка связи с источником или недопустимый ответ панели: перенаправление, HTML, пустой успешный профиль, профиль больше 8 МиБ либо суммарные заголовки больше 64 КиБ. Проверьте PANEL_API_URL и доступность /api/sub/<shortUuid>.
503 Шлюз отключён, проверка доступа временно недоступна, исчерпан лимит одновременных обращений или недоступен настроенный Redis для общих квот.
504 Истекло время ожидания ответа панели. Проверьте её доступность из backend.

Панель также может вернуть свой статус ошибки. Для диагностики используйте логи backend и прокси, справочник переменных и настройки инструкций.

При перевыпуске пользователем или администратором Minishop отзывает все локальные публичные разрешения этого пользователя панели и сохраняет отзыв до обращения к панели. Даже если панель вернёт ошибку, прежние токены остаются отозванными. Запрос из авторизованного аккаунта выдаёт новый токен, связанный с текущим shortUuid. Изменение shortUuid непосредственно в панели также делает старый токен недействительным при следующем обращении.

Публичный JSON API и выдача профиля проверяют разрешение в БД и актуальную запись пользователя панели при каждом запросе. Прежний публичный кеш на 300 секунд не используется.

Возврат SUBSCRIPTION_LINK_MODE=panel меняет только вновь выдаваемые ссылки. Оставьте шлюз включённым, пока пользователи могут хранить /s/-адреса в приложениях.

Для экстренной остановки выключите SUBSCRIPTION_GATEWAY_ENABLED: запросы профиля будут получать 503, а пользователям потребуется заново импортировать прямую ссылку панели. Откат backend на версию без шлюза также нарушит обновления уже импортированных /s/-профилей.

Сервис доставки получает привязку ресурса, разрешённую сервером, и запрос клиента. Адаптер Remnawave обращается к настроенному /api/sub/<shortUuid> и передаёт содержимое, статус и безопасные заголовки ответа без стандартной JSON-обёртки {"ok": …}. Он не отправляет административный Bearer-токен, не следует перенаправлениям, не пересылает пользовательские cookies, не кеширует профили и не принимает адрес источника из публичного запроса. Если оператор настроил PANEL_API_COOKIE для защищённого прокси панели, используется именно этот cookie.

Имена заголовков обрабатываются без учёта регистра, включая user-agent. Крупные настройки клиента в безопасных заголовках передаются без усечения: предел всех заголовков ответа панели — 64 КиБ с именами и разделителями. Превышение возвращает 502, а не профиль с потерянными настройками. Штатный Nginx имеет буфер 128 КиБ с запасом под служебные заголовки магазина; внешний прокси оператора также должен принимать такой ответ.

Увеличьте буфер заголовков на каждом внешнем Nginx перед frontend. Буферы внутреннего frontend не меняют ограничения внешнего прокси. Оба штатных примера Nginx и конфигурации для собственного ingress задают для location ^~ /s/:

proxy_buffer_size 128k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 128k;

Проверьте конфигурацию nginx -t и перечитайте её перед повторным импортом. Ошибка upstream sent too big header с внешним 502 при успешном ответе frontend означает недостаточный буфер внешнего прокси. Проверяйте успешный профиль с длинным x-client-settings и повторное обновление через публичный HTTPS-вход; маленький ответ 404 не проверяет этот предел. Сохраняйте access_log off для /s/, поскольку адрес содержит секретный токен.

Шлюз передаёт x-hwid, x-device-os, x-ver-os, x-device-model и User-Agent в публичный endpoint подписки того же пользователя Panel. Адрес клиента передаётся через x-remnawave-real-ip: входящее значение этого заголовка удаляется и заменяется адресом, вычисленным по цепочке доверенных прокси. Для собственного reverse proxy проверьте TRUSTED_PROXIES; непроверенным заголовкам клиентского IP шлюз не доверяет. Для поддерживаемого внутреннего HTTP endpoint Panel адаптер сам задаёт X-Forwarded-For с тем же доверенным адресом и X-Forwarded-Proto: https, требуемые проверкой reverse proxy панели. Входящие значения этих заголовков удаляются; административный Bearer-токен в запрос подписки не добавляется.

Ограничение устройств должно быть включено и настроено в Remnawave, а приложение должно отправлять поддерживаемый HWID. Шлюз не создаёт HWID за клиента и сохраняет статусы отказа и заголовки x-hwid-* панели. Подробности — в документации Remnawave. HWID регистрируется при импорте или обновлении подписки: он не идентифицирует каждое VPN-подключение. Учёт трафика и подключений выполняют Panel и её ноды.

Токен содержит 128 случайных бит и не зависит от ID пользователя. HTML, профиль и публичный JSON API используют общие квоты: 240 запросов в минуту на IP и 60 на токен. Redis обеспечивает общие счётчики для процессов и реплик; без него квоты действуют отдельно в каждом процессе. Отказ настроенного Redis возвращает 503. Эти ограничения не заменяют защиту внешнего прокси от массовой нагрузки.

Ссылка является секретом доступа. Квоты и случайный токен защищают от подбора, но не от использования уже раскрытой ссылки. При утечке перевыпустите доступ.

Маршрут выдачи профиля не входит в JSON-контракт OpenAPI: его ответ зависит от клиента и панели. JSON-маршрут /api/subscription-guides/public/<token> остаётся в OpenAPI, сохраняет поле config и возвращает guide_document с schemaVersion: 1. Документ строится из проверенного конфига Remnawave v1, содержит упорядоченные идентификаторы платформ и типизированные цели кнопок. Разрешение конкретной подписки выполняется отдельно; сервис доставки профиля не читает документ инструкций.

Ответы шлюза запрещают кеширование и индексацию. В стандартной конфигурации frontend исключает из журнала доступа /s/, публичный JSON API инструкций и запросы с такой ссылкой в Referer. Backend маскирует токены в URL, заголовках журнала доступа и атрибутах пути отчётов об ошибках, сохраняя статусы для диагностики. Для двух публичных маршрутов штатный Nginx также отключает журнал ошибок, который не умеет маскировать URL: диагностируйте их по безопасному журналу backend. При настройке собственного внешнего прокси исключите секретные ссылки из его журналов и отчётов. Обновление не удаляет токены из ранее записанных логов: ограничьте доступ к ним и перевыпустите ссылки, если они могли стать известны другим.