Страница подписки Remnawave
Страница подписки Remnawave — это легковесный и безопасный способ скрыть домен панели Remnawave от конечных пользователей. Используйте её как простую и красивую страницу подписки для ваших пользователей.
Шаг 1. Настройка файла .env
Отредактируйте файл /opt/remnawave/.env, изменив параметр SUB_PUBLIC_DOMAIN на доменное имя страницы подписки.
cd /opt/remnawave && nano .env
Укажите доменное имя без http или https.
SUB_PUBLIC_DOMAIN=subscription.domain.com
Убедитесь, что доменное имя указано корректно и доступно.
Шаг 2. Создание файла docker-compose.yml
mkdir -p /opt/remnawave/subscription && cd /opt/remnawave/subscription && nano docker-compose.yml
Вставьте следующее содержимое:
services:
remnawave-subscription-page:
image: remnawave/subscription-page:latest
container_name: remnawave-subscription-page
hostname: remnawave-subscription-page
restart: always
environment:
- REMNAWAVE_PANEL_URL=https://panel.com
- APP_PORT=3010
- META_TITLE="Страница подписки"
- META_DESCRIPTION="Описание страницы подписки"
ports:
- '127.0.0.1:3010:3010'
networks:
- remnawave-network
networks:
remnawave-network:
driver: bridge
external: true
Замените panel.com на URL панели Remnawave. Это может быть http://remnawave:3000 (для мостового подключения) или https://panel.com.
Для добавления вложенного пути используйте параметр CUSTOM_SUB_PREFIX, например:
- CUSTOM_SUB_PREFIX=sub
В этом случае в файле .env для контейнера remnawave укажите: SUB_PUBLIC_DOMAIN=link.domain.com/sub. Также настройте соответствующие пути в конфигурации Nginx/Caddy.
Полное описание файла .env
APP_PORT=3010
REMNAWAVE_PANEL_URL=https://panel.example.com
META_TITLE="Страница подписки"
META_DESCRIPTION="Описание страницы подписки"
CUSTOM_SUB_PREFIX=
MARZBAN_LEGACY_LINK_ENABLED=false
MARZBAN_LEGACY_SECRET_KEY=
REMNAWAVE_API_TOKEN=
CADDY_AUTH_API_TOKEN=
Шаг 3. Запуск контейнера
docker compose up -d && docker compose logs -f
Шаг 4. Настройка обратного прокси
Remnawave не поддерживает работу на подпутях (например, location /subscription). Страница должна быть доступна на корне домена или поддомена. Для пользовательских путей используйте CUSTOM_SUB_PREFIX.
Caddy
Конфигурация Caddy
Добавьте новый блок сайта в файл Caddyfile:
cd /opt/remnawave/caddy && nano Caddyfile
Добавьте следующий блок в конец файла:
https://SUBSCRIPTION_PAGE_DOMAIN {
reverse_proxy * http://remnawave-subscription-page:3010
}
Замените SUBSCRIPTION_PAGE_DOMAIN на ваше доменное имя. Не заменяйте полностью существующую конфигурацию, только добавьте новый блок.
Перезапустите контейнер Caddy:
docker compose down && docker compose up -d && docker compose logs -f
Caddy с настройками безопасности
Если используется Caddy с настройками безопасности, измените файл docker-compose.yml для страницы подписки:
cd /opt/remnawave/subscription && nano docker-compose.yml
Обновите параметр REMNAWAVE_PANEL_URL:
environment:
- REMNAWAVE_PANEL_URL=http://remnawave:3000
Перезапустите контейнер:
docker compose down && docker compose up -d && docker compose logs -f
Nginx
Конфигурация Nginx
Выпустите сертификат для домена страницы подписки:
acme.sh --issue --standalone -d 'SUBSCRIPTION_PAGE_DOMAIN' --key-file /opt/remnawave/nginx/subdomain_privkey.key --fullchain-file /opt/remnawave/nginx/subdomain_fullchain.pem --alpn --tlsport 8443
Отредактируйте файл конфигурации Nginx:
cd /opt/remnawave/nginx && nano nginx.conf
Добавьте новый блок upstream в начало файла:
upstream remnawave-subscription-page {
server remnawave-subscription-page:3010;
}
Добавьте новый блок server в конец файла:
server {
server_name SUBSCRIPTION_PAGE_DOMAIN;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
location / {
proxy_http_version 1.1;
proxy_pass http://remnawave-subscription-page;
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;
}
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_certificate "/etc/nginx/ssl/subdomain_fullchain.pem";
ssl_certificate_key "/etc/nginx/ssl/subdomain_privkey.key";
ssl_trusted_certificate "/etc/nginx/ssl/subdomain_fullchain.pem";
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
resolver_timeout 2s;
proxy_hide_header Strict-Transport-Security;
add_header Strict-Transport-Security "max-age=15552000" always;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types
application/atom+xml
application/geo+json
application/javascript
application/x-javascript
application/json
application/ld+json
application/manifest+json
application/rdf+xml
application/rss+xml
application/xhtml+xml
application/xml
font/eot
font/otf
font/ttf
image/svg+xml
text/css
text/javascript
text/plain
text/xml;
}
Обновите docker-compose.yml для монтирования новых сертификатов:
cd /opt/remnawave/nginx && nano docker-compose.yml
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
restart: always
ports:
- '0.0.0.0:443:443'
networks:
- remnawave-network
networks:
remnawave-network:
name: remnawave-network
driver: bridge
external: true
Перезапустите контейнер Nginx:
docker compose down && docker compose up -d && docker compose logs -f
Traefik
Конфигурация Traefik
Создайте новый файл конфигурации remnawave-sub-page.yml:
cd /opt/remnawave/traefik/config && nano remnawave-sub-page.yml
Вставьте следующую конфигурацию:
http:
routers:
remnawave-sub-page:
rule: "Host(`SUBSCRIPTION_PAGE_DOMAIN`)"
entrypoints:
- http
middlewares:
- remnawave-sub-page-https-redirect
service: remnawave-sub-page
remnawave-sub-page-secure:
rule: "Host(`SUBSCRIPTION_PAGE_DOMAIN`)"
entrypoints:
- https
middlewares:
tls:
certResolver: letsencrypt
service: remnawave-sub-page
middlewares:
remnawave-sub-page-https-redirect:
redirectScheme:
scheme: https
services:
remnawave-sub-page:
loadBalancer:
servers:
- url: "http://remnawave-subscription-page:3010"
Замените SUBSCRIPTION_PAGE_DOMAIN на ваше доменное имя.
Шаг 5. Использование
Страница подписки будет доступна по адресу https://subdomain.panel.com/<shortUuid>.
Настройка страницы подписки (опционально)
Для настройки доступных приложений и их текстов отредактируйте файл app-config.json.
Интерфейс app-config.json
export interface IAppConfig {
id: `${Lowercase<string>}`
name: string
isFeatured: boolean
isNeedBase64Encoding?: boolean
urlScheme: string
installationStep: {
buttons: {
buttonLink: string
buttonText: {
en: string
fa: string
ru: string
}
}[]
description: {
en: string
fa: string
ru: string
}
}
addSubscriptionStep: {
description: {
en: string
fa: string
ru: string
}
}
connectAndUseStep: {
description: {
en: string
fa: string
ru: string
}
}
additionalBeforeAddSubscriptionStep?: {
buttons: {
buttonLink: string
buttonText: {
en: string
fa: string
ru: string
}
}[]
description: {
en: string
fa: string
ru: string
}
title: {
en: string
fa: string
ru: string
}
}
additionalAfterAddSubscriptionStep?: {
buttons: {
buttonLink: string
buttonText: {
en: string
fa: string
ru: string
}
}[]
description: {
en: string
fa: string
ru: string
}
title: {
en: string
fa: string
ru: string
}
}
}
export interface IPlatformConfig {
ios: IAppConfig[]
android: IAppConfig[]
pc: IAppConfig[]
}
Структура конфигурации
Создайте файл app-config.json со следующей структурой:
{
"ios": [],
"android": [],
"pc": []
}
Свойства конфигурации приложения
| Свойство | Тип | Обязательно | Описание |
|---|---|---|---|
id |
string | Да | Уникальный идентификатор приложения (в нижнем регистре) |
name |
string | Да | Отображаемое имя приложения |
isFeatured |
boolean | Да | Отображать ли приложение как рекомендуемое |
isNeedBase64Encoding |
boolean | Нет | Требуется ли кодирование URL в Base64 |
urlScheme |
string | Да | Схема URL для запуска приложения с подпиской |
installationStep |
object | Да | Инструкции по установке приложения |
addSubscriptionStep |
object | Да | Инструкции по добавлению подписки |
connectAndUseStep |
object | Да | Инструкции по подключению к VPN |
additionalBeforeAddSubscriptionStep |
object | Нет | Дополнительные шаги перед добавлением подписки |
additionalAfterAddSubscriptionStep |
object | Нет | Дополнительные шаги после добавления подписки |
Локализация
Все тексты поддерживают несколько языков:
"description": {
"en": "English text",
"fa": "Persian text",
"ru": "Русский текст"
}
Пример конфигурации приложения
{
"id": "happ",
"name": "Happ",
"isFeatured": true,
"urlScheme": "happ://add/",
"installationStep": {
"buttons": [
{
"buttonLink": "https://apps.apple.com/us/app/happ-proxy-utility/id6504287215",
"buttonText": {
"en": "Open in App Store",
"fa": "باز کردن در App Store",
"ru": "Открыть в App Store"
}
}
],
"description": {
"en": "Open the page in App Store and install the app.",
"fa": "صفحه را در App Store باز کنید و برنامه را نصب کنید.",
"ru": "Откройте страницу в App Store и установите приложение."
}
},
"addSubscriptionStep": {
"description": {
"en": "Click the button below to add subscription",
"fa": "برای افزودن اشتراک روی دکمه زیر کلیک کنید",
"ru": "Нажмите кнопку ниже, чтобы добавить подписку"
}
},
"connectAndUseStep": {
"description": {
"en": "Open the app and connect to the server",
"fa": "برنامه را باز کنید و به سرور متصل شوید",
"ru": "Откройте приложение и подключитесь к серверу"
}
}
}
Дополнительные шаги
Для дополнительных инструкций до или после добавления подписки:
"additionalBeforeAddSubscriptionStep": {
"buttons": [
{
"buttonLink": "https://example.com/guide",
"buttonText": {
"en": "View Guide",
"fa": "مشاهده راهنما",
"ru": "Посмотреть руководство"
}
}
],
"description": {
"en": "Make sure to grant all required permissions",
"fa": "اطمینان حاصل کنید که تمام مجوزهای لازم را اعطا کردهاید",
"ru": "Убедитесь, что предоставили все необходимые разрешения"
},
"title": {
"en": "Permissions",
"fa": "مجوزها",
"ru": "Разрешения"
}
}
Кодирование Base64
Для приложений, требующих кодирования URL в Base64:
"isNeedBase64Encoding": true
Монтирование пользовательского шаблона
Для полного изменения интерфейса страницы подписки смонтируйте файлы index.html и папку assets:
volumes:
- ./index.html:/opt/app/frontend/index.html
- ./assets:/opt/app/frontend/assets
Исходный файл index.html доступен по ссылке: subscription-page/frontend/index.html. Папка assets: subscription-page/frontend/public/assets.
Переменные шаблона
Ваш HTML-шаблон должен содержать следующие переменные:
| Переменная | Описание |
|---|---|
<%= metaTitle %> |
Значение META_TITLE из файла .env |
<%= metaDescription %> |
Значение META_DESCRIPTION из файла .env |
<%- panelData %> |
Base64-кодированные данные из ответа /api/sub/<shortUuid>/info |
Пример использования panelData
let panelData;
panelData = '<%- panelData %>';
try {
panelData = JSON.parse(atob(panelData));
} catch (error) {
console.error('Error parsing panel data:', error);
}
Убедитесь, что все три переменные присутствуют и корректно используются в шаблоне.
Перезапустите контейнер для применения изменений:
docker compose down && docker compose up -d && docker compose logs -f
Монтирование пользовательского app-config.json
Для использования пользовательского файла app-config.json:
volumes:
- ./app-config.json:/opt/app/frontend/assets/app-config.json
Перезапустите контейнер:
docker compose down && docker compose up -d && docker compose logs -f
Полный пример
Ознакомьтесь с полным примером для настройки приложений на разных платформах.
