From 42a2e18047b9a7d8c358bdab7abf448fe36467b7 Mon Sep 17 00:00:00 2001 From: Denozordec Date: Fri, 4 Sep 2026 18:54:26 +0700 Subject: [PATCH] =?UTF-8?q?docs:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=20=D0=BE=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B4-=D1=80=D0=B0=D0=B7=D0=B2=D1=91=D1=80?= =?UTF-8?q?=D1=82=D1=8B=D0=B2=D0=B0=D0=BD=D0=B8=D0=B8=20Docker=20=D0=B8=20?= =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D1=82=D1=8C=20=D1=81=D1=81?= =?UTF-8?q?=D1=8B=D0=BB=D0=BA=D0=B8=20=D0=BD=D0=B0=20=D1=80=D0=B5=D0=BF?= =?UTF-8?q?=D0=BE=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/settings.local.json | 6 +- README.md | 6 +- deploy/docker-compose.traefik.yml | 169 ++++++++++++++++++++++++++++++ deploy/env.traefik.example | 26 +++++ 4 files changed, 203 insertions(+), 4 deletions(-) create mode 100644 deploy/docker-compose.traefik.yml create mode 100644 deploy/env.traefik.example diff --git a/.claude/settings.local.json b/.claude/settings.local.json index adbe773..cfb4d7d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,9 +12,9 @@ "Bash(curl -s -o /dev/null -w '%{http_code}' http://localhost:__TRACKED_VAR__/dashboard)", "Bash(curl -s -o /dev/null -w '%{http_code}' http://localhost:3333__TRACKED_VAR__)", "Bash(curl -s -o /dev/null -w '%{http_code}' http://localhost:59959__TRACKED_VAR__)", - "WebFetch(domain:git.shts.su)", - "Bash(curl -s \"https://git.shts.su/denozord/router-lists-ui/raw/branch/v5/frontend/src/RouteOptimizerPage.jsx\")", - "Bash(curl -s \"https://git.shts.su/denozord/router-lists-ui/raw/branch/v5/frontend/src/OspfToolsPage.jsx\")", + "WebFetch(domain:git.shx.one)", + "Bash(curl -s \"https://git.shx.one/denozord/router-lists-ui/raw/branch/v5/frontend/src/RouteOptimizerPage.jsx\")", + "Bash(curl -s \"https://git.shx.one/denozord/router-lists-ui/raw/branch/v5/frontend/src/OspfToolsPage.jsx\")", "Bash(node -e ' *)", "Bash(powershell -Command \"Get-Item 'C:\\\\Users\\\\shats\\\\.claude\\\\projects\\\\C--Users-shats-Dev-MikrotikManager-3\\\\b1dbd554-4665-40c0-bb5b-19d63bb494a0.jsonl'\")", "Bash(node -e \"const { createRequire } = require\\('module'\\); const r = createRequire\\(__filename\\); const lucide = r\\('lucide-react'\\); ['SlackIcon','WebhookIcon'].forEach\\(n => console.log\\(n, !!lucide[n]\\)\\)\")", diff --git a/README.md b/README.md index 019ed58..53002d6 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,11 @@ npm --prefix backend run db:studio ## Прод-развёртывание Docker -Эталон: `deploy/docker-compose.yml`. Рабочий каталог для команд compose — `deploy/` (или укажите `-f deploy/docker-compose.yml` из корня репозитория). +Эталон без reverse-proxy: `deploy/docker-compose.yml` (порты `3000` / `8000` на хост). + +Стек с Traefik + HTTPS (Let's Encrypt DNS-01 / Cloudflare), по аналогии с CDNManager: [`deploy/docker-compose.traefik.yml`](deploy/docker-compose.traefik.yml) + [`deploy/env.traefik.example`](deploy/env.traefik.example). На сервере публикуются только `:80`/`:443`; frontend получает HTTPS, `/api` и `/health` проксируются на backend внутри сети `mmapp`. Домен по умолчанию: `mm.shnt.top`. + +Рабочий каталог для команд compose — `deploy/` (или укажите `-f deploy/docker-compose.yml` / `-f deploy/docker-compose.traefik.yml` из корня репозитория). ### Прод-контейнеры diff --git a/deploy/docker-compose.traefik.yml b/deploy/docker-compose.traefik.yml new file mode 100644 index 0000000..ead2267 --- /dev/null +++ b/deploy/docker-compose.traefik.yml @@ -0,0 +1,169 @@ +# MikrotikManager + Traefik in one Compose stack (production). +# Docs: README.md (раздел «Прод-развёртывание Docker») +# +# On server: +# mkdir -p /opt/mmapp/data /opt/mmapp/state +# cp deploy/docker-compose.traefik.yml /opt/mmapp/docker-compose.yml +# cp deploy/env.traefik.example /opt/mmapp/.env # fill secrets +# cp deploy/updater/targets.json.example /opt/mmapp/updater/targets.json +# docker login git.shx.one +# cd /opt/mmapp && docker compose pull && docker compose up -d +# +# Traffic: +# Internet → :80/:443 (Traefik) → frontend:3000 +# └─ rewrite /api,/health → backend:8000 (internal) +# Backend is not published on the host — only Traefik exposes 80/443. + +services: + traefik: + image: traefik:${TRAEFIK_IMAGE_TAG:-v3.7} + container_name: mmapp-traefik + restart: unless-stopped + security_opt: + - no-new-privileges:true + ports: + - "${TRAEFIK_HTTP_PORT:-80}:80" + - "${TRAEFIK_HTTPS_PORT:-443}:443" + environment: + CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN:?set CF_DNS_API_TOKEN in .env} + # Optional if DNS token lacks Zone:Read: + # CF_ZONE_API_TOKEN: ${CF_ZONE_API_TOKEN:-} + command: + - --log.level=${TRAEFIK_LOG_LEVEL:-INFO} + - --api.dashboard=false + - --providers.docker=true + - --providers.docker.exposedbydefault=false + - --providers.docker.network=mmapp + - --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:?set LETSENCRYPT_EMAIL in .env} + - --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 + networks: + - mmapp + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + backend: + image: git.shx.one/denozord/mikrotikmanager-backend:${MM_BACKEND_IMAGE_TAG:-latest} + pull_policy: always + container_name: mmapp-backend + restart: unless-stopped + depends_on: + - traefik + # No host ports — frontend reaches backend on the Compose network. + environment: + NODE_ENV: production + PORT: "8000" + DATABASE_PATH: /app/data/mikrotik.db + CORS_ORIGIN: ${CORS_ORIGIN:-https://mm.shnt.top} + volumes: + - ./data:/app/data + networks: + - mmapp + labels: + mmapp.updater.managed: "true" + mmapp.updater.target: backend + mmapp.updater.image: git.shx.one/denozord/mikrotikmanager-backend:${MM_BACKEND_IMAGE_TAG:-latest} + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://127.0.0.1:8000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))", + ] + interval: 30s + timeout: 5s + retries: 3 + start_period: 15s + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + frontend: + image: git.shx.one/denozord/mikrotikmanager-frontend:${MM_FRONTEND_IMAGE_TAG:-latest} + pull_policy: always + container_name: mmapp-frontend + restart: unless-stopped + depends_on: + - backend + # No host ports — only Traefik publishes 80/443. + environment: + BACKEND_INTERNAL_URL: http://backend:8000 + networks: + - mmapp + labels: + - mmapp.updater.managed=true + - mmapp.updater.target=frontend + - mmapp.updater.image=git.shx.one/denozord/mikrotikmanager-frontend:${MM_FRONTEND_IMAGE_TAG:-latest} + - traefik.enable=true + - traefik.docker.network=mmapp + - traefik.http.routers.mmapp.rule=Host(`${MM_DOMAIN:-mm.shnt.top}`) + - traefik.http.routers.mmapp.entrypoints=websecure + - traefik.http.routers.mmapp.tls=true + - traefik.http.routers.mmapp.tls.certresolver=letsencrypt + - traefik.http.services.mmapp.loadbalancer.server.port=3000 + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "fetch('http://127.0.0.1:3000/dashboard').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))", + ] + interval: 30s + timeout: 5s + retries: 3 + start_period: 25s + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + + updater: + image: git.shx.one/denozord/mikrotikmanager-updater:${MM_UPDATER_IMAGE_TAG:-latest} + pull_policy: always + container_name: mmapp-updater + restart: unless-stopped + depends_on: + - frontend + environment: + REGISTRY: git.shx.one + REGISTRY_USERNAME: ${REGISTRY_USERNAME:-} + REGISTRY_PASSWORD: ${REGISTRY_PASSWORD:-} + POLL_INTERVAL_SECONDS: ${POLL_INTERVAL_SECONDS:-300} + HEALTH_TIMEOUT_SECONDS: ${HEALTH_TIMEOUT_SECONDS:-120} + STOP_TIMEOUT_SECONDS: ${STOP_TIMEOUT_SECONDS:-30} + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./state:/state + - ./updater/targets.json:/etc/updater/targets.json:ro + networks: + - mmapp + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" + +volumes: + traefik_letsencrypt: + name: mmapp_traefik_letsencrypt + +networks: + mmapp: + name: mmapp diff --git a/deploy/env.traefik.example b/deploy/env.traefik.example new file mode 100644 index 0000000..e04cf59 --- /dev/null +++ b/deploy/env.traefik.example @@ -0,0 +1,26 @@ +# Production .env for deploy/docker-compose.traefik.yml (MikrotikManager + Traefik). +# Copy to /opt/mmapp/.env and fill secrets. Do not commit. + +# --- Traefik / Let's Encrypt (Cloudflare DNS-01) --- +# Token for ACME only (Zone DNS Edit). Not used by the app itself. +CF_DNS_API_TOKEN= +LETSENCRYPT_EMAIL=admin@shnt.top +MM_DOMAIN=mm.shnt.top +# Must match the public HTTPS origin of the UI (same as MM_DOMAIN with https://). +CORS_ORIGIN=https://mm.shnt.top +# TRAEFIK_IMAGE_TAG=v3.7 +# TRAEFIK_HTTP_PORT=80 +# TRAEFIK_HTTPS_PORT=443 +# TRAEFIK_LOG_LEVEL=INFO + +# --- Images --- +MM_BACKEND_IMAGE_TAG=latest +MM_FRONTEND_IMAGE_TAG=latest +MM_UPDATER_IMAGE_TAG=latest + +# --- Updater (optional; needed for private registry pull) --- +REGISTRY_USERNAME= +REGISTRY_PASSWORD= +# POLL_INTERVAL_SECONDS=300 +# HEALTH_TIMEOUT_SECONDS=120 +# STOP_TIMEOUT_SECONDS=30