diff --git a/README.md b/README.md index 621cbcd..290c333 100644 --- a/README.md +++ b/README.md @@ -1,564 +1,82 @@ # Remnawave Minishop -Remnawave Minishop — это Telegram-бот **и** Web App (Mini App) для автоматизации продажи и управления подписками панели **Remnawave**. Бот закрывает сценарий покупки, продления и работы с поддержкой прямо в чате, а Web App в едином интерфейсе показывает ссылку подключения, остаток времени, трафик, оплату и устройства, поддерживая вход через Telegram Mini Apps `initData`, новый Telegram OAuth / OpenID Connect Login и одноразовый код по email. Под капотом — интеграция с API Remnawave для управления пользователями и подписками и набор платёжных шлюзов для приёма платежей. +Remnawave Minishop - Telegram-бот и Web App (Mini App) для продажи и управления подписками Remnawave. Бот обрабатывает регистрацию, оплату, продление, пробный период, промокоды, рефералов и поддержку в чате. Web App показывает ссылку подключения, срок действия, трафик, оплату, устройства и вход по Telegram Mini Apps `initData`, Telegram OAuth / OpenID Connect и одноразовому email-коду. -> 🍴 **Это глубоко переработанный форк [kavore/remnawave-tg-shop](https://github.com/kavore/remnawave-tg-shop).** Здесь добавлены полноценный Web App / Mini App, вход по email и многое другое. Возможна миграция. +Проект является переработанным форком [kavore/remnawave-tg-shop](https://github.com/kavore/remnawave-tg-shop). Для переноса данных из прежнего стека используйте [инструкцию по миграции](docs/migration-to-minishop.md). -## ✨ Ключевые возможности +## Возможности -### Для пользователей: -- **Регистрация и выбор языка:** Поддержка русского и английского языков. -- **Просмотр подписки:** Пользователи могут видеть статус своей подписки, дату окончания и ссылку на конфигурацию. -- **Web App (Mini App):** отдельный веб-интерфейс для просмотра ссылки подключения, остатка времени и оплаты подписки. -- **Вход по email:** вход и регистрация в Web App по коду из письма, а также привязка email и Telegram к одному аккаунту. -- **Мои устройства:** Опциональный раздел для просмотра и отключения подключенных устройств (активируется через переменную `MY_DEVICES_SECTION_ENABLED`). -- **Пробная подписка:** Система пробных подписок для новых пользователей (активируется вручную по кнопке). -- **Промокоды:** Возможность применять промокоды для получения скидок или бонусных дней. -- **Реферальная программа:** Пользователи могут приглашать друзей и получать за это бонусные дни подписки. -- **Оплата:** Поддержка оплаты через YooKassa, FreeKassa (REST API), Platega, SeverPay, CryptoPay и Telegram Stars. +Для пользователей: -### Для администраторов: -- **Защищенная админ-панель:** Доступ только для администраторов, указанных в `ADMIN_IDS`. -- **Статистика:** Просмотр статистики использования бота (общее количество пользователей, забаненные, активные подписки), недавние платежи и статус синхронизации с панелью. -- **Управление пользователями:** Блокировка/разблокировка пользователей, просмотр списка забаненных и детальной информации о пользователе. -- **Рассылка:** Отправка сообщений всем пользователям, пользователям с активной или истекшей подпиской. -- **Управление промокодами:** Создание и просмотр промокодов. -- **Синхронизация с панелью:** Ручной запуск синхронизации пользователей и подписок с панелью Remnawave. -- **Логи действий:** Просмотр логов всех действий пользователей. +- регистрация с выбором русского или английского языка; +- просмотр статуса подписки, даты окончания, ссылки подключения и трафика; +- покупка подписок, пакетов трафика, докупка трафика и устройств по настроенному каталогу тарифов; +- Web App / Mini App с входом через Telegram или email; +- пробный период, промокоды и реферальная программа; +- оплата через YooKassa, FreeKassa, Platega, SeverPay, CryptoPay и Telegram Stars; +- раздел "Мои устройства" при включенном `MY_DEVICES_SECTION_ENABLED`. -## 🚀 Технологии +Для администраторов: -- **Python 3.12** -- **Aiogram 3.x:** Асинхронный фреймворк для Telegram ботов. -- **aiohttp:** Для запуска веб-сервера (вебхуки). -- **SQLAlchemy 2.x & asyncpg:** Асинхронная работа с базой данных PostgreSQL. -- **YooKassa, FreeKassa API, Platega, SeverPay, aiocryptopay:** Интеграции с платежными системами. -- **Pydantic:** Для управления настройками из `.env` файла. -- **Docker & Docker Compose:** Для контейнеризации и развертывания. +- админ-панель для пользователей из `ADMIN_IDS`; +- статистика пользователей, подписок, платежей и синхронизации с Remnawave; +- блокировка пользователей, рассылки, промокоды и логи действий; +- ручная синхронизация пользователей и подписок с панелью. -## ⚙️ Установка и запуск +## Документация -### Предварительные требования +- [Настройка окружения](docs/configuration.md) - основные переменные `.env`, платежи, Remnawave, пробный период и секреты. +- [Тарифы](docs/tariffs.md) - каталог тарифов, period- и traffic-модели, докупки, смена тарифа, HWID-лимиты и обработка трафика. +- [Web App / Mini App](docs/webapp.md) - отдельный порт, домен, Telegram OAuth, email-вход и реферальные ссылки. +- [Развертывание](docs/deployment.md) - Docker Compose, reverse proxy, Nginx, Caddy, вебхуки и запуск из образа. +- [Миграция с remnawave-tg-shop](docs/migration-to-minishop.md) - перенос данных из прежнего стека. -- Установленные Docker и Docker Compose. -- Рабочая панель Remnawave. -- Токен Telegram-бота. -- Данные для подключения к платежным системам (YooKassa, CryptoPay и т.д.). +## Быстрый старт -### Шаги установки +Требования: -1. **Клонируйте репозиторий:** - ```bash - git clone https://github.com/3252a8/remnawave-minishop - cd remnawave-minishop - ``` - -2. **Создайте и настройте файл `.env`:** - Скопируйте `.env.example` в `.env` и заполните своими данными. - ```bash - cp .env.example .env - nano .env - ``` - Ниже перечислены ключевые переменные. - -
- Основные настройки - - | Переменная | Описание | Пример | - | --- | --- | --- | - | `BOT_TOKEN` | **Обязательно.** Токен вашего Telegram-бота. | `1234567890:ABC-DEF1234ghIkl-zyx57W2v1u123ew11` | - | `ADMIN_IDS` | **Обязательно.** ID администраторов в Telegram через запятую. | `12345678,98765432` | - | `DEFAULT_LANGUAGE` | Язык по умолчанию для новых пользователей. | `ru` | - | `SUPPORT_LINK` | (Опционально) Ссылка на поддержку. | `https://t.me/your_support` | - | `PRIVACY_POLICY_URL` | (Опционально) Ссылка на политику конфиденциальности, показывается внизу Web App. | `https://example.com/privacy` | - | `USER_AGREEMENT_URL` | (Опционально) Ссылка на пользовательское соглашение, показывается внизу Web App. | `https://example.com/agreement` | - | `SUBSCRIPTION_MINI_APP_URL` | (Опционально) Публичный URL Mini App для показа подписки. Если задан, кнопка «Моя подписка» откроет Web App. | `https://app.domain.com/` | - | `WEBAPP_ENABLED` | Включить Web App в том же контейнере, но на отдельном порту. | `true` | - | `WEBAPP_SERVER_PORT` | Внутренний порт Web App. | `8081` | - | `WEBAPP_TITLE` | Заголовок Web App. | `Моя подписка` | - | `WEBAPP_PRIMARY_COLOR` | Основной цвет Web App. | `#00fe7a` | - | `WEBAPP_LOGO_URL` | (Опционально) URL логотипа Web App. Если значение пустое, логотип не показывается вообще; если задано, он отображается в шапке и на экране логина. | `https://domain.com/logo.png` | - | `TELEGRAM_OAUTH_CLIENT_ID` | Client ID для нового Telegram OAuth / OpenID Connect Login из BotFather. Если пусто, используется числовой ID из `BOT_TOKEN`. | `1234567890` | - | `TELEGRAM_OAUTH_CLIENT_SECRET` | Client Secret из BotFather для Telegram OAuth Authorization Code Flow. | `tg_oauth_secret` | - | `TELEGRAM_OAUTH_REQUEST_ACCESS` | Дополнительные разрешения Telegram Login через запятую: `write`, `phone`. Пустое значение запрашивает только OpenID profile. | `write` | - | `SMTP_HOST` | SMTP-сервер для кодов входа по email. Для Brevo: `smtp-relay.brevo.com`. | `smtp-relay.brevo.com` | - | `SMTP_PORT` | SMTP-порт. Для Brevo обычно используется 587 с STARTTLS. | `587` | - | `SMTP_FALLBACK_PORTS` | Дополнительные SMTP-порты через запятую. Пробуются после `SMTP_PORT`; порт `465` автоматически используется через SSL. Для Brevo удобно оставить `2525,465`. | `2525,465` | - | `SMTP_TIMEOUT_SECONDS` | Timeout для каждой SMTP-попытки подключения и отправки. | `30` | - | `SMTP_USERNAME` / `SMTP_PASSWORD` | Логин и SMTP key/password из Brevo. Если не заданы вместе с `SMTP_FROM_EMAIL`, вход по email скрывается. | `user@smtp-brevo.com` | - | `SMTP_FROM_EMAIL` / `SMTP_FROM_NAME` | Подтвержденный отправитель и отображаемое имя отправителя для писем с кодом. | `no-reply@example.com` | - | `EMAIL_CODE_TTL_SECONDS` | Срок действия кода подтверждения email. | `600` | - | `EMAIL_CODE_RESEND_SECONDS` | Минимальная пауза между отправками кода на один email. | `60` | - | `EMAIL_CODE_MAX_ATTEMPTS` | Максимум попыток на один конкретный код. | `5` | - | `BRUTE_FORCE_MAX_FAILURES` | Максимум неудачных попыток в окне защиты от перебора. | `5` | - | `BRUTE_FORCE_WINDOW_SECONDS` | Длительность окна, в котором считаются неудачные попытки. | `900` | - | `BRUTE_FORCE_LOCK_SECONDS` | Время временной блокировки после превышения лимита. | `1800` | - | `MY_DEVICES_SECTION_ENABLED` | Включить раздел «Мои устройства» в меню подписки (`true`/`false`). | `false` | - | `WEBAPP_SESSION_SECRET` | (Опционально) HMAC-секрет для подписи сессий Web App. Если пусто — генерируется при старте, но тогда сессии станут невалидными после перезапуска контейнера. Для прода задайте явно. | `см. раздел «Генерация секретов»` | - | `WEBHOOK_SECRET_TOKEN` | (Опционально) Secret token для проверки подлинности вебхуков Telegram. Если пусто — генерируется при старте. Для прода задайте явно, чтобы значение пережило рестарт. | `см. раздел «Генерация секретов»` | - | `REQUIRED_CHANNEL_ID` | (Опционально) ID канала, на который пользователь должен подписаться перед использованием. Оставьте пустым, если проверка не нужна. | `-1001234567890` | - | `REQUIRED_CHANNEL_LINK` | (Опционально) Публичная ссылка или invite на канал для кнопки «Проверить подписку». | `https://t.me/your_channel` | -
- -
- Настройки платежей и вебхуков - - | Переменная | Описание | - | --- | --- | - | `WEBHOOK_BASE_URL` | **Обязательно.** Базовый URL для вебхуков, например `https://your.domain.com`. | - | `WEB_SERVER_HOST` | Хост для веб-сервера (по умолчанию `0.0.0.0`). | - | `WEB_SERVER_PORT` | Порт для веб-сервера (по умолчанию `8080`). | - | `WEBAPP_SERVER_HOST` | Хост отдельного веб-сервера Mini App (по умолчанию `0.0.0.0`). | - | `WEBAPP_SERVER_PORT` | Порт отдельного веб-сервера Mini App (по умолчанию `8081`). | - | `PAYMENT_METHODS_ORDER` | (Опционально) Порядок отображения кнопок оплаты через запятую. Поддерживаемые ключи: `severpay`, `freekassa`, `platega`, `yookassa`, `stars`, `cryptopay`. Первый будет сверху. | - | `YOOKASSA_ENABLED` | Включить/выключить YooKassa (`true`/`false`). | - | `YOOKASSA_SHOP_ID` | ID вашего магазина в YooKassa. | - | `YOOKASSA_SECRET_KEY` | Секретный ключ магазина YooKassa. | - | `YOOKASSA_AUTOPAYMENTS_ENABLED` | Включить автопродление (сохранение карт, автосписания, управление способами оплаты). | - | `YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING` | Требовать обязательную привязку карты при оплате с автосписанием. Установите `false`, чтобы пользователю показывался чекбокс «Сохранить карту». | - | `NALOGO_INN` | ИНН для авторизации в nalog.ru (самозанятый). | - | `NALOGO_PASSWORD` | Пароль для авторизации в nalog.ru (самозанятый). | - | `CRYPTOPAY_ENABLED` | Включить/выключить CryptoPay (`true`/`false`). | - | `CRYPTOPAY_TOKEN` | Токен из вашего CryptoPay App. | - | `FREEKASSA_ENABLED` | Включить/выключить FreeKassa (`true`/`false`). | - | `FREEKASSA_MERCHANT_ID` | ID вашего магазина в FreeKassa. | - | `FREEKASSA_API_KEY` | API-ключ для запросов к FreeKassa REST API. | - | `FREEKASSA_SECOND_SECRET` | Секретное слово №2 — используется для проверки уведомлений от FreeKassa. | - | `FREEKASSA_PAYMENT_URL` | (Опционально, legacy SCI) Базовый URL платёжной формы FreeKassa. По умолчанию `https://pay.freekassa.ru/`. | - | `FREEKASSA_PAYMENT_IP` | Внешний IP вашего сервера, который будет передаваться в запрос оплаты. | - | `FREEKASSA_PAYMENT_METHOD_ID` | ID метода оплаты через магазин FreeKassa. По умолчанию `44`. | - | `STARS_ENABLED` | Включить/выключить Telegram Stars (`true`/`false`). | - | `PLATEGA_ENABLED` | Включить/выключить Platega (`true`/`false`). | - | `PLATEGA_MERCHANT_ID` | MerchantId из личного кабинета Platega. | - | `PLATEGA_SECRET` | API секрет для запросов Platega. | - | `PLATEGA_PAYMENT_METHOD` | ID способа оплаты (2 — SBP QR, 10 — РФ карты, 12 — международные карты, 13 — crypto). | - | `PLATEGA_RETURN_URL` | (Опционально) URL редиректа после успешной оплаты. По умолчанию ссылка на бота. | - | `PLATEGA_FAILED_URL` | (Опционально) URL редиректа при ошибке/отмене. По умолчанию как `PLATEGA_RETURN_URL`. | - | `SEVERPAY_ENABLED` | Включить/выключить SeverPay (`true`/`false`). | - | `SEVERPAY_MID` | MID магазина в SeverPay. | - | `SEVERPAY_TOKEN` | Секрет/токен для подписи запросов SeverPay. | - | `SEVERPAY_BASE_URL` | (Опционально) Базовый URL API SeverPay. По умолчанию `https://severpay.io/api/merchant`. | - | `SEVERPAY_RETURN_URL` | (Опционально) URL редиректа после оплаты (по умолчанию ссылка на бота). | - | `SEVERPAY_LIFETIME_MINUTES` | (Опционально) Время жизни платежной ссылки в минутах (30–4320). | -
- -
- Настройки тарифов - - Бот умеет продавать **подписку на срок** (1/3/6/12 мес.) или **пакеты трафика** (`TRAFFIC_PACKAGES=10:199,50:799`). Эти режимы взаимоисключающие — наличие непустой `TRAFFIC_PACKAGES` (или `STARS_TRAFFIC_PACKAGES`) автоматически переключает бот в режим продажи трафика. - - Полное описание обоих режимов, переменных, что происходит при покупке, как ведут себя автопродление, реф-бонусы и триал — вынесено в [docs/tariffs.md](docs/tariffs.md). -
- -
- Настройки панели Remnawave - - | Переменная | Описание | - | --- | --- | - | `PANEL_API_URL` | URL API вашей панели Remnawave. | - | `PANEL_API_KEY` | API ключ для доступа к панели. | - | `PANEL_WEBHOOK_SECRET`| Секретный ключ для проверки вебхуков от панели. | - | `USER_SQUAD_UUIDS` | ID отрядов для новых пользователей. | - | `USER_EXTERNAL_SQUAD_UUID` | Опционально. UUID внешнего отряда (External Squad) из [документации Remnawave](https://docs.rw/api), куда автоматически добавляются новые пользователи. | - | `USER_TRAFFIC_LIMIT_GB`| Лимит трафика в ГБ (0 - безлимит). | - | `USER_HWID_DEVICE_LIMIT`| Лимит устройств (HWID) для новых пользователей (0 - безлимит). | - - > Раздел "Мои устройства" становится доступен пользователям только при включении `MY_DEVICES_SECTION_ENABLED`. Значение лимита устройств при создании записей в панели берётся из `USER_HWID_DEVICE_LIMIT`. -
- -
- Настройки пробного периода - - | Переменная | Описание | - | --- | --- | - | `TRIAL_ENABLED` | Включить/выключить пробный период (`true`/`false`). | - | `TRIAL_DURATION_DAYS`| Длительность пробного периода в днях. | - | `TRIAL_TRAFFIC_LIMIT_GB`| Лимит трафика для пробного периода в ГБ. | -
- -3. **Сгенерируйте секреты (рекомендуется):** - - Переменные `WEBAPP_SESSION_SECRET` и `WEBHOOK_SECRET_TOKEN` могут быть пустыми — тогда они автоматически сгенерируются при каждом старте контейнера. Однако в проде это означает, что после рестарта все сессии Web App станут невалидными, а Telegram придётся перерегистрировать webhook. Поэтому для боевого окружения задайте оба значения вручную. - - Сгенерировать криптостойкие значения можно одной из команд: - - ```bash - # вариант 1 — Python (есть в любом окружении с Python 3) - python -c "import secrets; print(secrets.token_urlsafe(32))" - - # вариант 2 — openssl - openssl rand -base64 32 | tr -d '=+/' | cut -c1-43 - - # вариант 3 — /dev/urandom (Linux/macOS) - head -c 32 /dev/urandom | base64 | tr -d '=+/' | cut -c1-43 - ``` - - Запустите команду дважды и подставьте полученные значения в `.env`: - - ```env - WEBAPP_SESSION_SECRET=<первое_значение> - WEBHOOK_SECRET_TOKEN=<второе_значение> - ``` - - > ⚠️ Не используйте одно и то же значение для обеих переменных и не коммитьте `.env` в git. - -4. **Запустите контейнеры:** - ```bash - docker compose up -d - ``` - Эта команда соберёт образ из `Dockerfile` (Python + сборка Web App на Node) и запустит сервис в фоновом режиме. Если нужен запуск из готового образа GHCR — используйте `docker-compose-remote-server.yml`. - -5. **Настройка вебхуков (Обязательно):** - Вебхуки являются **обязательным** компонентом для работы бота, так как они используются для получения уведомлений от платежных систем (YooKassa, FreeKassa, CryptoPay, Platega, SeverPay) и панели Remnawave. - - Вам понадобится обратный прокси (например, Nginx) для обработки HTTPS-трафика и перенаправления запросов на контейнер с ботом. - - **Пути для перенаправления:** - - `https://<ваш_домен>/webhook/yookassa` → `http://remnawave-minishop:/webhook/yookassa` - - `https://<ваш_домен>/webhook/freekassa` → `http://remnawave-minishop:/webhook/freekassa` - - `https://<ваш_домен>/webhook/platega` → `http://remnawave-minishop:/webhook/platega` - - `https://<ваш_домен>/webhook/severpay` → `http://remnawave-minishop:/webhook/severpay` - - `https://<ваш_домен>/webhook/cryptopay` → `http://remnawave-minishop:/webhook/cryptopay` - - `https://<ваш_домен>/webhook/panel` → `http://remnawave-minishop:/webhook/panel` - - **Для Telegram:** Бот автоматически установит вебхук, если в `.env` указан `WEBHOOK_BASE_URL`. Путь будет `https://<ваш_домен>/`. - - Где `remnawave-minishop` — это имя сервиса из `docker-compose.yml`, а `` — порт, указанный в `.env`. - - **Отдельный порт Web App:** - - `https://<домен_web_app>/` → `http://remnawave-minishop:/` - - Web App не должен проксироваться на `WEB_SERVER_PORT`: этот порт оставьте для Telegram, платежных и Remnawave webhooks. - -6. **Просмотр логов:** - ```bash - docker compose logs -f remnawave-minishop - ``` - - > 💡 Если включена проверка подписки на канал (`REQUIRED_CHANNEL_ID`), добавьте бота администратором в этот канал. Пользователь увидит кнопку «Проверить подписку», и, после первого успешного подтверждения, дальнейшие действия блокироваться не будут. - -### Настройка Web App / Mini App - -Web App запускается в том же контейнере, что и бот, но слушает отдельный порт `WEBAPP_SERVER_PORT` (по умолчанию `8081`). Внутри Telegram пользователь авторизуется через Telegram Mini Apps `initData`; если страницу открыть вне Telegram, используется новый Telegram OAuth / OpenID Connect Authorization Code Flow с PKCE, callback `/auth/telegram/callback`, `nonce` и серверной проверкой `id_token` по JWKS Telegram. Старый Login Widget больше не используется в UI. Также доступен вход по email через одноразовый код из письма, если настроен SMTP: после отправки письма код вводится в отдельном модальном окне подтверждения. После успешного входа страница обновляет данные сразу, без сообщений боту. - -1. Укажите в `.env` публичный URL Web App и порт: - - ```env - WEBAPP_ENABLED=True - WEBAPP_SERVER_HOST=0.0.0.0 - WEBAPP_SERVER_PORT=8081 - SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/ - WEBAPP_TITLE="Моя подписка" - WEBAPP_PRIMARY_COLOR="#00fe7a" - WEBAPP_LOGO_URL= - TELEGRAM_OAUTH_CLIENT_ID= - TELEGRAM_OAUTH_CLIENT_SECRET= - TELEGRAM_OAUTH_REQUEST_ACCESS=write - SMTP_HOST=smtp-relay.brevo.com - SMTP_PORT=587 - SMTP_FALLBACK_PORTS=2525,465 - SMTP_USERNAME= - SMTP_PASSWORD= - SMTP_FROM_EMAIL=no-reply@domain.com - ``` - - Если основной порт не отвечает, отправка письма автоматически пробует fallback-порты из `SMTP_FALLBACK_PORTS`. Для Brevo типичная схема: `587` с STARTTLS, затем `2525`, затем `465` через SSL. - -2. Убедитесь, что `docker-compose.yml` публикует порт Web App: - - ```yaml - ports: - - 127.0.0.1:8080:8080 - - 127.0.0.1:${WEBAPP_SERVER_PORT:-8081}:${WEBAPP_SERVER_PORT:-8081} - ``` - -3. Проксируйте отдельный домен или location на порт Web App: - - ```nginx - upstream remnawave-minishop-webapp { - server remnawave-minishop:8081; - } - - server { - server_name app.domain.com; - listen 443 ssl; - http2 on; - - ssl_certificate "/etc/nginx/ssl/app_fullchain.pem"; - ssl_certificate_key "/etc/nginx/ssl/app_privkey.key"; - - location / { - proxy_pass http://remnawave-minishop-webapp; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } - } - ``` - -4. В BotFather настройте бота, Mini App и Telegram OAuth Login: - - - `@BotFather` → `/mybots` → выберите бота. - - **Bot Settings → Domain**: укажите домен без протокола и пути, например `app.domain.com`. - - **Bot Settings → Mini Apps**: задайте URL Mini App, например `https://app.domain.com/`. - - **Bot Settings → Web Login**: если BotFather показывает кнопку `Switch to OpenID Connect Login`, нажмите ее. - - **Bot Settings → Web Login**: скопируйте Client ID и Client Secret в `TELEGRAM_OAUTH_CLIENT_ID` и `TELEGRAM_OAUTH_CLIENT_SECRET`. - - **Web Login → Allowed URLs**: добавьте: - `https://app.domain.com/` - `https://app.domain.com/auth/telegram/callback` - - `TELEGRAM_OAUTH_REQUEST_ACCESS=write` разрешает боту написать пользователю после логина. Если дополнительные разрешения не нужны, оставьте переменную пустой. - -5. Перезапустите контейнер: - - ```bash - docker compose up -d --build - ``` - -После этого кнопка «Личный кабинет» в меню бота откроет Web App. Рядом доступна кнопка «Бот-меню» для открытия расширенного интерфейса в чате без команды `/tg`, но основной сценарий управления подпиской удобнее проходить в личном кабинете. Web App показывает текущую ссылку подключения, остаток времени, трафик, оплату и блок аккаунта. Пользователь может привязать email к Telegram-аккаунту через код из письма или привязать Telegram к email-аккаунту через Telegram OAuth Login. После привязки вход работает обоими способами. - -Реферальные ссылки доступны в двух форматах: Telegram deep-link `https://t.me/?start=ref_u` и Web App ссылка с query-параметром `ref=u`. Web App учитывает `ref`, `start`, `start_param` и Telegram Mini Apps `start_param`, сохраняет найденный параметр до авторизации и передаёт его в Telegram OAuth и email-вход, чтобы регистрация корректно привязалась к пригласившему. - -Для email-регистраций пользователь в панели Remnawave создается с анонимным username вида `em_`; email добавляется в описание пользователя панели и, если API панели принимает поле email, передается отдельным полем. Для Telegram-регистраций сохраняется существующая схема `tg_`. - -## Подробная инструкция для развертывания на сервере с панелью Remnawave - -### 1. Клонирование репозитория +- Docker и Docker Compose; +- рабочая панель Remnawave; +- токен Telegram-бота; +- параметры хотя бы одного платежного провайдера. ```bash -git clone https://github.com/3252a8/remnawave-minishop && cd remnawave-minishop +git clone https://github.com/3252a8/remnawave-minishop +cd remnawave-minishop +cp .env.example .env +nano .env +docker compose up -d --build +docker compose logs -f remnawave-minishop ``` -### 2. Настройка переменных окружения +Минимально заполните в `.env`: + +- `BOT_TOKEN` - токен Telegram-бота; +- `ADMIN_IDS` - Telegram ID администраторов через запятую; +- `WEBHOOK_BASE_URL` - публичный URL вебхуков; +- `PANEL_API_URL`, `PANEL_API_KEY`, `PANEL_WEBHOOK_SECRET` - доступ к Remnawave; +- `USER_SQUAD_UUIDS` - Internal Squads для пользователей; +- настройки платежного провайдера; +- `SUBSCRIPTION_MINI_APP_URL`, если используется Web App. + +Для каталога тарифов используется `TARIFFS_CONFIG_PATH` со значением по умолчанию `config/tariffs.json`. Пример формата лежит в [config/tariffs.example.json](config/tariffs.example.json), подробности - в [docs/tariffs.md](docs/tariffs.md). + +## Полезные команды ```bash -cp .env.example .env && nano .env -``` +# Локальная сборка и запуск +docker compose up -d --build -**Обязательные поля для заполнения:** -- `BOT_TOKEN` - токен телеграмм бота, например, `234567890:ABC-DEF1234ghIkl-zyx57W2v1u123ew11` -- `ADMIN_IDS` - TG ID администраторов, например, `12345678,98765432` и т.д. (через запятую без пробелов) -- `WEBHOOK_BASE_URL` - Обязательно. Базовый URL для вебхуков, например `https://webhook.domain.com` -- `PANEL_API_URL` - URL API вашей панели Remnawave (например, `http://remnawave:3000/api` или `https://panel.domain.com/api`) -- `PANEL_API_KEY` - API ключ для доступа к панели (генерируется из UI-интерфейса панели) -- `PANEL_WEBHOOK_SECRET` - Секретный ключ для проверки вебхуков от панели (берётся из `.env` самой панели) -- `USER_SQUAD_UUIDS` - ID отрядов для новых пользователей +# Логи приложения +docker compose logs -f remnawave-minishop -### 3. Настройка Reverse Proxy (Nginx) +# Запуск с Caddy +docker compose -f docker-compose-caddy.yml up -d --build -Перейдите в директорию конфигурации Nginx панели Remnawave: - -```bash -cd /opt/remnawave/nginx && nano nginx.conf -``` - -Добавьте в `nginx.conf` следующую конфигурацию: - -```nginx -upstream remnawave-minishop { - server remnawave-minishop:8080; -} - -map $http_upgrade $connection_upgrade { - default upgrade; - "" close; -} - -server { - server_name webhook.domain.com; # Домен для отправки Webhook'ов - listen 443 ssl; - http2 on; - - ssl_certificate "/etc/nginx/ssl/webhook_fullchain.pem"; - ssl_certificate_key "/etc/nginx/ssl/webhook_privkey.key"; - ssl_trusted_certificate "/etc/nginx/ssl/webhook_fullchain.pem"; - - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection $connection_upgrade; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - proxy_set_header X-Forwarded-Port $server_port; - proxy_send_timeout 60s; - proxy_read_timeout 60s; - proxy_intercept_errors on; - error_page 400 404 500 502 @redirect; - - location / { - proxy_pass http://remnawave-minishop$request_uri; - } - - location @redirect { - return 404; - } -} -``` - -### 4. Выпуск SSL-сертификата для домена webhook - -Убедитесь, что установлены необходимые компоненты, а также откройте 80 порт: - -```bash -sudo apt-get install cron socat -curl https://get.acme.sh | sh -s email=EMAIL && source ~/.bashrc -ufw allow 80/tcp && ufw reload -``` - -Выпустите сертификат: - -```bash -acme.sh --set-default-ca --server letsencrypt -acme.sh --issue --standalone -d 'webhook.domain.com' \ - --key-file /opt/remnawave/nginx/webhook_privkey.key \ - --fullchain-file /opt/remnawave/nginx/webhook_fullchain.pem -``` - -### 5. Добавление сертификатов в Docker Compose Nginx - -Отредактируйте `docker-compose.yml` панели Nginx: - -```bash -cd /opt/remnawave/nginx && nano docker-compose.yml -``` - -Добавьте две строки в секцию `volumes`: - -```yaml -services: - remnawave-nginx: - image: nginx:1.26 - container_name: remnawave-nginx - hostname: remnawave-nginx - volumes: - - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro - - ./fullchain.pem:/etc/nginx/ssl/fullchain.pem:ro - - ./privkey.key:/etc/nginx/ssl/privkey.key:ro - - ./subdomain_fullchain.pem:/etc/nginx/ssl/subdomain_fullchain.pem:ro - - ./subdomain_privkey.key:/etc/nginx/ssl/subdomain_privkey.key:ro - - ./webhook_fullchain.pem:/etc/nginx/ssl/webhook_fullchain.pem:ro # Добавьте эту строку - - ./webhook_privkey.key:/etc/nginx/ssl/webhook_privkey.key:ro # Добавьте эту строку - restart: always - ports: - - '0.0.0.0:443:443' - networks: - - remnawave-network - -networks: - remnawave-network: - name: remnawave-network - driver: bridge - external: true -``` - -### 6. Запуск бота и перезапуск Nginx - -Запустите бота: - -```bash -cd /root/remnawave-minishop && docker compose up -d && docker compose logs -f -t -``` - -Перезапустите Nginx: - -```bash -cd /opt/remnawave/nginx && docker compose down && docker compose up -d && docker compose logs -f -t -``` - -## 🐳 Docker - -Файлы `Dockerfile` и `docker-compose.yml` уже настроены для локальной сборки и запуска проекта. - -Если нужен запуск из готового образа, используйте `docker-compose-remote-server.yml` как шаблон и укажите свой `image:` вместо локальной сборки. По умолчанию он тянет `ghcr.io/3252a8/remnawave-minishop:latest`, а для закрепления версии можно задать `IMAGE_TAG=3.1.0`. - -В GHCR доступны теги `3.1.0` и `latest`. - -Чтобы использовать сохранённый образ, можно запустить: -```bash +# Запуск из готового образа IMAGE_TAG=3.1.0 docker compose -f docker-compose-remote-server.yml up -d ``` -### Вариант с Caddy +## Поддержка -Если нужен reverse proxy на Caddy, используйте `docker-compose-caddy.yml` вместе с `Caddyfile`. Это удобный вариант, когда хочется, чтобы Caddy сам выпускал TLS-сертификаты и проксировал и webhook'и, и Mini App без ручной настройки Nginx. - -В этой схеме: -- Caddy публикует наружу `80` и `443`. -- Бот остается доступным только внутри docker-сети. -- `WEBHOOK_BASE_URL` должен указывать на домен вебхуков, а `SUBSCRIPTION_MINI_APP_URL` - на домен Mini App. - -Пример `Caddyfile`: - -```caddyfile -webhook.domain.com { - encode zstd gzip - reverse_proxy remnawave-minishop:{$WEB_SERVER_PORT:8080} -} - -app.domain.com { - encode zstd gzip - reverse_proxy remnawave-minishop:{$WEBAPP_SERVER_PORT:8081} -} -``` - -Что нужно поменять под себя: -- заменить `webhook.domain.com` и `app.domain.com` на свои домены; -- убедиться, что в `.env` заданы `WEBHOOK_BASE_URL=https://webhook.domain.com` и `SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/`; -- при необходимости скорректировать `WEB_SERVER_PORT` и `WEBAPP_SERVER_PORT`, если они отличаются от стандартных `8080` и `8081`. -- в BotFather укажите домен Mini App через `/setdomain`, чтобы он совпадал с `SUBSCRIPTION_MINI_APP_URL`. - -Запуск: - -```bash -docker compose -f docker-compose-caddy.yml up -d --build -``` - -После этого Caddy сам выпустит сертификаты и будет проксировать webhook'и на порт `8080`, а Mini App - на `8081`. - -## 🔄 Миграция с `remnawave-tg-shop` на `remnawave-minishop` - -Короткая инструкция, автоматический запуск helper'а из `raw` и ручной вариант переноса вынесены в отдельный документ: [docs/migration-to-minishop.md](docs/migration-to-minishop.md). - -## 📁 Структура проекта - -``` -. -├── bot/ -│ ├── app/ # Сборка приложения (фабрики, контроллеры, Web App) -│ │ ├── controllers/ # Запуск Aiogram dispatcher -│ │ ├── factories/ # Фабрики сервисов (платежи, панель и т.д.) -│ │ └── web/ # Web App / Mini App (сервер, аутентификация, шаблоны) -│ ├── filters/ # Пользовательские фильтры Aiogram -│ ├── handlers/ # Обработчики сообщений и колбэков (admin/, user/) -│ ├── keyboards/ # Клавиатуры -│ ├── middlewares/ # Промежуточные слои (i18n, проверка бана и т.д.) -│ ├── services/ # Бизнес-логика (платёжные шлюзы, API панели, email и т.д.) -│ ├── states/ # Состояния FSM (admin/user) -│ ├── utils/ # Вспомогательные утилиты -│ ├── routers.py # Регистрация всех роутеров Aiogram -│ └── main_bot.py # Основная логика бота -├── config/ -│ └── settings.py # Настройки Pydantic -├── db/ -│ ├── dal/ # Слой доступа к данным (DAL) -│ ├── database_setup.py # Настройка БД и подключения -│ ├── migrator.py # Миграции схемы при старте -│ └── models.py # Модели SQLAlchemy -├── locales/ # Файлы локализации (ru.json, en.json) -├── scripts/ # Сборка JS Web App, обновление копий Telegram JS и миграционный helper -├── tests/ # Pytest-тесты -├── .env.example # Пример файла с переменными окружения -├── Caddyfile # Пример конфигурации Caddy -├── Dockerfile # Multi-stage сборка (Python + Node для Web App) -├── docker-compose.yml # Локальная сборка и запуск -├── docker-compose-caddy.yml # Запуск с Caddy в качестве reverse proxy -├── docker-compose-remote-server.yml # Запуск из готового образа GHCR -├── package.json # Frontend-зависимости (Tailwind, esbuild) и сборка Web App -├── requirements.txt # Зависимости Python -└── main.py # Точка входа в приложение -``` - -## ❤️ Поддержка - Crypto: `USDT/Other ERC-20 0xeD506D44aae634fEc0E01C8835744fBedb7B2a44 (Ethereum/Polygon/Gnosis)` diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..0605819 --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,135 @@ +# Настройка окружения + +Конфигурация читается из `.env`. За основу удобно взять `.env.example` и заполнить значения под свою панель, домены и платежные провайдеры. + +```bash +cp .env.example .env +nano .env +``` + +## Основные настройки + +| Переменная | Назначение | +| --- | --- | +| `BOT_TOKEN` | Токен Telegram-бота. | +| `ADMIN_IDS` | Telegram ID администраторов через запятую. | +| `DEFAULT_LANGUAGE` | Язык по умолчанию для пользователей: `ru` или `en`. | +| `SUPPORT_LINK` | Ссылка на поддержку. | +| `PRIVACY_POLICY_URL` | Ссылка на политику конфиденциальности в Web App. | +| `USER_AGREEMENT_URL` | Ссылка на пользовательское соглашение в Web App. | +| `REQUIRED_CHANNEL_ID` | ID канала, на который пользователь должен подписаться перед использованием. | +| `REQUIRED_CHANNEL_LINK` | Ссылка на канал для кнопки проверки подписки. | + +Если используется проверка подписки на канал, добавьте бота администратором в этот канал. После первой успешной проверки пользователь продолжает работу без повторной блокировки действий. + +## Remnawave + +| Переменная | Назначение | +| --- | --- | +| `PANEL_API_URL` | URL API панели Remnawave, например `https://panel.domain.com/api`. | +| `PANEL_API_KEY` | API-ключ панели. | +| `PANEL_WEBHOOK_SECRET` | Секрет для проверки вебхуков Remnawave. | +| `USER_SQUAD_UUIDS` | Internal Squads, в которые добавляются пользователи. | +| `USER_EXTERNAL_SQUAD_UUID` | External Squad для пользователей, если он используется. | +| `USER_TRAFFIC_LIMIT_GB` | Лимит трафика для режима без JSON-каталога тарифов. `0` означает безлимит. | +| `USER_TRAFFIC_STRATEGY` | Стратегия лимита трафика для режима без JSON-каталога тарифов. | +| `USER_HWID_DEVICE_LIMIT` | Лимит HWID-устройств для пользователей. `0` означает безлимит. | + +При включенном каталоге тарифов значения `squad_uuids`, `monthly_gb`, `traffic_packages` и `hwid_device_limit` берутся из выбранного тарифа. Подробно это описано в [tariffs.md](tariffs.md). + +## Платежи + +| Переменная | Назначение | +| --- | --- | +| `PAYMENT_METHODS_ORDER` | Порядок кнопок оплаты через запятую: `severpay`, `freekassa`, `platega`, `yookassa`, `stars`, `cryptopay`. | +| `YOOKASSA_ENABLED` | Включает YooKassa. | +| `YOOKASSA_SHOP_ID` / `YOOKASSA_SECRET_KEY` | Данные магазина YooKassa. | +| `YOOKASSA_AUTOPAYMENTS_ENABLED` | Включает автопродление через сохраненные способы оплаты YooKassa. | +| `YOOKASSA_AUTOPAYMENTS_REQUIRE_CARD_BINDING` | Управляет обязательной привязкой карты при оплате. | +| `FREEKASSA_ENABLED` | Включает FreeKassa. | +| `FREEKASSA_MERCHANT_ID` / `FREEKASSA_API_KEY` / `FREEKASSA_SECOND_SECRET` | Данные FreeKassa и секрет уведомлений. | +| `FREEKASSA_PAYMENT_IP` | Внешний IP сервера для запроса оплаты FreeKassa. | +| `FREEKASSA_PAYMENT_METHOD_ID` | ID метода оплаты FreeKassa. | +| `PLATEGA_ENABLED` | Включает Platega. | +| `PLATEGA_MERCHANT_ID` / `PLATEGA_SECRET` | Данные Platega. | +| `PLATEGA_PAYMENT_METHOD` | ID способа оплаты Platega. | +| `PLATEGA_RETURN_URL` / `PLATEGA_FAILED_URL` | URL возврата после оплаты или ошибки. | +| `SEVERPAY_ENABLED` | Включает SeverPay. | +| `SEVERPAY_MID` / `SEVERPAY_TOKEN` | Данные SeverPay. | +| `SEVERPAY_BASE_URL` | Базовый URL API SeverPay. | +| `SEVERPAY_RETURN_URL` | URL возврата после оплаты. | +| `SEVERPAY_LIFETIME_MINUTES` | Время жизни платежной ссылки. | +| `CRYPTOPAY_ENABLED` | Включает CryptoPay. | +| `CRYPTOPAY_TOKEN` | Токен CryptoPay App. | +| `STARS_ENABLED` | Включает Telegram Stars. | + +Вебхуки платежных систем должны проксироваться на порт `WEB_SERVER_PORT`. Примеры маршрутов есть в [deployment.md](deployment.md). + +## Тарифы + +| Переменная | Назначение | +| --- | --- | +| `TARIFFS_CONFIG_PATH` | Путь к JSON-каталогу тарифов. По умолчанию `config/tariffs.json`. | +| `TARIFF_TRAFFIC_WARNING_LEVELS` | Проценты предупреждений по трафику, например `85,90,95`. | +| `RUB_PRICE_1_MONTH`, `RUB_PRICE_3_MONTHS`, `RUB_PRICE_6_MONTHS`, `RUB_PRICE_12_MONTHS` | Цены подписок в рублях для режима без JSON-каталога. | +| `STARS_PRICE_1_MONTH`, `STARS_PRICE_3_MONTHS`, `STARS_PRICE_6_MONTHS`, `STARS_PRICE_12_MONTHS` | Цены подписок в Telegram Stars для режима без JSON-каталога. | +| `1_MONTH_ENABLED`, `3_MONTHS_ENABLED`, `6_MONTHS_ENABLED`, `12_MONTHS_ENABLED` | Доступность периодов подписки для режима без JSON-каталога. | +| `TRAFFIC_PACKAGES` | Пакеты трафика в рублях для режима без JSON-каталога, например `10:199,50:799`. | +| `STARS_TRAFFIC_PACKAGES` | Пакеты трафика в Telegram Stars для режима без JSON-каталога. | + +Если файл из `TARIFFS_CONFIG_PATH` существует, бот использует каталог тарифов. Если файла нет, применяется конфигурация из переменных `.env`. + +## Web App и email-вход + +| Переменная | Назначение | +| --- | --- | +| `WEBAPP_ENABLED` | Включает Web App в том же контейнере. | +| `WEBAPP_SERVER_HOST` / `WEBAPP_SERVER_PORT` | Хост и порт Web App. По умолчанию порт `8081`. | +| `SUBSCRIPTION_MINI_APP_URL` | Публичный URL Web App. | +| `WEBAPP_TITLE` | Заголовок Web App. | +| `WEBAPP_PRIMARY_COLOR` | Основной цвет интерфейса. | +| `WEBAPP_LOGO_URL` | URL логотипа Web App. | +| `WEBAPP_SESSION_SECRET` | HMAC-секрет сессий Web App. | +| `TELEGRAM_OAUTH_CLIENT_ID` / `TELEGRAM_OAUTH_CLIENT_SECRET` | Данные Telegram OAuth / OpenID Connect из BotFather. | +| `TELEGRAM_OAUTH_REQUEST_ACCESS` | Дополнительные разрешения Telegram Login, например `write`. | +| `SMTP_HOST`, `SMTP_PORT`, `SMTP_FALLBACK_PORTS` | SMTP-подключение для email-кодов. | +| `SMTP_USERNAME` / `SMTP_PASSWORD` | Логин и пароль или SMTP key. | +| `SMTP_FROM_EMAIL` / `SMTP_FROM_NAME` | Отправитель писем с кодом. | +| `EMAIL_CODE_TTL_SECONDS` | Срок действия email-кода. | +| `EMAIL_CODE_RESEND_SECONDS` | Пауза перед повторной отправкой кода. | +| `EMAIL_CODE_MAX_ATTEMPTS` | Максимум попыток ввода одного кода. | +| `BRUTE_FORCE_MAX_FAILURES` | Количество неудачных попыток до временной блокировки. | +| `BRUTE_FORCE_WINDOW_SECONDS` | Окно учета неудачных попыток. | +| `BRUTE_FORCE_LOCK_SECONDS` | Длительность временной блокировки. | +| `MY_DEVICES_SECTION_ENABLED` | Показывает раздел "Мои устройства" и включает API устройств. | + +Настройка домена, BotFather и callback URL описана в [webapp.md](webapp.md). + +## Пробный период + +| Переменная | Назначение | +| --- | --- | +| `TRIAL_ENABLED` | Включает пробный период. | +| `TRIAL_DURATION_DAYS` | Длительность пробного периода в днях. | +| `TRIAL_TRAFFIC_LIMIT_GB` | Лимит трафика пробного периода. `0` означает безлимит. | +| `TRIAL_TRAFFIC_STRATEGY` | Стратегия лимита трафика пробного периода. | + +## Реферальная программа + +| Переменная | Назначение | +| --- | --- | +| `REFERRAL_WELCOME_BONUS_DAYS` | Бонус пользователю, который пришел по реферальной ссылке. | +| `REFERRAL_ONE_BONUS_PER_REFEREE` | Ограничивает бонусы одним успешным платежом приглашенного пользователя. | +| `REFERRAL_BONUS_DAYS_*` | Бонусные дни пригласившему по периодам подписки. | +| `REFEREE_BONUS_DAYS_*` | Бонусные дни приглашенному по периодам подписки. | +| `LEGACY_REFS` | Разрешает ссылки формата `ref_`. | + +В режиме продажи трафика без JSON-каталога бонусы по периодам не отображаются, потому что покупка не привязана к сроку подписки. + +## Секреты + +`WEBAPP_SESSION_SECRET` и `WEBHOOK_SECRET_TOKEN` могут генерироваться при старте, но для рабочего окружения их лучше задать явно. Иначе после рестарта сессии Web App станут невалидными, а Telegram webhook будет установлен с другим secret token. + +```bash +openssl rand -hex 32 +``` diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..8e7bf9b --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,176 @@ +# Развертывание + +Документ описывает запуск через Docker Compose, маршруты вебхуков и варианты reverse proxy. Перед запуском заполните `.env` по [configuration.md](configuration.md). + +## Docker Compose + +Локальная сборка: + +```bash +docker compose up -d --build +docker compose logs -f remnawave-minishop +``` + +Запуск из готового образа: + +```bash +IMAGE_TAG=3.1.0 docker compose -f docker-compose-remote-server.yml up -d +``` + +`docker-compose-remote-server.yml` можно использовать как шаблон и заменить `image:` на нужный образ. По умолчанию используется `ghcr.io/3252a8/remnawave-minishop:latest`. + +## Порты + +| Порт | Назначение | +| --- | --- | +| `WEB_SERVER_PORT` (`8080`) | Telegram webhook, платежные вебхуки, Remnawave webhook. | +| `WEBAPP_SERVER_PORT` (`8081`) | Web App / Mini App. | + +Web App не должен проксироваться на `WEB_SERVER_PORT`. + +## Маршруты вебхуков + +Проксируйте платежные и системные вебхуки на `WEB_SERVER_PORT`: + +- `https:///webhook/yookassa` -> `http://remnawave-minishop:/webhook/yookassa`; +- `https:///webhook/freekassa` -> `http://remnawave-minishop:/webhook/freekassa`; +- `https:///webhook/platega` -> `http://remnawave-minishop:/webhook/platega`; +- `https:///webhook/severpay` -> `http://remnawave-minishop:/webhook/severpay`; +- `https:///webhook/cryptopay` -> `http://remnawave-minishop:/webhook/cryptopay`; +- `https:///webhook/panel` -> `http://remnawave-minishop:/webhook/panel`. + +Telegram webhook устанавливается приложением, если задан `WEBHOOK_BASE_URL`. Путь формируется как `https:///`. + +## Nginx рядом с Remnawave + +Пример upstream и server-блока для домена вебхуков: + +```nginx +upstream remnawave-minishop { + server remnawave-minishop:8080; +} + +map $http_upgrade $connection_upgrade { + default upgrade; + "" close; +} + +server { + server_name webhook.domain.com; + listen 443 ssl; + http2 on; + + ssl_certificate "/etc/nginx/ssl/webhook_fullchain.pem"; + ssl_certificate_key "/etc/nginx/ssl/webhook_privkey.key"; + ssl_trusted_certificate "/etc/nginx/ssl/webhook_fullchain.pem"; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + proxy_intercept_errors on; + error_page 400 404 500 502 @redirect; + + location / { + proxy_pass http://remnawave-minishop$request_uri; + } + + location @redirect { + return 404; + } +} +``` + +Для Web App используйте отдельный upstream на `WEBAPP_SERVER_PORT`; пример есть в [webapp.md](webapp.md). + +## SSL для домена вебхуков + +Пример выпуска сертификата через `acme.sh`: + +```bash +sudo apt-get install cron socat +curl https://get.acme.sh | sh -s email=EMAIL +source ~/.bashrc +ufw allow 80/tcp +ufw reload + +acme.sh --set-default-ca --server letsencrypt +acme.sh --issue --standalone -d 'webhook.domain.com' \ + --key-file /opt/remnawave/nginx/webhook_privkey.key \ + --fullchain-file /opt/remnawave/nginx/webhook_fullchain.pem +``` + +Если Nginx панели Remnawave запускается в Docker, добавьте сертификаты в `volumes` сервиса Nginx: + +```yaml +services: + remnawave-nginx: + volumes: + - ./webhook_fullchain.pem:/etc/nginx/ssl/webhook_fullchain.pem:ro + - ./webhook_privkey.key:/etc/nginx/ssl/webhook_privkey.key:ro +``` + +После изменения конфигурации перезапустите Nginx: + +```bash +cd /opt/remnawave/nginx +docker compose down +docker compose up -d +docker compose logs -f -t +``` + +## Caddy + +Для схемы с Caddy используйте `docker-compose-caddy.yml` и `Caddyfile`. Caddy публикует наружу `80` и `443`, выпускает TLS-сертификаты и проксирует вебхуки и Web App на разные внутренние порты. + +Пример `Caddyfile`: + +```caddyfile +webhook.domain.com { + encode zstd gzip + reverse_proxy remnawave-minishop:{$WEB_SERVER_PORT:8080} +} + +app.domain.com { + encode zstd gzip + reverse_proxy remnawave-minishop:{$WEBAPP_SERVER_PORT:8081} +} +``` + +В `.env` укажите: + +```env +WEBHOOK_BASE_URL=https://webhook.domain.com +SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/ +``` + +Запуск: + +```bash +docker compose -f docker-compose-caddy.yml up -d --build +``` + +В BotFather укажите домен Mini App через настройки домена, чтобы он совпадал с `SUBSCRIPTION_MINI_APP_URL`. + +## Проверка + +```bash +docker compose ps +docker compose logs -f remnawave-minishop +``` + +Проверьте: + +- бот отвечает в Telegram; +- Telegram webhook установлен без ошибок в логах; +- платежные вебхуки доходят до приложения; +- Remnawave webhook проходит проверку `PANEL_WEBHOOK_SECRET`; +- Web App открывается по домену из `SUBSCRIPTION_MINI_APP_URL`; +- в BotFather разрешены URL Web App и callback. diff --git a/docs/tariffs.md b/docs/tariffs.md index fc3fc30..0d6bebf 100644 --- a/docs/tariffs.md +++ b/docs/tariffs.md @@ -1,99 +1,227 @@ -# Тарифы 2.0 +# Тарифы -Бот поддерживает каталог тарифов в JSON-файле. Путь задается через `TARIFFS_CONFIG_PATH`, по умолчанию используется `config/tariffs.json`. +Бот поддерживает два способа описания продаж: -Если файл отсутствует, включается legacy-режим: используются старые `.env` поля `RUB_PRICE_*`, `STARS_PRICE_*`, `USER_TRAFFIC_LIMIT_GB`, `USER_SQUAD_UUIDS`, а также старый режим продажи трафика через `TRAFFIC_PACKAGES`. +- JSON-каталог тарифов из `TARIFFS_CONFIG_PATH` (по умолчанию `config/tariffs.json`); +- конфигурация через переменные `.env`, если JSON-файл отсутствует. -## Конфиг +JSON-каталог может содержать несколько тарифов разных моделей: подписки на срок, пакеты трафика без срока действия, разные наборы Internal Squads, лимиты устройств и пакеты докупки. Пример формата: [config/tariffs.example.json](../config/tariffs.example.json). -Пример конфига: [`config/tariffs.example.json`](../config/tariffs.example.json). +## Как выбирается режим + +Если файл из `TARIFFS_CONFIG_PATH` существует и проходит валидацию, используется каталог тарифов. В этом режиме `TRAFFIC_PACKAGES` и цены подписок из `.env` не формируют витрину продаж, потому что цены и пакеты берутся из JSON. + +Если JSON-файл отсутствует, бот использует значения `.env`: + +- `RUB_PRICE_*`, `STARS_PRICE_*` и `*_MONTHS_ENABLED` для подписок на срок; +- `TRAFFIC_PACKAGES` и `STARS_TRAFFIC_PACKAGES` для продажи пакетов трафика; +- `USER_TRAFFIC_LIMIT_GB`, `USER_TRAFFIC_STRATEGY`, `USER_SQUAD_UUIDS`, `USER_HWID_DEVICE_LIMIT` для пользователей Remnawave. + +В режиме без JSON-каталога наличие `TRAFFIC_PACKAGES` или `STARS_TRAFFIC_PACKAGES` переключает витрину на продажу трафика вместо подписок на срок. + +## Структура JSON-каталога + +Минимальная структура: + +```json +{ + "default_tariff": "standard", + "topup_packages_default": { + "rub": [{ "gb": 10, "price": 99 }], + "stars": [{ "gb": 10, "price": 2500 }] + }, + "tariffs": [ + { + "key": "standard", + "names": { "ru": "Стандарт", "en": "Standard" }, + "descriptions": { "ru": "Базовый набор серверов" }, + "squad_uuids": ["uuid-1"], + "billing_model": "period", + "monthly_gb": 500, + "prices_rub": { "1": 150, "3": 400 }, + "enabled_periods": [1, 3], + "enabled": true + } + ] +} +``` Основные поля: -| Поле | Описание | +| Поле | Назначение | | --- | --- | -| `default_tariff` | Тариф по умолчанию для миграции существующих подписок и первичного выбора | -| `topup_packages_default` | Пакеты докупки трафика для period-тарифов без собственных пакетов | -| `tariffs[].billing_model` | Модель тарификации: `period` или `traffic` | -| `tariffs[].squad_uuids` | Internal Squads Remnawave, которые получает пользователь на этом тарифе | -| `tariffs[].hwid_device_limit` | Базовый лимит HWID-устройств для тарифа; `0` означает без ограничений, отсутствие поля использует `USER_HWID_DEVICE_LIMIT` | -| `tariffs[].hwid_device_packages` | Пакеты докупки HWID-устройств, например `{ "count": 1, "price": 99 }` | -| `prices_rub` / `prices_stars` | Цены period-тарифов по месяцам | -| `traffic_packages` | Пакеты GB для traffic-тарифов | +| `default_tariff` | Тариф по умолчанию для первичного выбора и привязки активных подписок без `tariff_key`. | +| `topup_packages_default` | Пакеты докупки трафика для period-тарифов, у которых не задан `topup_packages`. | +| `tariffs[].key` | Стабильный ключ тарифа. Используется в платежах, подписках и смене тарифа. | +| `tariffs[].names` | Названия тарифа по языкам. | +| `tariffs[].descriptions` | Описания тарифа по языкам. | +| `tariffs[].enabled` | Доступность тарифа на витрине. | +| `tariffs[].squad_uuids` | Internal Squads Remnawave для пользователей тарифа. | +| `tariffs[].billing_model` | Модель тарифа: `period` или `traffic`. | +| `tariffs[].hwid_device_limit` | Базовый лимит HWID-устройств. `0` означает безлимит, отсутствие поля использует `USER_HWID_DEVICE_LIMIT`. | +| `tariffs[].hwid_device_packages` | Пакеты докупки устройств: `{ "count": 1, "price": 99 }`. | -## Period-Тариф +Для `period`-тарифа также используются: -Period-тариф продает доступ на срок и лимит трафика с календарным ежемесячным сбросом. +| Поле | Назначение | +| --- | --- | +| `monthly_gb` | Базовый месячный лимит трафика тарифа. `0` означает безлимит. | +| `prices_rub` | Цены периодов в рублях, ключ - количество месяцев. | +| `prices_stars` | Цены периодов в Telegram Stars. | +| `enabled_periods` | Периоды, доступные для покупки. | +| `topup_packages` | Пакеты докупки трафика именно для этого тарифа. | -- `monthly_gb` превращается в `tier_baseline_bytes`. -- Докупленные пакеты трафика хранятся в `topup_balance_bytes`. -- В Remnawave отправляется `trafficLimitBytes = tier_baseline_bytes + topup_balance_bytes`. -- Для period-тарифов бот выставляет `trafficLimitStrategy = MONTH`, а дальнейший сброс выполняет сама панель. -- Дата сброса больше не считается в боте. -- Если покупка или продление были в середине месяца, сброс все равно произойдет по правилам панели для `MONTH`. -- Бот меняет только лимиты в GB и следит за предупреждениями на основе текущего usage из панели. +Для `traffic`-тарифа используются: -## Traffic-Тариф +| Поле | Назначение | +| --- | --- | +| `traffic_packages` | Пакеты трафика в GB для рублей и Telegram Stars. | +| `conversion_rate_rub_per_gb` | Курс для конвертации оставшихся дней period-тарифа в GB при смене на traffic-тариф. | -Traffic-тариф продает GB без срока действия. +Если у traffic-тарифа нет RUB-пакетов, `conversion_rate_rub_per_gb` обязателен. -- `end_date` технически ставится в `2099-01-01 UTC`. -- `period_start_at = NULL`. -- `trafficLimitStrategy = NO_RESET`. -- Новая покупка добавляет GB к фактическому остатку: `limit = used + remaining + purchased`. -- Доступ ограничивается только при исчерпании купленного трафика. +## Period-тарифы -## HWID-Устройства +`period` продает доступ на срок с месячным лимитом трафика. -Тарифы поддерживают лимит HWID-устройств и платную докупку устройств. +При покупке или продлении: -- При покупке или продлении тарифа бот отправляет в Remnawave `hwidDeviceLimit`. -- `subscriptions.hwid_device_limit` хранит базовый лимит тарифа. -- `subscriptions.extra_hwid_devices` хранит количество докупленных устройств. -- Эффективный лимит равен `hwid_device_limit + extra_hwid_devices`. -- Если базовый лимит равен `0`, это безлимит; докупка не нужна, а в панель отправляется `0`. -- Докупка устройств использует `sale_mode=hwid_devices`. -- Количество купленных устройств сохраняется в `payments.purchased_hwid_devices`. -- История докупок пишется в `hwid_device_purchases`. -- Докупка доступна в Web App через `/api/devices/topup-options` и `/api/payments`. -- Докупка доступна в Telegram-боте из раздела устройств. -- При смене тарифа базовый HWID-лимит берется из нового тарифа, а уже докупленные устройства сохраняются. +- дата начала берется от текущей активной подписки, если она еще действует, иначе от текущего времени; +- срок считается календарными месяцами через `add_months`; +- промокод может добавить бонусные дни к рассчитанному сроку; +- `tier_baseline_bytes` получает значение `monthly_gb`; +- `topup_balance_bytes` сохраняется из текущей активной подписки; +- `traffic_limit_bytes` становится `tier_baseline_bytes + topup_balance_bytes`; +- в Remnawave отправляется `trafficLimitStrategy = MONTH`; +- в Remnawave отправляются Internal Squads из тарифа; +- в Remnawave отправляется эффективный HWID-лимит тарифа. -## Смена Тарифа +`MONTH` означает, что сброс использованного трафика выполняет Remnawave. Бот не рассчитывает дату сброса самостоятельно и не хранит отдельный период сброса для period-тарифов. -Смена тарифа пишется в `tariff_changes`. +Докупка трафика для period-тарифа увеличивает `topup_balance_bytes` и общий `traffic_limit_bytes`. Этот баланс сохраняется в подписке и учитывается при продлении period-тарифа. В панель отправляется актуальный лимит, а доступ переводится в `ACTIVE`. -- `period -> period`: расчет идет от `effective_monthly_price_rub`; пересчет дней использует `floor`. -- `period -> traffic`: остаток оплаченных дней конвертируется в GB по `conversion_rate_rub_per_gb` или по минимальной цене GB в RUB-пакетах. -- `traffic -> period`: пользователь покупает период, а остаток GB сохраняется как top-up поверх нового тарифа. -- При смене тарифа обновляются Internal Squads, лимит трафика, стратегия сброса и базовый HWID-лимит. +## Traffic-тарифы + +`traffic` продает объем трафика без пользовательского срока действия. + +При покупке: + +- `end_date` ставится в дальнюю дату `2099-01-01 UTC`, если у активной подписки нет более поздней даты; +- `duration_months = 0`; +- `period_start_at = NULL`; +- `tier_baseline_bytes = 0`; +- `topup_balance_bytes` хранит доступный остаток трафика; +- в Remnawave отправляется `trafficLimitStrategy = NO_RESET`; +- автопродление и уведомления о скором окончании срока отключаются для такой подписки. + +Очередная покупка добавляет GB к фактическому остатку: + +```text +remaining = max(0, current_limit - current_used) +balance_after = remaining + purchased +limit_after = current_used + balance_after +``` + +Так пользователь не теряет уже оплаченный остаток, а Remnawave продолжает считать общий лимит от текущего использованного трафика. + +Если докупка трафика вызывается для traffic-тарифа, она обрабатывается как покупка очередного пакета этого же traffic-тарифа. + +## HWID-устройства + +Тариф может задавать базовый лимит устройств и пакеты докупки: + +```json +{ + "hwid_device_limit": 5, + "hwid_device_packages": { + "rub": [{ "count": 1, "price": 99 }], + "stars": [{ "count": 1, "price": 2500 }] + } +} +``` + +Правила: + +- `hwid_device_limit` хранит базовый лимит тарифа; +- `extra_hwid_devices` хранит количество докупленных устройств; +- эффективный лимит равен `hwid_device_limit + extra_hwid_devices`; +- базовый лимит `0` означает безлимит, в Remnawave отправляется `hwidDeviceLimit = 0`; +- при безлимитном базовом лимите докупка устройств не применяется; +- при смене тарифа базовый лимит берется из целевого тарифа, а докупленные устройства сохраняются; +- история докупок пишется в `hwid_device_purchases`; +- платеж хранит количество устройств в `payments.purchased_hwid_devices`. + +Докупка устройств доступна в Web App через `/api/devices/topup-options` и `/api/payments`, а также в Telegram-боте из раздела устройств. + +## Смена тарифа + +Смена тарифа доступна для активных подписок с `tariff_key` и записывается в таблицу `tariff_changes`. + +Варианты расчета: + +| Переход | Поведение | +| --- | --- | +| `period -> period` | Остаток оплаченных дней оценивается по `effective_monthly_price_rub`, затем пересчитывается в дни целевого тарифа через месячную цену целевого тарифа. Количество дней округляется вниз. | +| `period -> period` с доплатой | Если целевой тариф дороже, может быть создан платеж `tariff_upgrade`; после оплаты применяется целевой тариф. | +| `period -> traffic` | Остаток оплаченных дней конвертируется в GB по `conversion_rate_rub_per_gb` или минимальной RUB-цене GB из пакетов целевого тарифа. | +| `traffic -> period` | Пользователь выбирает и оплачивает период целевого тарифа; остаток GB сохраняется как `topup_balance_bytes` поверх лимита period-тарифа. | + +При смене тарифа бот меняет: + +- `tariff_key`; +- Internal Squads в Remnawave; +- `trafficLimitBytes`; +- `trafficLimitStrategy`; +- базовый HWID-лимит; +- `effective_monthly_price_rub` для period-тарифов; +- `auto_renew_enabled` и уведомления для traffic-тарифов. ## Платежи -Новые платежи сохраняют: +В платежах используются поля: -- `sale_mode`: `subscription`, `traffic_package`, `topup`, `tariff_upgrade`, `hwid_devices`; -- `tariff_key`; -- `purchased_gb` для GB-покупок; -- `purchased_hwid_devices` для докупки HWID-устройств. +| Поле | Назначение | +| --- | --- | +| `sale_mode` | Тип продажи: `subscription`, `traffic_package`, `topup`, `tariff_upgrade`, `hwid_devices`. | +| `tariff_key` | Ключ тарифа, к которому относится платеж. | +| `purchased_gb` | Купленный объем GB для traffic-пакетов и докупки трафика. | +| `purchased_hwid_devices` | Количество устройств при докупке HWID. | +| `subscription_duration_months` | Количество месяцев для подписки на срок; также используется платежными обработчиками как числовое поле покупки. | -Legacy-поле `subscription_duration_months` остается для совместимости с существующими платежными обработчиками. +В callback и metadata платежных провайдеров `sale_mode` может передаваться с суффиксом тарифа, например `subscription@standard` или `topup@standard`. При активации платежа тариф сохраняется отдельно в `tariff_key`. -## Поведение При Исчерпании Трафика +## Предупреждения и исчерпание трафика -Remnawave сама ограничивает пользователя при достижении `trafficLimitBytes`: панель переводит пользователя в статус `LIMITED`. Бот не должен удалять пользователя из Internal Squads при 100% использования трафика. +Remnawave ограничивает доступ при достижении `trafficLimitBytes`, переводя пользователя в статус `LIMITED`. Бот не удаляет пользователя из Internal Squads при 100% использования трафика. -Текущее поведение воркера: +`TariffTrafficWorker` запускается, когда активен JSON-каталог тарифов. Раз в 300 секунд он: - синхронизирует из панели `status`, `trafficLimitBytes`, `usedTrafficBytes` и `trafficLimitStrategy`; +- для period-тарифов выставляет `trafficLimitStrategy = MONTH`, если панель еще показывает другую стратегию; - отправляет предупреждения на уровнях из `TARIFF_TRAFFIC_WARNING_LEVELS` (по умолчанию `85,90,95`); -- оставляет блокировку и разблокировку доступа штатной логике Remnawave. +- не отправляет `status=ACTIVE` при простой синхронизации стратегии, чтобы не снять статус `LIMITED`, выставленный Remnawave; +- дедуплицирует предупреждения через `traffic_warnings`. -## Воркеры +Для period-тарифов дедупликация предупреждений привязана к началу текущего месяца. Для traffic-тарифов она учитывает текущий `trafficLimitBytes`, чтобы после покупки очередного пакета пользователь мог получить следующий набор предупреждений. -`TariffTrafficWorker` запускается только при активном `tariffs.json`. +Подписки, которые были ограничены логикой предыдущих запусков бота (`is_throttled=True`), восстанавливаются воркером только когда лимит снова больше использованного трафика. -- Раз в несколько минут синхронизирует `trafficLimitStrategy = MONTH` для period-тарифов, если панель еще не переключена. -- При синхронизации стратегии не отправляет `status=ACTIVE`, чтобы случайно не снять статус `LIMITED`, выставленный Remnawave. -- Дедуплицирует предупреждения через таблицу `traffic_warnings`. -- Для traffic-тарифов дедупликация предупреждений учитывает текущий `trafficLimitBytes`, чтобы новая покупка трафика могла создать новый набор предупреждений. +## Автопродление, пробный период и бонусы + +Автопродление через YooKassa применяется к подпискам на срок. Для режима продажи трафика без JSON-каталога автопродление пропускается. Для traffic-тарифов JSON-каталога покупка является пакетом трафика, а не периодической подпиской. + +Пробный период использует настройки `TRIAL_DURATION_DAYS`, `TRIAL_TRAFFIC_LIMIT_GB` и `TRIAL_TRAFFIC_STRATEGY`. Он не выбирает тариф из JSON-каталога. + +Промокоды с бонусными днями применяются к покупке period-подписки. Реферальные бонусы по периодам также относятся к подпискам на срок; в режиме продажи трафика без JSON-каталога Web App не показывает детализацию бонусов по месяцам. + +## Привязка существующих подписок + +При запуске с активным JSON-каталогом бот заполняет активные подписки без `tariff_key`: + +- `tariff_key` получает `default_tariff`; +- `tier_baseline_bytes` берется из текущего лимита подписки или из `monthly_gb` тарифа по умолчанию; +- `topup_balance_bytes` становится `0`, если значение отсутствовало; +- `period_start_at` очищается; +- `effective_monthly_price_rub` берется из последнего успешного платежа или из цены тарифа по умолчанию. + +Это позволяет существующим активным подпискам отображаться и управляться в интерфейсах тарифов. diff --git a/docs/webapp.md b/docs/webapp.md new file mode 100644 index 0000000..e4258f9 --- /dev/null +++ b/docs/webapp.md @@ -0,0 +1,119 @@ +# Web App / Mini App + +Web App запускается в том же контейнере, что и бот, но слушает отдельный порт `WEBAPP_SERVER_PORT` (по умолчанию `8081`). Порт `WEB_SERVER_PORT` остается для Telegram, платежных и Remnawave вебхуков. + +## Что показывает Web App + +- текущую ссылку подключения; +- статус и дату окончания подписки; +- использованный и доступный трафик; +- доступные тарифы, способы оплаты и платежный статус; +- смену тарифа и докупку трафика при настроенном каталоге тарифов; +- раздел "Мои устройства" при `MY_DEVICES_SECTION_ENABLED=True`; +- реферальную ссылку и статистику приглашений; +- привязку email и Telegram к одному аккаунту. + +## Настройки `.env` + +```env +WEBAPP_ENABLED=True +WEBAPP_SERVER_HOST=0.0.0.0 +WEBAPP_SERVER_PORT=8081 +SUBSCRIPTION_MINI_APP_URL=https://app.domain.com/ +WEBAPP_TITLE="Моя подписка" +WEBAPP_PRIMARY_COLOR="#00fe7a" +WEBAPP_LOGO_URL= +WEBAPP_SESSION_SECRET= + +TELEGRAM_OAUTH_CLIENT_ID= +TELEGRAM_OAUTH_CLIENT_SECRET= +TELEGRAM_OAUTH_REQUEST_ACCESS=write + +SMTP_HOST=smtp-relay.brevo.com +SMTP_PORT=587 +SMTP_FALLBACK_PORTS=2525,465 +SMTP_USERNAME= +SMTP_PASSWORD= +SMTP_FROM_EMAIL=no-reply@domain.com +SMTP_FROM_NAME=Remnawave Minishop +``` + +Если `WEBAPP_LOGO_URL` пустой, логотип в Web App не показывается. Если SMTP-настройки не заполнены, вход по email скрывается. + +## Telegram-авторизация + +Внутри Telegram Mini App пользователь авторизуется через Telegram Mini Apps `initData`. При открытии страницы вне Telegram используется Telegram OAuth / OpenID Connect Authorization Code Flow с PKCE, callback `/auth/telegram/callback`, `nonce` и серверной проверкой `id_token` по JWKS Telegram. + +Настройка в BotFather: + +1. Откройте `@BotFather` -> `/mybots` -> выберите бота. +2. В `Bot Settings` -> `Domain` укажите домен Web App без протокола и пути, например `app.domain.com`. +3. В `Bot Settings` -> `Mini Apps` укажите URL, например `https://app.domain.com/`. +4. В `Bot Settings` -> `Web Login` включите OpenID Connect Login, если BotFather предлагает переключение. +5. Скопируйте Client ID и Client Secret в `TELEGRAM_OAUTH_CLIENT_ID` и `TELEGRAM_OAUTH_CLIENT_SECRET`. +6. В `Web Login` -> `Allowed URLs` добавьте: + +```text +https://app.domain.com/ +https://app.domain.com/auth/telegram/callback +``` + +`TELEGRAM_OAUTH_REQUEST_ACCESS=write` разрешает боту написать пользователю после логина. Если дополнительные разрешения не нужны, оставьте переменную пустой. + +## Email-вход + +Email-вход работает через одноразовый код: + +1. Пользователь вводит email. +2. Бот отправляет код через SMTP. +3. Код вводится в модальном окне Web App. +4. После подтверждения создается или находится пользователь, а email можно связать с Telegram-аккаунтом. + +Для Brevo обычно подходит порт `587` с STARTTLS. Если основной порт недоступен, приложение пробует порты из `SMTP_FALLBACK_PORTS`; порт `465` используется через SSL. + +## Проксирование + +Web App должен проксироваться отдельно от вебхуков: + +```nginx +upstream remnawave-minishop-webapp { + server remnawave-minishop:8081; +} + +server { + server_name app.domain.com; + listen 443 ssl; + http2 on; + + ssl_certificate "/etc/nginx/ssl/app_fullchain.pem"; + ssl_certificate_key "/etc/nginx/ssl/app_privkey.key"; + + location / { + proxy_pass http://remnawave-minishop-webapp; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +В `docker-compose.yml` порт Web App публикуется отдельно: + +```yaml +ports: + - 127.0.0.1:8080:8080 + - 127.0.0.1:${WEBAPP_SERVER_PORT:-8081}:${WEBAPP_SERVER_PORT:-8081} +``` + +## Реферальные ссылки + +Реферальные ссылки доступны в двух форматах: + +- Telegram deep-link: `https://t.me/?start=ref_u`; +- Web App ссылка: `https://app.domain.com/?ref=u`. + +Web App учитывает `ref`, `start`, `start_param` и Telegram Mini Apps `start_param`, сохраняет найденный параметр до авторизации и передает его в Telegram OAuth или email-вход. + +Для email-регистраций пользователь в Remnawave создается с username вида `em_`. Email добавляется в описание пользователя панели и, если API панели принимает поле `email`, передается отдельным полем. Для Telegram-регистраций используется username `tg_`.