CI / changes (push) Successful in 5s
CI / commitlint (push) Skipped
CI / openapi (push) Successful in 38s
CI / web (push) Successful in 55s
CI / go (push) Successful in 1m4s
CI / bird2 (push) Successful in 17s
CI / release (push) Successful in 4m17s
Перевести CI, semantic-release, bake и compose с git.shts.su на git.shx.one. Co-authored-by: Cursor <cursoragent@cursor.com>
368 lines
14 KiB
YAML
368 lines
14 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.shx.one/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.shx.one/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.shx.one/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
|
||
# Portal SSO (JWT) — см. docs/access.md / auth-portal integrate-evobgp.md
|
||
AUTH_REQUIRED: ${AUTH_REQUIRED:-false}
|
||
AUTH_JWT_SECRET: ${AUTH_JWT_SECRET:-}
|
||
AUTH_ISSUER: ${AUTH_ISSUER:-https://auth.shnt.top}
|
||
AUTH_PORTAL_URL: ${AUTH_PORTAL_URL:-}
|
||
EVOBGP_PORTAL_TENANT_ID: ${EVOBGP_PORTAL_TENANT_ID:-}
|
||
# 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.shx.one/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
|