Страница подписки Remnawave

Страница подписки 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:

app-config (4).json (33,9 КБ)

Для использования пользовательского файла 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

Полный пример

Ознакомьтесь с полным примером для настройки приложений на разных платформах.