diff --git a/deploy/compose/.env.example b/deploy/compose/.env.example index b3cc85a..faac5dc 100644 --- a/deploy/compose/.env.example +++ b/deploy/compose/.env.example @@ -3,3 +3,6 @@ EVOBGP_REGISTRY=git.shts.su/denozord EVOBGP_IMAGE_TAG=latest + +# Для защищенного Web UI (Traefik + Let's Encrypt + Cloudflare DNS challenge) +# используйте второй файл рядом: .env.web-sec (шаблон: .env.web-sec.example) diff --git a/deploy/compose/.env.stack.microvps-full.example b/deploy/compose/.env.stack.microvps-full.example new file mode 100644 index 0000000..a4b0a41 --- /dev/null +++ b/deploy/compose/.env.stack.microvps-full.example @@ -0,0 +1,14 @@ +# Standalone stack env (Linux): stack.microvps-full.yaml +# Использование: +# cp .env.stack.microvps-full.example .env.stack.microvps-full +# docker compose --env-file .env.stack.microvps-full -f stack.microvps-full.yaml up -d + +# Образы EvoBGP +EVOBGP_REGISTRY=git.shts.su/denozord +EVOBGP_IMAGE_TAG=latest + +# Защищенный Web UI (Traefik + Let's Encrypt + Cloudflare DNS challenge) +WEBUI_DOMAIN=bgp.example.com +WEBUI_IP_WHITELIST=203.0.113.10/32,198.51.100.0/24 +LETSENCRYPT_EMAIL=admin@example.com +CF_DNS_API_TOKEN=replace_me diff --git a/deploy/compose/.env.web-sec.example b/deploy/compose/.env.web-sec.example new file mode 100644 index 0000000..4c1b157 --- /dev/null +++ b/deploy/compose/.env.web-sec.example @@ -0,0 +1,16 @@ +# Параметры защищенного Web UI (Traefik + Let's Encrypt DNS challenge через Cloudflare). +# Используйте вместе с deploy/compose/.env: +# docker compose --env-file .env --env-file .env.web-sec ... + +# FQDN для Web UI (A/AAAA запись должна указывать на VPS; Cloudflare: DNS only). +WEBUI_DOMAIN=bgp.example.com + +# Разрешенные IP/CIDR (через запятую). +# Пример: один IP + подсеть офиса. +WEBUI_IP_WHITELIST=203.0.113.10/32,198.51.100.0/24 + +# Email для ACME аккаунта Let's Encrypt. +LETSENCRYPT_EMAIL=admin@example.com + +# Cloudflare API token с правами Zone:DNS:Edit только на нужную зону. +CF_DNS_API_TOKEN=replace_me diff --git a/deploy/compose/docker-compose.yaml b/deploy/compose/docker-compose.yaml index 784575a..7784241 100644 --- a/deploy/compose/docker-compose.yaml +++ b/deploy/compose/docker-compose.yaml @@ -243,8 +243,15 @@ services: restart: unless-stopped depends_on: - evobgp-all - ports: - - "3000:80" + labels: + - traefik.enable=true + - traefik.http.routers.evobgp-web.rule=Host(`${WEBUI_DOMAIN}`) + - traefik.http.routers.evobgp-web.entrypoints=websecure + - traefik.http.routers.evobgp-web.tls=true + - traefik.http.routers.evobgp-web.tls.certresolver=letsencrypt + - traefik.http.routers.evobgp-web.middlewares=webui-ipwhitelist@docker + - traefik.http.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST} + - traefik.http.services.evobgp-web.loadbalancer.server.port=80 logging: *default-logging deploy: resources: @@ -252,6 +259,40 @@ services: cpus: "0.5" memory: 128M + evobgp-edge: + profiles: ["microvps-full"] + image: traefik:v3.1 + restart: unless-stopped + depends_on: + - evobgp-web-microvps + ports: + - "80:80" + - "443:443" + environment: + CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN} + command: + - --api.dashboard=false + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL} + - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json + - --certificatesresolvers.letsencrypt.acme.dnschallenge=true + - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare + - --certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=15 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - traefik_letsencrypt:/letsencrypt + logging: *default-logging + deploy: + resources: + limits: + cpus: "0.35" + memory: 128M + prometheus-microvps: profiles: ["microvps-full"] image: prom/prometheus:v2.54.1 @@ -340,3 +381,4 @@ volumes: pgdata: bird_etc: bird_run: + traefik_letsencrypt: diff --git a/deploy/compose/stack.microvps-full.yaml b/deploy/compose/stack.microvps-full.yaml index 2191b87..23681a7 100644 --- a/deploy/compose/stack.microvps-full.yaml +++ b/deploy/compose/stack.microvps-full.yaml @@ -3,6 +3,9 @@ # с встроенными configs для Prometheus (как у вас). # # Postgres: shm_size + start_period — иначе часто «dependency postgres failed» на слабом хосте. +# Для защищенного Web UI задайте переменные окружения перед запуском: +# WEBUI_DOMAIN, WEBUI_IP_WHITELIST, LETSENCRYPT_EMAIL, CF_DNS_API_TOKEN +# Cloudflare запись для WEBUI_DOMAIN: DNS only (серый облачок). name: evobgp-microvps-full configs: @@ -58,7 +61,7 @@ services: max-file: "3" bird2: - image: git.shts.su/denozord/evobgp-bird2:latest + image: ${EVOBGP_REGISTRY:-git.shts.su/denozord}/evobgp-bird2:${EVOBGP_IMAGE_TAG:-latest} restart: unless-stopped cap_add: - NET_ADMIN @@ -77,7 +80,7 @@ services: max-file: "3" evobgp-agent: - image: git.shts.su/denozord/evobgp-agent:latest + image: ${EVOBGP_REGISTRY:-git.shts.su/denozord}/evobgp-agent:${EVOBGP_IMAGE_TAG:-latest} restart: unless-stopped depends_on: bird2: @@ -96,7 +99,7 @@ services: max-file: "3" evobgp-all: - image: git.shts.su/denozord/evobgp-all:latest + image: ${EVOBGP_REGISTRY:-git.shts.su/denozord}/evobgp-all:${EVOBGP_IMAGE_TAG:-latest} restart: unless-stopped depends_on: postgres: @@ -127,13 +130,53 @@ services: max-file: "3" evobgp-web: - image: git.shts.su/denozord/evobgp-web-all:latest + image: ${EVOBGP_REGISTRY:-git.shts.su/denozord}/evobgp-web-all:${EVOBGP_IMAGE_TAG:-latest} restart: unless-stopped depends_on: evobgp-all: condition: service_started + labels: + - traefik.enable=true + - traefik.http.routers.evobgp-web.rule=Host(`${WEBUI_DOMAIN}`) + - traefik.http.routers.evobgp-web.entrypoints=websecure + - traefik.http.routers.evobgp-web.tls=true + - traefik.http.routers.evobgp-web.tls.certresolver=letsencrypt + - traefik.http.routers.evobgp-web.middlewares=webui-ipwhitelist@docker + - traefik.http.middlewares.webui-ipwhitelist.ipallowlist.sourcerange=${WEBUI_IP_WHITELIST} + - traefik.http.services.evobgp-web.loadbalancer.server.port=80 + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + evobgp-edge: + image: traefik:v3.1 + restart: unless-stopped + depends_on: + evobgp-web: + condition: service_started ports: - - "3000:80" + - "80:80" + - "443:443" + environment: + CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN} + command: + - --api.dashboard=false + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --entrypoints.web.address=:80 + - --entrypoints.websecure.address=:443 + - --entrypoints.web.http.redirections.entrypoint.to=websecure + - --entrypoints.web.http.redirections.entrypoint.scheme=https + - --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL} + - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json + - --certificatesresolvers.letsencrypt.acme.dnschallenge=true + - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare + - --certificatesresolvers.letsencrypt.acme.dnschallenge.delaybeforecheck=15 + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - traefik_letsencrypt:/letsencrypt logging: driver: json-file options: @@ -168,3 +211,4 @@ volumes: pgdata: bird_etc: bird_run: + traefik_letsencrypt: diff --git a/docs/quickstart.md b/docs/quickstart.md index aa19379..ab2f01f 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -109,35 +109,57 @@ docker compose --profile microvps down -v Из каталога `deploy\compose` (как в варианте 1: `.env` из `.env.example`, при необходимости `docker login git.shts.su`): +1. Подготовьте отдельный файл параметров защищенного UI: + +```powershell +Copy-Item .env.web-sec.example .env.web-sec -Force +``` + +2. Заполните в `.env.web-sec`: +- `WEBUI_DOMAIN` — домен панели; +- `WEBUI_IP_WHITELIST` — список разрешенных IP/CIDR через запятую; +- `LETSENCRYPT_EMAIL` — email для ACME; +- `CF_DNS_API_TOKEN` — Cloudflare token для `DNS challenge` (минимальные права `Zone:DNS:Edit` на нужной зоне). + +3. В Cloudflare для `WEBUI_DOMAIN` используйте запись в режиме **DNS only** (серый облачок), указывающую на публичный IP VPS. + +4. Запускайте compose с двумя env-файлами: + ```powershell # С ужатыми лимитами и EVOBGP_BROKER_URL=nats://… (ориентир под ~1 ГиБ RAM на хосте) -docker compose -f docker-compose.yaml -f docker-compose.microvps-full.yaml --profile microvps-full pull -docker compose -f docker-compose.yaml -f docker-compose.microvps-full.yaml --profile microvps-full up -d +docker compose --env-file .env --env-file .env.web-sec -f docker-compose.yaml -f docker-compose.microvps-full.yaml --profile microvps-full pull +docker compose --env-file .env --env-file .env.web-sec -f docker-compose.yaml -f docker-compose.microvps-full.yaml --profile microvps-full up -d ``` Только профиль `microvps-full` и **без** файла `docker-compose.microvps-full.yaml` (лимиты как в базовом compose, без принудительной подстановки брокера в `evobgp-all`): ```powershell -docker compose --profile microvps-full pull -docker compose --profile microvps-full up -d +docker compose --env-file .env --env-file .env.web-sec --profile microvps-full pull +docker compose --env-file .env --env-file .env.web-sec --profile microvps-full up -d ``` При необходимости задайте брокер вручную для `evobgp-all` (override или `.env` рядом с compose): `EVOBGP_BROKER_URL=nats://nats:4222`. | Порт | Назначение | |------|------------| -| **3000** | Web UI (nginx → API под капотом) | +| **80** | HTTP вход (редирект на HTTPS через Traefik) | +| **443** | HTTPS Web UI (Traefik + Let's Encrypt) | | **8080** | Прямой HTTP API (`evobgp-all`) | | **9090** | Prometheus (`prometheus-microvps`) | | **4222** | NATS | | **179** | BGP (BIRD2), как в варианте 1 | -Проверка UI: откройте `http://:3000`. Health API: `http://:8080/v1/health`. +Проверка UI: +- `http://` должен редиректить на `https://`; +- с IP из `WEBUI_IP_WHITELIST` UI доступен по HTTPS; +- с неразрешенного IP Traefik вернет `403`. + +Health API: `http://:8080/v1/health`. Остановка (если поднимали с двумя `-f`): ```powershell -docker compose -f docker-compose.yaml -f docker-compose.microvps-full.yaml --profile microvps-full down +docker compose --env-file .env --env-file .env.web-sec -f docker-compose.yaml -f docker-compose.microvps-full.yaml --profile microvps-full down ``` ## Вариант 2: Docker, профиль reference