CI / changes (push) Successful in 15s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 1m0s
CI / go (push) Successful in 1m0s
CI / bird2 (push) Successful in 16s
CI / release (push) Successful in 3m39s
Added public HTTPS endpoints for firewall installation and enrollment scripts, allowing access without API keys. Updated the URL handling in the firewall code to ensure all suggested control plane URLs are served over HTTPS. Enhanced documentation to reflect the new public endpoints and their usage. Updated tests to verify the correct behavior of the new URL handling logic.
362 lines
13 KiB
YAML
362 lines
13 KiB
YAML
# Standalone stack (без profiles): Portainer / `docker compose -f stack.microvps-full.yaml up -d`
|
||
# Эквивалент microvps-full из docker-compose.yaml + docker-compose.microvps-full.yaml,
|
||
# с встроенными 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 (серый облачок).
|
||
#
|
||
# ACME / Let's Encrypt: путь задан в evobgp-edge (--certificatesresolvers...storage).
|
||
# Имя тома traefik_letsencrypt зафиксировано (volumes.traefik_letsencrypt.name), иначе при
|
||
# другом имени стека в Portainer или смене project name создаётся новый пустой том —
|
||
# Traefik не находит acme.json и отдаёт дефолтный сертификат до новой выдачи LE.
|
||
# Не использовать `docker compose down -v` без бэкапа. Если раньше был том с префиксом
|
||
# проекта, перенесите acme.json в том evobgp_traefik_letsencrypt.
|
||
#
|
||
# Долгий сбор логов в файлы на хосте: сервис stack-runtime-logs пишет в каталог
|
||
# ${EVOBGP_RUNTIME_LOGS_HOST_DIR:-./runtime-logs} по одному файлу на сервис.
|
||
# evobgp-all монтирует тот же каталог в /opt/evobgp/runtime-logs для API /v1/runtime-logs/*.
|
||
# Автообновление сервисов из registry (без перезапуска bird2): stack-auto-updater.
|
||
# Настройки через .env: AUTO_UPDATE_ENABLED, AUTO_UPDATE_INTERVAL_SEC,
|
||
# AUTO_UPDATE_SERVICES, AUTO_UPDATE_PROTECTED_SERVICES.
|
||
# Имя проекта в Docker должно совпадать с label com.docker.compose.project:
|
||
# при смене `name:` или имени стека в Portainer задайте COMPOSE_PROJECT_NAME.
|
||
# Требуется доступ к docker.sock (полные права на демон — осознанно).
|
||
name: evobgp-microvps-full
|
||
|
||
configs:
|
||
prometheus_yml:
|
||
content: |
|
||
global:
|
||
scrape_interval: 15s
|
||
evaluation_interval: 15s
|
||
rule_files:
|
||
- /etc/prometheus/alerts.yml
|
||
scrape_configs:
|
||
- job_name: evobgp-all
|
||
metrics_path: /metrics
|
||
static_configs:
|
||
- targets: ["evobgp-all:8080"]
|
||
prometheus_alerts_yml:
|
||
content: |
|
||
groups: []
|
||
|
||
services:
|
||
postgres:
|
||
image: postgres:16-alpine
|
||
restart: unless-stopped
|
||
shm_size: "256mb"
|
||
environment:
|
||
POSTGRES_USER: evobgp
|
||
POSTGRES_PASSWORD: evobgp
|
||
POSTGRES_DB: evobgp
|
||
volumes:
|
||
- pgdata:/var/lib/postgresql/data
|
||
healthcheck:
|
||
test: ["CMD-SHELL", "pg_isready -U evobgp -d evobgp"]
|
||
interval: 10s
|
||
timeout: 5s
|
||
retries: 12
|
||
start_period: 45s
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
nats:
|
||
image: nats:2.10-alpine
|
||
restart: unless-stopped
|
||
command: ["-js", "-m", "8222"]
|
||
ports:
|
||
- "4222:4222"
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
bird2:
|
||
image: ${EVOBGP_REGISTRY:-git.shts.su/denozord}/evobgp-bird2:${EVOBGP_IMAGE_TAG:-latest}
|
||
restart: unless-stopped
|
||
cap_add:
|
||
- NET_ADMIN
|
||
sysctls:
|
||
net.ipv4.ip_forward: "1"
|
||
net.ipv6.conf.all.forwarding: "1"
|
||
volumes:
|
||
- bird_etc:/etc/bird
|
||
- bird_run:/run/bird
|
||
ports:
|
||
- "179:179/tcp"
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
evobgp-agent:
|
||
image: ${EVOBGP_REGISTRY:-git.shts.su/denozord}/evobgp-agent:${EVOBGP_IMAGE_TAG:-latest}
|
||
restart: unless-stopped
|
||
depends_on:
|
||
bird2:
|
||
condition: service_started
|
||
cap_add:
|
||
- NET_ADMIN
|
||
volumes:
|
||
- bird_etc:/etc/bird
|
||
- bird_run:/run/bird
|
||
entrypoint: ["/usr/local/bin/evobgp-agent"]
|
||
command: ["watch", "-socket=/run/bird/bird.ctl", "-watch-interval=30s"]
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
evobgp-all:
|
||
image: ${EVOBGP_REGISTRY:-git.shts.su/denozord}/evobgp-all:${EVOBGP_IMAGE_TAG:-latest}
|
||
restart: unless-stopped
|
||
depends_on:
|
||
postgres:
|
||
condition: service_healthy
|
||
nats:
|
||
condition: service_started
|
||
bird2:
|
||
condition: service_started
|
||
ports:
|
||
- "8080:8080"
|
||
environment:
|
||
EVOBGP_DATABASE_URL: postgres://evobgp:evobgp@postgres:5432/evobgp?sslmode=disable
|
||
EVOBGP_BROKER_URL: nats://nats:4222
|
||
EVOBGP_HTTP_ADDR: ":8080"
|
||
EVOBGP_SEED_DEMO: "1"
|
||
EVOBGP_BIRDC_SOCKET: /run/bird/bird.ctl
|
||
EVOBGP_BIRDC_INTERVAL: 30s
|
||
EVOBGP_BIRD_ACTIVE_DIR: /etc/bird
|
||
EVOBGP_BIRD_STAGING_DIR: /tmp/evobgp-bird-staging
|
||
EVOBGP_SERVICE: evobgp-all
|
||
EVOBGP_RUNTIME_LOGS_DIR: /opt/evobgp/runtime-logs
|
||
# DEV ONLY — не для production (см. docs/access.md).
|
||
EVOBGP_DEV_INSECURE: "1"
|
||
volumes:
|
||
- bird_etc:/etc/bird
|
||
- bird_run:/run/bird:ro
|
||
- type: bind
|
||
source: ${EVOBGP_RUNTIME_LOGS_HOST_DIR:-./runtime-logs}
|
||
target: /opt/evobgp/runtime-logs
|
||
bind:
|
||
create_host_path: true
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
evobgp-web:
|
||
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
|
||
# Публичные firewall-эндпоинты — без WEBUI_IP_WHITELIST (установка с произвольных серверов).
|
||
- traefik.http.routers.evobgp-firewall-public.rule=Host(`${WEBUI_DOMAIN}`) && (Path(`/v1/firewall/install.sh`) || Path(`/v1/firewall/sync-script`) || PathPrefix(`/v1/firewall/enroll`))
|
||
- traefik.http.routers.evobgp-firewall-public.entrypoints=websecure
|
||
- traefik.http.routers.evobgp-firewall-public.tls=true
|
||
- traefik.http.routers.evobgp-firewall-public.tls.certresolver=letsencrypt
|
||
- traefik.http.routers.evobgp-firewall-public.priority=100
|
||
- traefik.http.routers.evobgp-firewall-public.service=evobgp-web
|
||
- 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.priority=10
|
||
- 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:latest
|
||
restart: unless-stopped
|
||
depends_on:
|
||
evobgp-web:
|
||
condition: service_started
|
||
ports:
|
||
- "80:80"
|
||
- "443:443"
|
||
environment:
|
||
DOCKER_API_VERSION: "1.44"
|
||
CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN}
|
||
command:
|
||
- --log.level=INFO
|
||
- --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:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
prometheus:
|
||
image: prom/prometheus:v2.54.1
|
||
restart: unless-stopped
|
||
depends_on:
|
||
evobgp-all:
|
||
condition: service_started
|
||
ports:
|
||
- "9090:9090"
|
||
configs:
|
||
- source: prometheus_yml
|
||
target: /etc/prometheus/prometheus.yml
|
||
- source: prometheus_alerts_yml
|
||
target: /etc/prometheus/alerts.yml
|
||
command:
|
||
- --config.file=/etc/prometheus/prometheus.yml
|
||
- --storage.tsdb.path=/prometheus
|
||
- --storage.tsdb.retention.time=7d
|
||
- --web.enable-lifecycle
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
stack-runtime-logs:
|
||
image: docker:27-cli
|
||
restart: unless-stopped
|
||
depends_on:
|
||
evobgp-all:
|
||
condition: service_started
|
||
environment:
|
||
COMPOSE_PROJECT_NAME: ${COMPOSE_PROJECT_NAME:-evobgp-microvps-full}
|
||
volumes:
|
||
- /var/run/docker.sock:/var/run/docker.sock
|
||
- type: bind
|
||
source: ${EVOBGP_RUNTIME_LOGS_HOST_DIR:-./runtime-logs}
|
||
target: /logs
|
||
bind:
|
||
create_host_path: true
|
||
entrypoint: ["/bin/sh", "-c"]
|
||
command:
|
||
- |
|
||
set -eu
|
||
mkdir -p /logs
|
||
PROJECT=$$COMPOSE_PROJECT_NAME
|
||
SERVICES="postgres nats bird2 evobgp-agent evobgp-all evobgp-web evobgp-edge prometheus"
|
||
log_one() {
|
||
svc=$$1
|
||
f="/logs/$$svc.log"
|
||
while true; do
|
||
cid=$$(docker ps -q \
|
||
-f "label=com.docker.compose.service=$$svc" \
|
||
-f "label=com.docker.compose.project=$$PROJECT" | head -n1)
|
||
if [ -n "$$cid" ]; then
|
||
echo "---- $$(date -u +"%Y-%m-%dT%H:%M:%SZ") attach $$svc $$cid ----" >> "$$f"
|
||
docker logs -f --timestamps "$$cid" >> "$$f" 2>&1 || true
|
||
fi
|
||
sleep 3
|
||
done
|
||
}
|
||
for s in $$SERVICES; do log_one "$$s" & done
|
||
wait
|
||
|
||
stack-auto-updater:
|
||
image: docker:27-cli
|
||
restart: unless-stopped
|
||
depends_on:
|
||
evobgp-all:
|
||
condition: service_started
|
||
environment:
|
||
COMPOSE_PROJECT_NAME: ${COMPOSE_PROJECT_NAME:-evobgp-microvps-full}
|
||
AUTO_UPDATE_ENABLED: ${AUTO_UPDATE_ENABLED:-0}
|
||
AUTO_UPDATE_INTERVAL_SEC: ${AUTO_UPDATE_INTERVAL_SEC:-300}
|
||
AUTO_UPDATE_SERVICES: ${AUTO_UPDATE_SERVICES:-evobgp-all,evobgp-web}
|
||
AUTO_UPDATE_PROTECTED_SERVICES: ${AUTO_UPDATE_PROTECTED_SERVICES:-bird2}
|
||
volumes:
|
||
- /var/run/docker.sock:/var/run/docker.sock
|
||
- type: bind
|
||
source: ./stack.microvps-full.yaml
|
||
target: /stack/stack.microvps-full.yaml
|
||
read_only: true
|
||
entrypoint: ["/bin/sh", "-c"]
|
||
command:
|
||
- |
|
||
set -eu
|
||
PROJECT=$$COMPOSE_PROJECT_NAME
|
||
INTERVAL=$$AUTO_UPDATE_INTERVAL_SEC
|
||
ENABLED=$$AUTO_UPDATE_ENABLED
|
||
if [ "$$ENABLED" != "1" ]; then
|
||
echo "auto-updater disabled: AUTO_UPDATE_ENABLED=$$ENABLED"
|
||
while true; do sleep 3600; done
|
||
fi
|
||
if [ "$$INTERVAL" -lt 30 ]; then
|
||
echo "auto-updater interval too low ($$INTERVAL), forcing to 30"
|
||
INTERVAL=30
|
||
fi
|
||
echo "auto-updater started: project=$$PROJECT interval=$$INTERVAL services=$$AUTO_UPDATE_SERVICES protected=$$AUTO_UPDATE_PROTECTED_SERVICES"
|
||
while true; do
|
||
TS=$$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||
SERVICES_LIST=$$(echo "$$AUTO_UPDATE_SERVICES" | tr ',' ' ')
|
||
PROTECTED_LIST=$$(echo "$$AUTO_UPDATE_PROTECTED_SERVICES" | tr ',' ' ')
|
||
for svc in $$SERVICES_LIST; do
|
||
if [ -z "$$svc" ]; then
|
||
continue
|
||
fi
|
||
skip=0
|
||
for protected in $$PROTECTED_LIST; do
|
||
if [ "$$svc" = "$$protected" ]; then
|
||
echo "$$TS skip protected service '$$svc'"
|
||
skip=1
|
||
break
|
||
fi
|
||
done
|
||
if [ "$$skip" = "1" ]; then
|
||
continue
|
||
fi
|
||
before=$$(docker image inspect "$$(docker compose -p "$$PROJECT" -f /stack/stack.microvps-full.yaml config --images $$svc | tail -n1)" --format '{{.Id}}' 2>/dev/null || true)
|
||
pull_out=$$(docker compose -p "$$PROJECT" -f /stack/stack.microvps-full.yaml pull $$svc 2>&1 || true)
|
||
echo "$$TS pull $$svc: $$pull_out"
|
||
after=$$(docker image inspect "$$(docker compose -p "$$PROJECT" -f /stack/stack.microvps-full.yaml config --images $$svc | tail -n1)" --format '{{.Id}}' 2>/dev/null || true)
|
||
if [ -n "$$after" ] && [ "$$before" != "$$after" ]; then
|
||
echo "$$TS update detected for $$svc, restarting container"
|
||
docker compose -p "$$PROJECT" -f /stack/stack.microvps-full.yaml up -d --no-deps $$svc
|
||
else
|
||
echo "$$TS no update for $$svc"
|
||
fi
|
||
done
|
||
sleep "$$INTERVAL"
|
||
done
|
||
logging:
|
||
driver: json-file
|
||
options:
|
||
max-size: "10m"
|
||
max-file: "3"
|
||
|
||
volumes:
|
||
pgdata:
|
||
bird_etc:
|
||
bird_run:
|
||
traefik_letsencrypt:
|
||
name: evobgp_traefik_letsencrypt
|