From 60d8c297f9cce28bfb11119a7224423d1e2b0ede Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Sat, 23 May 2026 22:01:40 +0300 Subject: [PATCH] docs: update deploy examples --- README.md | 13 +- deploy/examples/.gitignore | 4 + deploy/examples/README.md | 33 ++++ deploy/examples/caddy/.env.example | 41 +++++ deploy/examples/caddy/Caddyfile | 19 +++ deploy/examples/caddy/README.md | 31 ++++ .../caddy/docker-compose.yml} | 107 +++++++------ deploy/examples/newt/.env.example | 42 +++++ deploy/examples/newt/README.md | 33 ++++ deploy/examples/newt/docker-compose.yml | 146 +++++++++++++++++ deploy/examples/nginx/.env.example | 41 +++++ deploy/examples/nginx/README.md | 42 +++++ deploy/examples/nginx/docker-compose.yml | 150 ++++++++++++++++++ deploy/examples/nginx/nginx.conf.template | 62 ++++++++ deploy/examples/nginx/ssl/README.md | 18 +++ deploy/examples/no-proxy/.env.example | 41 +++++ deploy/examples/no-proxy/README.md | 23 +++ .../no-proxy/docker-compose.yml} | 92 ++++++----- docs/deployment.md | 93 +++++------ docs/migration-to-minishop.md | 69 +++++--- docs/webapp.md | 61 ++----- tests/test_migration_doc_accuracy.py | 12 +- 22 files changed, 949 insertions(+), 224 deletions(-) create mode 100644 deploy/examples/.gitignore create mode 100644 deploy/examples/README.md create mode 100644 deploy/examples/caddy/.env.example create mode 100644 deploy/examples/caddy/Caddyfile create mode 100644 deploy/examples/caddy/README.md rename deploy/{compose/docker-compose-caddy.yml => examples/caddy/docker-compose.yml} (67%) create mode 100644 deploy/examples/newt/.env.example create mode 100644 deploy/examples/newt/README.md create mode 100644 deploy/examples/newt/docker-compose.yml create mode 100644 deploy/examples/nginx/.env.example create mode 100644 deploy/examples/nginx/README.md create mode 100644 deploy/examples/nginx/docker-compose.yml create mode 100644 deploy/examples/nginx/nginx.conf.template create mode 100644 deploy/examples/nginx/ssl/README.md create mode 100644 deploy/examples/no-proxy/.env.example create mode 100644 deploy/examples/no-proxy/README.md rename deploy/{compose/docker-compose-remote-server.yml => examples/no-proxy/docker-compose.yml} (63%) diff --git a/README.md b/README.md index 3dad18a..510278b 100644 --- a/README.md +++ b/README.md @@ -109,13 +109,18 @@ docker compose up -d --build # Логи приложения docker compose logs -f backend worker frontend -# Запуск с Caddy -docker compose -f deploy/compose/docker-compose-caddy.yml up -d +# Готовые production-примеры +cd deploy/examples/caddy # или nginx, newt, no-proxy +cp .env.example .env +nano .env +docker compose up -d -# Запуск из готового образа -IMAGE_TAG=3.1.0 docker compose -f deploy/compose/docker-compose-remote-server.yml up -d +# Запуск из готового образа с конкретным тегом +IMAGE_TAG=3.1.0 docker compose up -d ``` +Для production-запуска удобнее брать готовые папки из [`deploy/examples`](deploy/examples): там отдельно собраны варианты для Caddy, Nginx, Newt/Pangolin и прямой публикации портов без reverse proxy. В каждой папке рядом лежат `docker-compose.yml`, `.env.example`, README и нужный proxy-конфиг. + GHCR image names for releases: - `ghcr.io/3252a8/remnawave-minishop-backend` diff --git a/deploy/examples/.gitignore b/deploy/examples/.gitignore new file mode 100644 index 0000000..7da0c9c --- /dev/null +++ b/deploy/examples/.gitignore @@ -0,0 +1,4 @@ +**/.env +nginx/ssl/* +!nginx/ssl/README.md + diff --git a/deploy/examples/README.md b/deploy/examples/README.md new file mode 100644 index 0000000..f79ea0a --- /dev/null +++ b/deploy/examples/README.md @@ -0,0 +1,33 @@ +# Готовые варианты запуска + +В этой папке лежат самодостаточные compose-примеры. Каждый вариант запускается из своей директории обычной командой: + +```bash +cp .env.example .env +nano .env +docker compose up -d +``` + +После старта полезно проверить: + +```bash +docker compose ps +docker compose logs -f backend worker frontend +``` + +## Какой вариант выбрать + +| Папка | Когда использовать | Что править | +| --- | --- | --- | +| [`caddy`](caddy) | Нужен самый простой публичный HTTPS с автоматическими сертификатами Let's Encrypt. | `.env`; при нестандартной схеме можно поправить `Caddyfile`. | +| [`nginx`](nginx) | Уже используете Nginx и готовы положить TLS-сертификаты рядом с примером. | `.env`, `nginx.conf.template`, файлы в `ssl/`. | +| [`newt`](newt) | Публикуете сервисы через Pangolin/Newt без входящих портов на сервере приложения. | `.env` и ресурсы в панели Pangolin. | +| [`no-proxy`](no-proxy) | Нужно напрямую открыть порты backend/frontend или проверить стек без reverse proxy. | `.env`. | + +Для всех вариантов нужны два публичных URL: + +- webhook/backend URL для Telegram, платежных систем и Remnawave webhooks; +- Mini App/frontend URL для Telegram Mini App и Web App. + +Обычно это два домена, например `webhooks.example.com` и `app.example.com`. + diff --git a/deploy/examples/caddy/.env.example b/deploy/examples/caddy/.env.example new file mode 100644 index 0000000..06a6800 --- /dev/null +++ b/deploy/examples/caddy/.env.example @@ -0,0 +1,41 @@ +# Copy this file to .env, fill real values, then run: +# docker compose up -d + +COMPOSE_PROJECT_NAME=remnawave-minishop +IMAGE_TAG=latest + +# Public hostnames. Caddyfile uses the same values and gets HTTPS certificates. +WEBHOOK_HOST=webhooks.example.com +MINIAPP_HOST=app.example.com + +# Optional bind addresses for Caddy. +HTTP_BIND=0.0.0.0:80 +HTTPS_BIND=0.0.0.0:443 + +# Telegram bot token from @BotFather. +BOT_TOKEN=your_bot_token_here + +# Telegram numeric user IDs allowed to open the admin panel. +ADMIN_IDS=123456789 + +# PostgreSQL credentials created by Docker Compose. +POSTGRES_USER=remnawave_minishop +POSTGRES_PASSWORD=change_me_to_a_long_random_password +POSTGRES_DB=remnawave_minishop + +# Keep Web App enabled for the first setup. +WEBAPP_ENABLED=True + +# Stable secrets. Generate with: +# openssl rand -hex 32 +WEBAPP_SESSION_SECRET=change_me_to_64_hex_chars +WEBHOOK_SECRET_TOKEN=change_me_to_64_hex_chars + +# Remnawave panel integration. +PANEL_API_URL=https://panel.example.com/api +PANEL_API_KEY=change_me +PANEL_WEBHOOK_SECRET=change_me + +# Caddy and Docker network ranges that may set X-Forwarded-For. +TRUSTED_PROXIES=127.0.0.1,::1,172.16.0.0/12 + diff --git a/deploy/examples/caddy/Caddyfile b/deploy/examples/caddy/Caddyfile new file mode 100644 index 0000000..edf2b96 --- /dev/null +++ b/deploy/examples/caddy/Caddyfile @@ -0,0 +1,19 @@ +# Domains are read from .env: +# WEBHOOK_HOST=webhooks.example.com +# MINIAPP_HOST=app.example.com +# +# If you prefer hardcoded hostnames, replace {$WEBHOOK_HOST} and +# {$MINIAPP_HOST} with your real domains. + +{$WEBHOOK_HOST} { + encode zstd gzip + + reverse_proxy backend:8080 +} + +{$MINIAPP_HOST} { + encode zstd gzip + + reverse_proxy frontend:80 +} + diff --git a/deploy/examples/caddy/README.md b/deploy/examples/caddy/README.md new file mode 100644 index 0000000..e0db906 --- /dev/null +++ b/deploy/examples/caddy/README.md @@ -0,0 +1,31 @@ +# Запуск с Caddy + +Caddy сам выпускает и продлевает HTTPS-сертификаты. На сервере должны быть открыты входящие `80/tcp` и `443/tcp`, а DNS-записи `WEBHOOK_HOST` и `MINIAPP_HOST` должны смотреть на этот сервер. + +```bash +cp .env.example .env +nano .env +docker compose up -d +``` + +Минимально поменяйте в `.env`: + +- `WEBHOOK_HOST` и `MINIAPP_HOST`; +- `BOT_TOKEN`, `ADMIN_IDS`; +- `POSTGRES_PASSWORD`; +- `WEBAPP_SESSION_SECRET`, `WEBHOOK_SECRET_TOKEN`; +- `PANEL_API_URL`, `PANEL_API_KEY`, `PANEL_WEBHOOK_SECRET`. + +`Caddyfile` лежит рядом и использует домены из `.env`. Если нужна нестандартная логика Caddy, правьте его и перезапускайте: + +```bash +docker compose up -d --force-recreate caddy +``` + +Проверка: + +```bash +docker compose ps +docker compose logs -f caddy backend worker frontend +``` + diff --git a/deploy/compose/docker-compose-caddy.yml b/deploy/examples/caddy/docker-compose.yml similarity index 67% rename from deploy/compose/docker-compose-caddy.yml rename to deploy/examples/caddy/docker-compose.yml index 9e88123..8c5f710 100644 --- a/deploy/compose/docker-compose-caddy.yml +++ b/deploy/examples/caddy/docker-compose.yml @@ -1,14 +1,25 @@ +x-app-env-file: &app_env_file + - ${APP_ENV_FILE:-.env} + +x-app-environment: &app_environment + IMAGE_TAG: ${IMAGE_TAG:-latest} + POSTGRES_HOST: postgres + REDIS_URL: redis://redis:6379/0 + WEBHOOK_BASE_URL: https://${WEBHOOK_HOST:?set WEBHOOK_HOST in .env} + SUBSCRIPTION_MINI_APP_URL: https://${MINIAPP_HOST:?set MINIAPP_HOST in .env}/ + services: postgres: image: postgres:17 - container_name: remnawave-minishop-postgres - env_file: - - ${APP_ENV_FILE:-../../.env} - volumes: - - remnawave-minishop-db-data:/var/lib/postgresql/data - networks: - - remnawave-network restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:?set POSTGRES_USER in .env} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env} + POSTGRES_DB: ${POSTGRES_DB:?set POSTGRES_DB in .env} + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - remnawave-shop healthcheck: test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 5s @@ -17,13 +28,12 @@ services: redis: image: redis:7-alpine - container_name: remnawave-minishop-redis + restart: unless-stopped command: ["redis-server", "--appendonly", "yes"] volumes: - - remnawave-minishop-redis-data:/data + - redis-data:/data networks: - - remnawave-network - restart: unless-stopped + - remnawave-shop healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s @@ -31,19 +41,16 @@ services: retries: 20 migrate: - container_name: remnawave-minishop-migrate image: ghcr.io/3252a8/remnawave-minishop-backend:${IMAGE_TAG:-latest} + restart: "no" command: ["python", "backend/main_migrate.py"] - env_file: - - ${APP_ENV_FILE:-../../.env} + env_file: *app_env_file environment: - IMAGE_TAG: ${IMAGE_TAG:-latest} - POSTGRES_HOST: postgres - REDIS_URL: redis://redis:6379/0 + <<: *app_environment volumes: - shop-data:/app/data networks: - - remnawave-network + - remnawave-shop depends_on: postgres: condition: service_healthy @@ -51,21 +58,17 @@ services: condition: service_healthy backend: - container_name: remnawave-minishop-backend image: ghcr.io/3252a8/remnawave-minishop-backend:${IMAGE_TAG:-latest} - env_file: - - ${APP_ENV_FILE:-../../.env} + restart: unless-stopped + env_file: *app_env_file environment: - IMAGE_TAG: ${IMAGE_TAG:-latest} - POSTGRES_HOST: postgres - REDIS_URL: redis://redis:6379/0 - WEBAPP_ENABLED: "true" + <<: *app_environment + WEBAPP_ENABLED: ${WEBAPP_ENABLED:-true} TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1,172.16.0.0/12} volumes: - shop-data:/app/data networks: - - remnawave-network - restart: unless-stopped + - remnawave-shop depends_on: postgres: condition: service_healthy @@ -81,19 +84,15 @@ services: start_period: 30s worker: - container_name: remnawave-minishop-worker image: ghcr.io/3252a8/remnawave-minishop-worker:${IMAGE_TAG:-latest} - env_file: - - ${APP_ENV_FILE:-../../.env} + restart: unless-stopped + env_file: *app_env_file environment: - IMAGE_TAG: ${IMAGE_TAG:-latest} - POSTGRES_HOST: postgres - REDIS_URL: redis://redis:6379/0 + <<: *app_environment volumes: - shop-data:/app/data networks: - - remnawave-network - restart: unless-stopped + - remnawave-shop depends_on: postgres: condition: service_healthy @@ -103,15 +102,13 @@ services: condition: service_completed_successfully frontend: - container_name: remnawave-minishop-frontend image: ghcr.io/3252a8/remnawave-minishop-frontend:${IMAGE_TAG:-latest} + restart: unless-stopped environment: IMAGE_TAG: ${IMAGE_TAG:-latest} - FRONTEND_PORT: ${FRONTEND_PORT:-8082} - FRONTEND_PUBLIC: via caddy + FRONTEND_PUBLIC: https://${MINIAPP_HOST:?set MINIAPP_HOST in .env}/ networks: - - remnawave-network - restart: unless-stopped + - remnawave-shop depends_on: backend: condition: service_healthy @@ -123,30 +120,32 @@ services: caddy: image: caddy:2-alpine - container_name: remnawave-minishop-caddy + restart: unless-stopped + environment: + WEBHOOK_HOST: ${WEBHOOK_HOST:?set WEBHOOK_HOST in .env} + MINIAPP_HOST: ${MINIAPP_HOST:?set MINIAPP_HOST in .env} + ports: + - "${HTTP_BIND:-0.0.0.0:80}:80" + - "${HTTPS_BIND:-0.0.0.0:443}:443" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - caddy-data:/data + - caddy-config:/config + networks: + - remnawave-shop depends_on: frontend: condition: service_healthy backend: condition: service_healthy - ports: - - "${HTTP_BIND:-0.0.0.0:80}:80" - - "${HTTPS_BIND:-0.0.0.0:443}:443" - volumes: - - ../docker/caddy/Caddyfile:/etc/caddy/Caddyfile:ro - - caddy-data:/data - - caddy-config:/config - networks: - - remnawave-network - restart: unless-stopped networks: - remnawave-network: + remnawave-shop: volumes: - remnawave-minishop-db-data: + postgres-data: name: remnawave-minishop-db-data - remnawave-minishop-redis-data: + redis-data: name: remnawave-minishop-redis-data shop-data: name: remnawave-minishop-shop-data diff --git a/deploy/examples/newt/.env.example b/deploy/examples/newt/.env.example new file mode 100644 index 0000000..e0542ec --- /dev/null +++ b/deploy/examples/newt/.env.example @@ -0,0 +1,42 @@ +# Copy this file to .env, fill real values, then run: +# docker compose up -d + +COMPOSE_PROJECT_NAME=remnawave-minishop +IMAGE_TAG=latest + +# Public hostnames configured as Pangolin resources. +WEBHOOK_HOST=webhooks.example.com +MINIAPP_HOST=app.example.com + +# Newt credentials from Pangolin site settings. +PANGOLIN_ENDPOINT=https://pangolin.example.com +NEWT_ID=change_me +NEWT_SECRET=change_me + +# Telegram bot token from @BotFather. +BOT_TOKEN=your_bot_token_here + +# Telegram numeric user IDs allowed to open the admin panel. +ADMIN_IDS=123456789 + +# PostgreSQL credentials created by Docker Compose. +POSTGRES_USER=remnawave_minishop +POSTGRES_PASSWORD=change_me_to_a_long_random_password +POSTGRES_DB=remnawave_minishop + +# Keep Web App enabled for the first setup. +WEBAPP_ENABLED=True + +# Stable secrets. Generate with: +# openssl rand -hex 32 +WEBAPP_SESSION_SECRET=change_me_to_64_hex_chars +WEBHOOK_SECRET_TOKEN=change_me_to_64_hex_chars + +# Remnawave panel integration. +PANEL_API_URL=https://panel.example.com/api +PANEL_API_KEY=change_me +PANEL_WEBHOOK_SECRET=change_me + +# Pangolin/Newt and Docker network ranges that may set X-Forwarded-For. +TRUSTED_PROXIES=127.0.0.1,::1,172.16.0.0/12 + diff --git a/deploy/examples/newt/README.md b/deploy/examples/newt/README.md new file mode 100644 index 0000000..65a8c18 --- /dev/null +++ b/deploy/examples/newt/README.md @@ -0,0 +1,33 @@ +# Запуск через Newt / Pangolin + +Этот вариант не открывает входящие порты на сервере приложения. Newt подключается к Pangolin, а публичные домены настраиваются ресурсами в панели Pangolin. + +```bash +cp .env.example .env +nano .env +docker compose up -d +``` + +В `.env` заполните: + +- `WEBHOOK_HOST` и `MINIAPP_HOST` - публичные домены ресурсов в Pangolin; +- `PANGOLIN_ENDPOINT`, `NEWT_ID`, `NEWT_SECRET` - значения из настроек site/client в Pangolin; +- обычные переменные приложения: `BOT_TOKEN`, `ADMIN_IDS`, `POSTGRES_PASSWORD`, секреты и доступ к Remnawave. + +Официальная инструкция Pangolin по установке Newt site: . + +В Pangolin создайте два HTTP-ресурса для этого Newt site: + +| Публичный домен | Upstream | +| --- | --- | +| `https://webhooks.example.com` | `http://backend:8080` | +| `https://app.example.com` | `http://frontend:80` | + +Домены в Pangolin должны совпадать с `WEBHOOK_HOST` и `MINIAPP_HOST`. + +Проверка: + +```bash +docker compose ps +docker compose logs -f newt backend worker frontend +``` diff --git a/deploy/examples/newt/docker-compose.yml b/deploy/examples/newt/docker-compose.yml new file mode 100644 index 0000000..744a33a --- /dev/null +++ b/deploy/examples/newt/docker-compose.yml @@ -0,0 +1,146 @@ +x-app-env-file: &app_env_file + - ${APP_ENV_FILE:-.env} + +x-app-environment: &app_environment + IMAGE_TAG: ${IMAGE_TAG:-latest} + POSTGRES_HOST: postgres + REDIS_URL: redis://redis:6379/0 + WEBHOOK_BASE_URL: https://${WEBHOOK_HOST:?set WEBHOOK_HOST in .env} + SUBSCRIPTION_MINI_APP_URL: https://${MINIAPP_HOST:?set MINIAPP_HOST in .env}/ + +services: + postgres: + image: postgres:17 + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:?set POSTGRES_USER in .env} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env} + POSTGRES_DB: ${POSTGRES_DB:?set POSTGRES_DB in .env} + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - remnawave-shop + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 5s + timeout: 5s + retries: 20 + + redis: + image: redis:7-alpine + restart: unless-stopped + command: ["redis-server", "--appendonly", "yes"] + volumes: + - redis-data:/data + networks: + - remnawave-shop + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 20 + + migrate: + image: ghcr.io/3252a8/remnawave-minishop-backend:${IMAGE_TAG:-latest} + restart: "no" + command: ["python", "backend/main_migrate.py"] + env_file: *app_env_file + environment: + <<: *app_environment + volumes: + - shop-data:/app/data + networks: + - remnawave-shop + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + + backend: + image: ghcr.io/3252a8/remnawave-minishop-backend:${IMAGE_TAG:-latest} + restart: unless-stopped + env_file: *app_env_file + environment: + <<: *app_environment + WEBAPP_ENABLED: ${WEBAPP_ENABLED:-true} + TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1,172.16.0.0/12} + volumes: + - shop-data:/app/data + networks: + - remnawave-shop + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + migrate: + condition: service_completed_successfully + healthcheck: + test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/healthz', timeout=3).read()\""] + interval: 30s + timeout: 5s + retries: 5 + start_period: 30s + + worker: + image: ghcr.io/3252a8/remnawave-minishop-worker:${IMAGE_TAG:-latest} + restart: unless-stopped + env_file: *app_env_file + environment: + <<: *app_environment + volumes: + - shop-data:/app/data + networks: + - remnawave-shop + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + migrate: + condition: service_completed_successfully + + frontend: + image: ghcr.io/3252a8/remnawave-minishop-frontend:${IMAGE_TAG:-latest} + restart: unless-stopped + environment: + IMAGE_TAG: ${IMAGE_TAG:-latest} + FRONTEND_PUBLIC: https://${MINIAPP_HOST:?set MINIAPP_HOST in .env}/ + networks: + - remnawave-shop + depends_on: + backend: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/health | grep -q ok"] + interval: 30s + timeout: 5s + retries: 5 + + newt: + image: fosrl/newt:latest + restart: unless-stopped + environment: + PANGOLIN_ENDPOINT: ${PANGOLIN_ENDPOINT:?set PANGOLIN_ENDPOINT in .env} + NEWT_ID: ${NEWT_ID:?set NEWT_ID in .env} + NEWT_SECRET: ${NEWT_SECRET:?set NEWT_SECRET in .env} + HEALTH_FILE: /tmp/healthy + networks: + - remnawave-shop + healthcheck: + test: ["CMD-SHELL", "test -f /tmp/healthy"] + interval: 30s + timeout: 5s + retries: 5 + +networks: + remnawave-shop: + +volumes: + postgres-data: + name: remnawave-minishop-db-data + redis-data: + name: remnawave-minishop-redis-data + shop-data: + name: remnawave-minishop-shop-data diff --git a/deploy/examples/nginx/.env.example b/deploy/examples/nginx/.env.example new file mode 100644 index 0000000..f0ab28f --- /dev/null +++ b/deploy/examples/nginx/.env.example @@ -0,0 +1,41 @@ +# Copy this file to .env, fill real values, then run: +# docker compose up -d + +COMPOSE_PROJECT_NAME=remnawave-minishop +IMAGE_TAG=latest + +# Public hostnames. nginx.conf.template uses the same values. +WEBHOOK_HOST=webhooks.example.com +MINIAPP_HOST=app.example.com + +# Optional bind addresses for Nginx. +HTTP_BIND=0.0.0.0:80 +HTTPS_BIND=0.0.0.0:443 + +# Telegram bot token from @BotFather. +BOT_TOKEN=your_bot_token_here + +# Telegram numeric user IDs allowed to open the admin panel. +ADMIN_IDS=123456789 + +# PostgreSQL credentials created by Docker Compose. +POSTGRES_USER=remnawave_minishop +POSTGRES_PASSWORD=change_me_to_a_long_random_password +POSTGRES_DB=remnawave_minishop + +# Keep Web App enabled for the first setup. +WEBAPP_ENABLED=True + +# Stable secrets. Generate with: +# openssl rand -hex 32 +WEBAPP_SESSION_SECRET=change_me_to_64_hex_chars +WEBHOOK_SECRET_TOKEN=change_me_to_64_hex_chars + +# Remnawave panel integration. +PANEL_API_URL=https://panel.example.com/api +PANEL_API_KEY=change_me +PANEL_WEBHOOK_SECRET=change_me + +# Nginx and Docker network ranges that may set X-Forwarded-For. +TRUSTED_PROXIES=127.0.0.1,::1,172.16.0.0/12 + diff --git a/deploy/examples/nginx/README.md b/deploy/examples/nginx/README.md new file mode 100644 index 0000000..c1b1c22 --- /dev/null +++ b/deploy/examples/nginx/README.md @@ -0,0 +1,42 @@ +# Запуск с Nginx + +Этот пример поднимает Nginx в той же Docker-сети, что и приложение: + +- `WEBHOOK_HOST` проксируется в `backend:8080`; +- `MINIAPP_HOST` проксируется в `frontend:80`; +- `frontend` сам проксирует внутренние `/api`, `/auth` и ассеты тем в `backend:8081`. + +## Подготовка + +```bash +cp .env.example .env +nano .env +``` + +Положите TLS-сертификаты в `ssl/`: + +```text +ssl/ + webhooks.example.com/ + fullchain.pem + privkey.pem + app.example.com/ + fullchain.pem + privkey.pem +``` + +Имена папок должны совпадать с `WEBHOOK_HOST` и `MINIAPP_HOST` в `.env`. + +## Запуск + +```bash +docker compose up -d +docker compose logs -f nginx backend worker frontend +``` + +Если нужно поменять заголовки, лимиты или TLS-настройки, правьте `nginx.conf.template` рядом с compose и перезапускайте Nginx: + +```bash +docker compose up -d --force-recreate nginx +``` + diff --git a/deploy/examples/nginx/docker-compose.yml b/deploy/examples/nginx/docker-compose.yml new file mode 100644 index 0000000..e9574c0 --- /dev/null +++ b/deploy/examples/nginx/docker-compose.yml @@ -0,0 +1,150 @@ +x-app-env-file: &app_env_file + - ${APP_ENV_FILE:-.env} + +x-app-environment: &app_environment + IMAGE_TAG: ${IMAGE_TAG:-latest} + POSTGRES_HOST: postgres + REDIS_URL: redis://redis:6379/0 + WEBHOOK_BASE_URL: https://${WEBHOOK_HOST:?set WEBHOOK_HOST in .env} + SUBSCRIPTION_MINI_APP_URL: https://${MINIAPP_HOST:?set MINIAPP_HOST in .env}/ + +services: + postgres: + image: postgres:17 + restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:?set POSTGRES_USER in .env} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env} + POSTGRES_DB: ${POSTGRES_DB:?set POSTGRES_DB in .env} + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - remnawave-shop + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 5s + timeout: 5s + retries: 20 + + redis: + image: redis:7-alpine + restart: unless-stopped + command: ["redis-server", "--appendonly", "yes"] + volumes: + - redis-data:/data + networks: + - remnawave-shop + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 5s + timeout: 3s + retries: 20 + + migrate: + image: ghcr.io/3252a8/remnawave-minishop-backend:${IMAGE_TAG:-latest} + restart: "no" + command: ["python", "backend/main_migrate.py"] + env_file: *app_env_file + environment: + <<: *app_environment + volumes: + - shop-data:/app/data + networks: + - remnawave-shop + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + + backend: + image: ghcr.io/3252a8/remnawave-minishop-backend:${IMAGE_TAG:-latest} + restart: unless-stopped + env_file: *app_env_file + environment: + <<: *app_environment + WEBAPP_ENABLED: ${WEBAPP_ENABLED:-true} + TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1,172.16.0.0/12} + volumes: + - shop-data:/app/data + networks: + - remnawave-shop + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + migrate: + condition: service_completed_successfully + healthcheck: + test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/healthz', timeout=3).read()\""] + interval: 30s + timeout: 5s + retries: 5 + start_period: 30s + + worker: + image: ghcr.io/3252a8/remnawave-minishop-worker:${IMAGE_TAG:-latest} + restart: unless-stopped + env_file: *app_env_file + environment: + <<: *app_environment + volumes: + - shop-data:/app/data + networks: + - remnawave-shop + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + migrate: + condition: service_completed_successfully + + frontend: + image: ghcr.io/3252a8/remnawave-minishop-frontend:${IMAGE_TAG:-latest} + restart: unless-stopped + environment: + IMAGE_TAG: ${IMAGE_TAG:-latest} + FRONTEND_PUBLIC: https://${MINIAPP_HOST:?set MINIAPP_HOST in .env}/ + networks: + - remnawave-shop + depends_on: + backend: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/health | grep -q ok"] + interval: 30s + timeout: 5s + retries: 5 + + nginx: + image: nginx:1.27-alpine + restart: unless-stopped + environment: + WEBHOOK_HOST: ${WEBHOOK_HOST:?set WEBHOOK_HOST in .env} + MINIAPP_HOST: ${MINIAPP_HOST:?set MINIAPP_HOST in .env} + ports: + - "${HTTP_BIND:-0.0.0.0:80}:80" + - "${HTTPS_BIND:-0.0.0.0:443}:443" + volumes: + - ./nginx.conf.template:/etc/nginx/templates/default.conf.template:ro + - ./ssl:/etc/nginx/ssl:ro + networks: + - remnawave-shop + depends_on: + frontend: + condition: service_healthy + backend: + condition: service_healthy + +networks: + remnawave-shop: + +volumes: + postgres-data: + name: remnawave-minishop-db-data + redis-data: + name: remnawave-minishop-redis-data + shop-data: + name: remnawave-minishop-shop-data diff --git a/deploy/examples/nginx/nginx.conf.template b/deploy/examples/nginx/nginx.conf.template new file mode 100644 index 0000000..829cbe8 --- /dev/null +++ b/deploy/examples/nginx/nginx.conf.template @@ -0,0 +1,62 @@ +upstream remnawave_backend_webhooks { + server backend:8080; + keepalive 16; +} + +upstream remnawave_frontend { + server frontend:80; + keepalive 16; +} + +server { + listen 80; + server_name ${WEBHOOK_HOST} ${MINIAPP_HOST}; + + return 301 https://$host$request_uri; +} + +# WEBHOOK_BASE_URL: Telegram, payment providers and Remnawave webhooks. +server { + listen 443 ssl; + http2 on; + server_name ${WEBHOOK_HOST}; + + ssl_certificate /etc/nginx/ssl/${WEBHOOK_HOST}/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/${WEBHOOK_HOST}/privkey.pem; + + client_max_body_size 20m; + + location / { + proxy_pass http://remnawave_backend_webhooks; + 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-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + +# SUBSCRIPTION_MINI_APP_URL: static Mini App frontend. +# The frontend container proxies /api, /auth and theme/logo assets to backend:8081. +server { + listen 443 ssl; + http2 on; + server_name ${MINIAPP_HOST}; + + ssl_certificate /etc/nginx/ssl/${MINIAPP_HOST}/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/${MINIAPP_HOST}/privkey.pem; + + client_max_body_size 20m; + + location / { + proxy_pass http://remnawave_frontend; + 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-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + } +} + diff --git a/deploy/examples/nginx/ssl/README.md b/deploy/examples/nginx/ssl/README.md new file mode 100644 index 0000000..55c952f --- /dev/null +++ b/deploy/examples/nginx/ssl/README.md @@ -0,0 +1,18 @@ +# TLS-сертификаты для Nginx + +Положите сюда сертификаты для доменов из `.env`. + +Пример структуры: + +```text +ssl/ + webhooks.example.com/ + fullchain.pem + privkey.pem + app.example.com/ + fullchain.pem + privkey.pem +``` + +Если используете wildcard-сертификат, можно положить одинаковые `fullchain.pem` и `privkey.pem` в обе папки. + diff --git a/deploy/examples/no-proxy/.env.example b/deploy/examples/no-proxy/.env.example new file mode 100644 index 0000000..763dee3 --- /dev/null +++ b/deploy/examples/no-proxy/.env.example @@ -0,0 +1,41 @@ +# Copy this file to .env, fill real values, then run: +# docker compose up -d + +COMPOSE_PROJECT_NAME=remnawave-minishop +IMAGE_TAG=latest + +# Direct host port publishing. Use 127.0.0.1:PORT for local-only access. +WEB_SERVER_BIND=0.0.0.0:8080 +FRONTEND_BIND=0.0.0.0:8082 + +# URLs that reach the published ports. +# For local checks HTTP is fine. Telegram webhooks and Mini Apps require +# public HTTPS in production; the app containers themselves do not terminate TLS. +WEBHOOK_PUBLIC_URL=http://127.0.0.1:8080 +MINIAPP_PUBLIC_URL=http://127.0.0.1:8082/ + +# Telegram bot token from @BotFather. +BOT_TOKEN=your_bot_token_here + +# Telegram numeric user IDs allowed to open the admin panel. +ADMIN_IDS=123456789 + +# PostgreSQL credentials created by Docker Compose. +POSTGRES_USER=remnawave_minishop +POSTGRES_PASSWORD=change_me_to_a_long_random_password +POSTGRES_DB=remnawave_minishop + +# Keep Web App enabled for the first setup. +WEBAPP_ENABLED=True + +# Stable secrets. Generate with: +# openssl rand -hex 32 +WEBAPP_SESSION_SECRET=change_me_to_64_hex_chars +WEBHOOK_SECRET_TOKEN=change_me_to_64_hex_chars + +# Remnawave panel integration. +PANEL_API_URL=https://panel.example.com/api +PANEL_API_KEY=change_me +PANEL_WEBHOOK_SECRET=change_me + +TRUSTED_PROXIES=127.0.0.1,::1 diff --git a/deploy/examples/no-proxy/README.md b/deploy/examples/no-proxy/README.md new file mode 100644 index 0000000..bd45498 --- /dev/null +++ b/deploy/examples/no-proxy/README.md @@ -0,0 +1,23 @@ +# Запуск без reverse proxy + +Этот вариант напрямую публикует два HTTP-порта: + +- backend/webhooks: `WEB_SERVER_BIND`, по умолчанию `0.0.0.0:8080`; +- frontend/Mini App: `FRONTEND_BIND`, по умолчанию `0.0.0.0:8082`. + +```bash +cp .env.example .env +nano .env +docker compose up -d +``` + +Важно: контейнеры приложения сами не выпускают TLS-сертификаты. Для реального Telegram webhook и Mini App публичные URL должны быть HTTPS. Используйте этот вариант для локальной проверки, внутренней сети или когда HTTPS уже завершается внешней платформой и дальше трафик приходит на эти порты. + +Проверка локально: + +```bash +curl http://127.0.0.1:8080/healthz +curl http://127.0.0.1:8082/health +docker compose logs -f backend worker frontend +``` + diff --git a/deploy/compose/docker-compose-remote-server.yml b/deploy/examples/no-proxy/docker-compose.yml similarity index 63% rename from deploy/compose/docker-compose-remote-server.yml rename to deploy/examples/no-proxy/docker-compose.yml index a07e2bf..7f2600f 100644 --- a/deploy/compose/docker-compose-remote-server.yml +++ b/deploy/examples/no-proxy/docker-compose.yml @@ -1,14 +1,25 @@ +x-app-env-file: &app_env_file + - ${APP_ENV_FILE:-.env} + +x-app-environment: &app_environment + IMAGE_TAG: ${IMAGE_TAG:-latest} + POSTGRES_HOST: postgres + REDIS_URL: redis://redis:6379/0 + WEBHOOK_BASE_URL: ${WEBHOOK_PUBLIC_URL:?set WEBHOOK_PUBLIC_URL in .env} + SUBSCRIPTION_MINI_APP_URL: ${MINIAPP_PUBLIC_URL:?set MINIAPP_PUBLIC_URL in .env} + services: postgres: image: postgres:17 - container_name: remnawave-minishop-postgres - env_file: - - ${APP_ENV_FILE:-../../.env} - volumes: - - remnawave-minishop-db-data:/var/lib/postgresql/data - networks: - - remnawave-network restart: unless-stopped + environment: + POSTGRES_USER: ${POSTGRES_USER:?set POSTGRES_USER in .env} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env} + POSTGRES_DB: ${POSTGRES_DB:?set POSTGRES_DB in .env} + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - remnawave-shop healthcheck: test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 5s @@ -17,13 +28,12 @@ services: redis: image: redis:7-alpine - container_name: remnawave-minishop-redis + restart: unless-stopped command: ["redis-server", "--appendonly", "yes"] volumes: - - remnawave-minishop-redis-data:/data + - redis-data:/data networks: - - remnawave-network - restart: unless-stopped + - remnawave-shop healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s @@ -31,19 +41,16 @@ services: retries: 20 migrate: - container_name: remnawave-minishop-migrate image: ghcr.io/3252a8/remnawave-minishop-backend:${IMAGE_TAG:-latest} + restart: "no" command: ["python", "backend/main_migrate.py"] - env_file: - - ${APP_ENV_FILE:-../../.env} + env_file: *app_env_file environment: - IMAGE_TAG: ${IMAGE_TAG:-latest} - POSTGRES_HOST: postgres - REDIS_URL: redis://redis:6379/0 + <<: *app_environment volumes: - shop-data:/app/data networks: - - remnawave-network + - remnawave-shop depends_on: postgres: condition: service_healthy @@ -51,22 +58,19 @@ services: condition: service_healthy backend: - container_name: remnawave-minishop-backend image: ghcr.io/3252a8/remnawave-minishop-backend:${IMAGE_TAG:-latest} - env_file: - - ${APP_ENV_FILE:-../../.env} + restart: unless-stopped + env_file: *app_env_file environment: - IMAGE_TAG: ${IMAGE_TAG:-latest} - POSTGRES_HOST: postgres - REDIS_URL: redis://redis:6379/0 - WEBAPP_ENABLED: "true" + <<: *app_environment + WEBAPP_ENABLED: ${WEBAPP_ENABLED:-true} + TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1,::1} + ports: + - "${WEB_SERVER_BIND:-0.0.0.0:8080}:8080" volumes: - shop-data:/app/data networks: - - remnawave-network - ports: - - "127.0.0.1:${WEB_SERVER_PORT:-8080}:8080" - restart: unless-stopped + - remnawave-shop depends_on: postgres: condition: service_healthy @@ -82,19 +86,15 @@ services: start_period: 30s worker: - container_name: remnawave-minishop-worker image: ghcr.io/3252a8/remnawave-minishop-worker:${IMAGE_TAG:-latest} - env_file: - - ${APP_ENV_FILE:-../../.env} + restart: unless-stopped + env_file: *app_env_file environment: - IMAGE_TAG: ${IMAGE_TAG:-latest} - POSTGRES_HOST: postgres - REDIS_URL: redis://redis:6379/0 + <<: *app_environment volumes: - shop-data:/app/data networks: - - remnawave-network - restart: unless-stopped + - remnawave-shop depends_on: postgres: condition: service_healthy @@ -104,17 +104,15 @@ services: condition: service_completed_successfully frontend: - container_name: remnawave-minishop-frontend image: ghcr.io/3252a8/remnawave-minishop-frontend:${IMAGE_TAG:-latest} + restart: unless-stopped environment: IMAGE_TAG: ${IMAGE_TAG:-latest} - FRONTEND_PORT: ${FRONTEND_PORT:-8082} - FRONTEND_PUBLIC: 127.0.0.1:${FRONTEND_PORT:-8082}->80 - networks: - - remnawave-network + FRONTEND_PUBLIC: ${MINIAPP_PUBLIC_URL:?set MINIAPP_PUBLIC_URL in .env} ports: - - "127.0.0.1:${FRONTEND_PORT:-8082}:80" - restart: unless-stopped + - "${FRONTEND_BIND:-0.0.0.0:8082}:80" + networks: + - remnawave-shop depends_on: backend: condition: service_healthy @@ -125,12 +123,12 @@ services: retries: 5 networks: - remnawave-network: + remnawave-shop: volumes: - remnawave-minishop-db-data: + postgres-data: name: remnawave-minishop-db-data - remnawave-minishop-redis-data: + redis-data: name: remnawave-minishop-redis-data shop-data: name: remnawave-minishop-shop-data diff --git a/docs/deployment.md b/docs/deployment.md index df2a4ad..ffa009a 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -21,11 +21,35 @@ docker compose logs -f backend worker frontend - `worker` только после успешных миграций; - `frontend` как отдельный nginx-образ без Python runtime. -Для обратного прокси с Caddy используйте отдельный `deploy/compose/docker-compose-caddy.yml`. - Миграции не запускаются внутри backend. Их выполняет отдельный сервис `migrate`, поэтому старт приложения не создает гонки на схеме БД. +## Готовые папки запуска + +Для production удобнее использовать не корневой compose, а отдельные примеры в +[`deploy/examples`](../deploy/examples). В каждой папке лежат свой `docker-compose.yml`, +`.env.example`, README и нужный конфиг рядом: + +| Папка | Назначение | Запуск | +| --- | --- | --- | +| [`deploy/examples/caddy`](../deploy/examples/caddy) | Caddy с автоматическим HTTPS. | `cp .env.example .env`, заполнить `.env`, `docker compose up -d`. | +| [`deploy/examples/nginx`](../deploy/examples/nginx) | Nginx в Docker-сети приложения, TLS-сертификаты кладутся в `ssl/`. | `cp .env.example .env`, заполнить `.env`, положить сертификаты, `docker compose up -d`. | +| [`deploy/examples/newt`](../deploy/examples/newt) | Pangolin/Newt без входящих портов на сервере приложения. | `cp .env.example .env`, заполнить Newt credentials, создать ресурсы в Pangolin, `docker compose up -d`. | +| [`deploy/examples/no-proxy`](../deploy/examples/no-proxy) | Прямая публикация портов backend/frontend. | `cp .env.example .env`, заполнить публичные URL и порты, `docker compose up -d`. | + +Пример для Caddy: + +```bash +cd deploy/examples/caddy +cp .env.example .env +nano .env +docker compose up -d +docker compose logs -f caddy backend worker frontend +``` + +Корневой `docker-compose.yml` оставлен для локальной сборки из исходников. Примеры в +`deploy/examples` используют готовые GHCR-образы и не требуют указывать `-f`. + ## Миграции При обычном старте миграции применяются автоматически: @@ -57,8 +81,8 @@ docker compose logs migrate - `postgres`: PostgreSQL 17. - `redis`: Redis 7 для FSM, кеша, rate-limit, очередей и locks. -`deploy/compose/docker-compose-caddy.yml` добавляет `caddy` как внешний HTTP/HTTPS reverse proxy. -Перед запуском замените example-домены прямо в `deploy/docker/caddy/Caddyfile`. +В production-примерах внешний доступ добавляют `caddy`, `nginx`, `newt` или прямые `ports` в +соответствующей папке из [`deploy/examples`](../deploy/examples). ## Логи и проверка @@ -77,7 +101,8 @@ curl http://127.0.0.1:8080/health ``` В обычном compose backend публикуется на `127.0.0.1:${WEB_SERVER_PORT:-8080}`, frontend на -`127.0.0.1:${FRONTEND_PORT:-8082}`. В Caddy-варианте проверяйте `HTTP_BIND`. +`127.0.0.1:${FRONTEND_PORT:-8082}`. В новых production-примерах проверяйте bind-переменные +конкретной папки: `HTTP_BIND`, `HTTPS_BIND`, `WEB_SERVER_BIND` или `FRONTEND_BIND`. ## Обновление @@ -192,68 +217,46 @@ docker compose up -d backend worker ## Обратный прокси -Вариант `deploy/compose/docker-compose-caddy.yml` проксирует: +Готовые reverse-proxy примеры лежат в: -- webhook-домен целиком в `backend:8080`; -- Mini App-домен целиком в `frontend:80`; +- [`deploy/examples/caddy`](../deploy/examples/caddy) - Caddy, автоматический HTTPS; +- [`deploy/examples/nginx`](../deploy/examples/nginx) - Nginx, сертификаты кладутся рядом в `ssl/`; +- [`deploy/examples/newt`](../deploy/examples/newt) - Newt/Pangolin, без входящих портов на сервере приложения. + +Во всех вариантах схема одинаковая: + +- webhook/backend-домен целиком идет в `backend:8080`; +- Mini App/frontend-домен целиком идет в `frontend:80`; - API/auth/theme routes Mini App дальше проксируются frontend nginx в `backend:8081`. -Встроенный `deploy/docker/caddy/Caddyfile` намеренно повторяет старую двухдоменную структуру: +Минимальная логика Caddy: ```caddyfile -# Replace the example domains with your real webhook and Mini App hostnames. -app.example.com { - encode zstd gzip - +webhooks.example.com { reverse_proxy backend:8080 } -web.example.com { - encode zstd gzip - +app.example.com { reverse_proxy frontend:80 } ``` -Готовый пример для внешнего Nginx лежит в -[`deploy/docker/nginx/remnawave-minishop.conf`](../deploy/docker/nginx/remnawave-minishop.conf). -Он рассчитан на Nginx в той же Docker network, поэтому использует DNS-имена сервисов: - -```nginx -upstream remnawave_backend_webhooks { - server backend:8080; -} - -upstream remnawave_frontend { - server frontend:80; -} -``` - -Webhook и health-маршруты должны идти в `backend:8080`. Статические ассеты Mini App отдавайте через -`frontend:80`; frontend nginx уже проксирует `/api/*`, `/auth/*` и ассеты тем/логотипов в -`backend:8081`. +Минимальная логика Nginx такая же: `webhooks.example.com` проксируется в `backend:8080`, +`app.example.com` - в `frontend:80`. В `deploy/examples/nginx/nginx.conf.template` уже есть +заголовки `X-Forwarded-*`, редирект HTTP -> HTTPS и пути сертификатов. ## Newt -Если `newt` запущен в одной Compose network с приложением, указывайте внутренние имена сервисов, -а не порты на хосте. +Для Newt используйте [`deploy/examples/newt`](../deploy/examples/newt). В compose уже есть сервис +`newt`, а в `.env.example` - поля `PANGOLIN_ENDPOINT`, `NEWT_ID` и `NEWT_SECRET`. -Если используете Caddy-вариант и хотите публиковать стек одной точкой через Newt/Pangolin, укажите: - -```text -http://caddy:80 -``` - -Если публикуете через Newt/Pangolin без Caddy, настройте отдельные ресурсы: +В Pangolin создайте два HTTP-ресурса для этого Newt site: ```text Mini App / frontend: http://frontend:80 Webhooks / backend: http://backend:8080 ``` -Такой вариант нормальный: Caddy не обязателен. Он нужен только если вы хотите заранее объединить -frontend и backend-маршруты в один внутренний upstream. - `backend:8081` является внутренним WebApp API/auth-сервером для frontend nginx; обычно его не нужно указывать в Newt напрямую. diff --git a/docs/migration-to-minishop.md b/docs/migration-to-minishop.md index d66402e..a5ee041 100644 --- a/docs/migration-to-minishop.md +++ b/docs/migration-to-minishop.md @@ -4,17 +4,25 @@ Если вы используете только готовые Docker-образы и не собираете проект локально, git-команды из ручного способа не нужны. Достаточно обновить -compose-файл до варианта с готовыми образами (`deploy/compose/docker-compose-remote-server.yml`) -и перенести/обновить БД. +compose-файл до одного из готовых примеров в `deploy/examples` и +перенести/обновить БД. Самый прямой вариант без встроенного reverse proxy - +`deploy/examples/no-proxy/docker-compose.yml`; для Caddy, Nginx и Newt есть +такие же самостоятельные папки. Минимальная последовательность: ```bash docker compose down -# Замените compose-файл на актуальный вариант для готовых образов. -# Если файл лежит рядом, можно запускать его явно: -IMAGE_TAG=3.4.0 docker compose -f deploy/compose/docker-compose-remote-server.yml up --no-start +# Скопируйте старый .env в выбранную папку примера и обновите значения там. +cp .env deploy/examples/no-proxy/.env +nano deploy/examples/no-proxy/.env + +# Подготовьте стек из готовых образов. +IMAGE_TAG=3.4.0 docker compose \ + --env-file deploy/examples/no-proxy/.env \ + -f deploy/examples/no-proxy/docker-compose.yml \ + up --no-start # Нужно только при переходе со старого имени volume remnawave-tg-shop-db-data. # Если у вас уже есть remnawave-minishop-db-data, этот шаг пропустите. @@ -23,8 +31,14 @@ docker run --rm \ -v remnawave-minishop-db-data:/to \ alpine sh -c "cd /from && cp -a . /to" -IMAGE_TAG=3.4.0 docker compose -f deploy/compose/docker-compose-remote-server.yml up -d -docker compose -f deploy/compose/docker-compose-remote-server.yml logs migrate +IMAGE_TAG=3.4.0 docker compose \ + --env-file deploy/examples/no-proxy/.env \ + -f deploy/examples/no-proxy/docker-compose.yml \ + up -d +docker compose \ + --env-file deploy/examples/no-proxy/.env \ + -f deploy/examples/no-proxy/docker-compose.yml \ + logs migrate ``` Сервис `migrate` сам применит недостающие схемные миграции к перенесённому @@ -61,7 +75,7 @@ docker compose -f deploy/compose/docker-compose-remote-server.yml logs migrate - старые тома **не удаляются** автоматически — это безопасный бэкап на случай отката; -- сертификаты Caddy (если используется `deploy/compose/docker-compose-caddy.yml`) +- сертификаты Caddy (если используется `deploy/examples/caddy/docker-compose.yml`) тоже переносятся, чтобы Let's Encrypt не выписывал их заново и не упереться в rate limit; - схема БД обновляется автоматически: при первом `docker compose up -d` сервис @@ -89,7 +103,7 @@ docker compose -f deploy/compose/docker-compose-remote-server.yml logs migrate | `remnawave-minishop-db-data` | переименовать из `remnawave-tg-shop-db-data` | переносится скриптом | PostgreSQL | | `remnawave-minishop-redis-data` | — | создаётся пустым | Redis (FSM, rate-limit, cache, очередь webhooks, distributed locks) | | `remnawave-minishop-shop-data` | — | создаётся пустым | `/app/data`: `tariffs.json`, темы Web App, кэш логотипа/emoji | -| `remnawave-minishop-caddy-data` / `…-caddy-config` | переименовать из `remnawave-tg-shop-caddy-*` | переносится скриптом | только при Caddy-варианте | +| `remnawave-minishop-caddy-data` / `remnawave-minishop-caddy-config` | переименовать из `remnawave-tg-shop-caddy-*` | переносится скриптом | только при Caddy-варианте | `redis-data` и `shop-data` стартуют пустыми — это нормально. Redis ничего долгоживущего не хранит (всё либо FSM, либо кеш с TTL), а `data/` инициализируется @@ -152,8 +166,10 @@ bash scripts/migrate_to_minishop.sh Примеры: ```bash -# Caddy-вариант из raw -COMPOSE_FILE=deploy/compose/docker-compose-caddy.yml \ +# Caddy-вариант из raw. +# Перед запуском скопируйте старый .env в deploy/examples/caddy/.env +# и заполните WEBHOOK_HOST / MINIAPP_HOST. +COMPOSE_FILE=deploy/examples/caddy/docker-compose.yml \ bash <(curl -fsSL https://raw.githubusercontent.com/3252a8/remnawave-minishop/main/scripts/migrate_to_minishop.sh) # С переключением origin на форк 3252a8 @@ -238,11 +254,18 @@ docker volume rm remnawave-tg-shop-caddy-data remnawave-tg-shop-caddy-config 2>/ # Локальная сборка docker compose up --no-start --build - # Или Caddy-вариант - docker compose -f deploy/compose/docker-compose-caddy.yml up --no-start + # Или готовый Caddy-вариант из GHCR-образов + cp .env deploy/examples/caddy/.env + nano deploy/examples/caddy/.env + docker compose \ + --env-file deploy/examples/caddy/.env \ + -f deploy/examples/caddy/docker-compose.yml \ + up --no-start - # Или готовый образ - docker compose -f deploy/compose/docker-compose-remote-server.yml up --no-start + # Другие готовые варианты: + # deploy/examples/nginx/docker-compose.yml + # deploy/examples/newt/docker-compose.yml + # deploy/examples/no-proxy/docker-compose.yml ``` 5. **Перенесите том БД в новое имя:** @@ -275,9 +298,10 @@ docker volume rm remnawave-tg-shop-caddy-data remnawave-tg-shop-caddy-config 2>/ ```bash docker compose up -d # или - docker compose -f deploy/compose/docker-compose-caddy.yml up -d - # или - docker compose -f deploy/compose/docker-compose-remote-server.yml up -d + docker compose \ + --env-file deploy/examples/caddy/.env \ + -f deploy/examples/caddy/docker-compose.yml \ + up -d ``` Сервис `migrate` запустится первым, обнаружит перенесённый том, @@ -332,11 +356,12 @@ server { } ``` -Полные примеры (Caddy, Newt/Pangolin) — в [docs/deployment.md](deployment.md) -и [docs/webapp.md](webapp.md). Если раньше прокси указывал на +Полные примеры (Caddy, Nginx, Newt/Pangolin и запуск без reverse proxy) — в +[docs/deployment.md](deployment.md), [docs/webapp.md](webapp.md) и папке +[`deploy/examples`](../deploy/examples). Если раньше прокси указывал на `remnawave-tg-shop:8000` напрямую, после миграции нужно либо переключиться на -`backend:8080` / `frontend:80`, либо использовать встроенный Caddy-вариант, -который уже знает правильную маршрутизацию. +`backend:8080` / `frontend:80`, либо использовать готовый Caddy/Nginx/Newt +пример, который уже знает правильную маршрутизацию. ## Если что-то пошло не так diff --git a/docs/webapp.md b/docs/webapp.md index 9b1ccac..ebf4eea 100644 --- a/docs/webapp.md +++ b/docs/webapp.md @@ -122,59 +122,24 @@ Email-вход работает через одноразовый код: ## Проксирование -Web App должен проксироваться отдельно от вебхуков: +Рекомендуемая production-схема - два публичных домена: -```nginx -upstream remnawave_frontend { - server frontend:80; -} +- `WEBHOOK_BASE_URL`, например `https://webhooks.domain.com`, целиком проксируется в `backend:8080`; +- `SUBSCRIPTION_MINI_APP_URL`, например `https://app.domain.com/`, целиком проксируется в `frontend:80`. -upstream remnawave_backend_webapp { - server backend:8081; -} +`frontend` уже сам проксирует `/api/*`, `/auth/*`, `/webapp-logo` и ассеты тем/логотипов во внутренний +WebApp API на `backend:8081`, поэтому внешний reverse proxy обычно не должен отправлять эти пути в +`backend:8081` напрямую. -upstream remnawave_backend_webhooks { - server backend:8080; -} +Готовые примеры лежат в [`deploy/examples`](../deploy/examples): -server { - server_name app.domain.com; - listen 443 ssl; - http2 on; +- `caddy` - Caddy с автоматическим HTTPS; +- `nginx` - Nginx с сертификатами в соседней папке `ssl/`; +- `newt` - Pangolin/Newt; +- `no-proxy` - прямая публикация портов для проверки или внешней TLS-платформы. - ssl_certificate "/etc/nginx/ssl/app_fullchain.pem"; - ssl_certificate_key "/etc/nginx/ssl/app_privkey.key"; - - location / { - proxy_pass http://remnawave_frontend; - 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; - } - - location ~ ^/(api|auth)/ { - proxy_pass http://remnawave_backend_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; - } - - location /webhook/ { - proxy_pass http://remnawave_backend_webhooks; - 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; - } -} -``` - -В default `docker-compose.yml` наружу публикуются `frontend` и webhook/backend port, а внутри Docker network сервисы доступны друг другу по service DNS names: +В default `docker-compose.yml` наружу публикуются `frontend` и webhook/backend port, а внутри Docker +network сервисы доступны друг другу по service DNS names: ```yaml services: diff --git a/tests/test_migration_doc_accuracy.py b/tests/test_migration_doc_accuracy.py index ec5033f..dafbfca 100644 --- a/tests/test_migration_doc_accuracy.py +++ b/tests/test_migration_doc_accuracy.py @@ -22,8 +22,10 @@ DOC_PATH = REPO_ROOT / "docs" / "migration-to-minishop.md" SCRIPT_PATH = REPO_ROOT / "scripts" / "migrate_to_minishop.sh" COMPOSE_FILES = ( REPO_ROOT / "docker-compose.yml", - REPO_ROOT / "deploy" / "compose" / "docker-compose-caddy.yml", - REPO_ROOT / "deploy" / "compose" / "docker-compose-remote-server.yml", + REPO_ROOT / "deploy" / "examples" / "caddy" / "docker-compose.yml", + REPO_ROOT / "deploy" / "examples" / "nginx" / "docker-compose.yml", + REPO_ROOT / "deploy" / "examples" / "newt" / "docker-compose.yml", + REPO_ROOT / "deploy" / "examples" / "no-proxy" / "docker-compose.yml", ) # Names that the current architecture must produce in at least one compose file. @@ -223,8 +225,10 @@ class DocComposeFileReferencesTests(unittest.TestCase): doc = _read(DOC_PATH) for relpath in ( "docker-compose.yml", - "deploy/compose/docker-compose-caddy.yml", - "deploy/compose/docker-compose-remote-server.yml", + "deploy/examples/caddy/docker-compose.yml", + "deploy/examples/nginx/docker-compose.yml", + "deploy/examples/newt/docker-compose.yml", + "deploy/examples/no-proxy/docker-compose.yml", ): with self.subTest(path=relpath): self.assertIn(relpath, doc)