#!/usr/bin/env bash # Run MikrotikManager beside an already-running CDNManager Traefik stack. # Does NOT start a second Traefik — attaches frontend to network `cdnmanager`. # # Usage (on the VPS): # curl -fsSL -o /tmp/run-beside-cdn-traefik.sh \ # https://git.shx.one/denozord/MikrotikManager/raw/branch/main/deploy/run-beside-cdn-traefik.sh # chmod +x /tmp/run-beside-cdn-traefik.sh # sudo MM_DOMAIN=mm.shnt.top /tmp/run-beside-cdn-traefik.sh # # Or copy this file to the server and run it. # # Env overrides: # MM_DOMAIN=mm.shnt.top # CORS_ORIGIN=https://mm.shnt.top # MM_ROOT=/opt/mmapp # MM_BACKEND_IMAGE_TAG=latest # MM_FRONTEND_IMAGE_TAG=latest # MM_UPDATER_IMAGE_TAG=latest # REGISTRY_USERNAME=… REGISTRY_PASSWORD=… # optional private pull set -euo pipefail MM_DOMAIN="${MM_DOMAIN:-mm.shnt.top}" CORS_ORIGIN="${CORS_ORIGIN:-https://${MM_DOMAIN}}" MM_ROOT="${MM_ROOT:-/opt/mmapp}" MM_BACKEND_IMAGE_TAG="${MM_BACKEND_IMAGE_TAG:-latest}" MM_FRONTEND_IMAGE_TAG="${MM_FRONTEND_IMAGE_TAG:-latest}" MM_UPDATER_IMAGE_TAG="${MM_UPDATER_IMAGE_TAG:-latest}" REGISTRY="${REGISTRY:-git.shx.one}" BACKEND_IMAGE="${REGISTRY}/denozord/mikrotikmanager-backend:${MM_BACKEND_IMAGE_TAG}" FRONTEND_IMAGE="${REGISTRY}/denozord/mikrotikmanager-frontend:${MM_FRONTEND_IMAGE_TAG}" UPDATER_IMAGE="${REGISTRY}/denozord/mikrotikmanager-updater:${MM_UPDATER_IMAGE_TAG}" echo "==> Check CDNManager Traefik network" if ! docker network inspect cdnmanager >/dev/null 2>&1; then echo "ERROR: Docker network 'cdnmanager' not found." >&2 echo "Start CDNManager Traefik stack first (deploy/docker-compose.traefik.yml)." >&2 exit 1 fi if ! docker inspect cdnmanager-traefik >/dev/null 2>&1; then echo "WARN: container 'cdnmanager-traefik' not found — labels may not be routed." >&2 fi echo "==> Prepare dirs under ${MM_ROOT}" mkdir -p "${MM_ROOT}/data" "${MM_ROOT}/state" "${MM_ROOT}/updater" TARGETS="${MM_ROOT}/updater/targets.json" if [[ ! -f "${TARGETS}" ]]; then cat >"${TARGETS}" <<'EOF' { "targets": [ { "id": "backend", "container_name": "mmapp-backend", "image": "git.shx.one/denozord/mikrotikmanager-backend:latest", "health": { "type": "http", "url": "http://backend:8000/health", "expect_status": 200 } }, { "id": "frontend", "container_name": "mmapp-frontend", "image": "git.shx.one/denozord/mikrotikmanager-frontend:latest", "health": { "type": "http", "url": "http://frontend:3000/dashboard", "expect_status": 200 } } ] } EOF echo " wrote ${TARGETS}" fi echo "==> Ensure internal network mmapp" docker network inspect mmapp >/dev/null 2>&1 || docker network create mmapp >/dev/null if [[ -n "${REGISTRY_USERNAME:-}" && -n "${REGISTRY_PASSWORD:-}" ]]; then echo "==> docker login ${REGISTRY}" echo "${REGISTRY_PASSWORD}" | docker login "${REGISTRY}" -u "${REGISTRY_USERNAME}" --password-stdin fi echo "==> Pull images" docker pull "${BACKEND_IMAGE}" docker pull "${FRONTEND_IMAGE}" docker pull "${UPDATER_IMAGE}" stop_rm() { local name="$1" if docker inspect "${name}" >/dev/null 2>&1; then docker stop "${name}" >/dev/null || true docker rm "${name}" >/dev/null || true fi } echo "==> Recreate mmapp-backend" stop_rm mmapp-backend docker run -d \ --name mmapp-backend \ --restart unless-stopped \ --network mmapp \ --network-alias backend \ -e NODE_ENV=production \ -e PORT=8000 \ -e DATABASE_PATH=/app/data/mikrotik.db \ -e "CORS_ORIGIN=${CORS_ORIGIN}" \ -e "AUTH_REQUIRED=${AUTH_REQUIRED:-true}" \ -e "AUTH_JWT_SECRET=${AUTH_JWT_SECRET:?set AUTH_JWT_SECRET}" \ -e "AUTH_ISSUER=${AUTH_ISSUER:-https://auth.shnt.top}" \ -e "AUTH_PORTAL_URL=${AUTH_PORTAL_URL:-https://auth.shnt.top}" \ -v "${MM_ROOT}/data:/app/data" \ --label mmapp.updater.managed=true \ --label mmapp.updater.target=backend \ --label "mmapp.updater.image=${BACKEND_IMAGE}" \ --health-cmd="node -e \"fetch('http://127.0.0.1:8000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\"" \ --health-interval=30s \ --health-timeout=5s \ --health-retries=3 \ --health-start-period=15s \ --log-driver json-file \ --log-opt max-size=10m \ --log-opt max-file=3 \ "${BACKEND_IMAGE}" echo "==> Recreate mmapp-frontend (mmapp + cdnmanager)" stop_rm mmapp-frontend docker run -d \ --name mmapp-frontend \ --restart unless-stopped \ --network mmapp \ --network-alias frontend \ -e BACKEND_INTERNAL_URL=http://backend:8000 \ --label mmapp.updater.managed=true \ --label mmapp.updater.target=frontend \ --label "mmapp.updater.image=${FRONTEND_IMAGE}" \ --label traefik.enable=true \ --label traefik.docker.network=cdnmanager \ --label "traefik.http.routers.mmapp.rule=Host(\`${MM_DOMAIN}\`)" \ --label traefik.http.routers.mmapp.entrypoints=websecure \ --label traefik.http.routers.mmapp.tls=true \ --label traefik.http.routers.mmapp.tls.certresolver=letsencrypt \ --label traefik.http.services.mmapp.loadbalancer.server.port=3000 \ --health-cmd="node -e \"fetch('http://127.0.0.1:3000/dashboard').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\"" \ --health-interval=30s \ --health-timeout=5s \ --health-retries=3 \ --health-start-period=25s \ --log-driver json-file \ --log-opt max-size=10m \ --log-opt max-file=3 \ "${FRONTEND_IMAGE}" docker network connect cdnmanager mmapp-frontend echo "==> Recreate mmapp-updater" stop_rm mmapp-updater docker run -d \ --name mmapp-updater \ --restart unless-stopped \ --network mmapp \ -e "REGISTRY=${REGISTRY}" \ -e "REGISTRY_USERNAME=${REGISTRY_USERNAME:-}" \ -e "REGISTRY_PASSWORD=${REGISTRY_PASSWORD:-}" \ -e "POLL_INTERVAL_SECONDS=${POLL_INTERVAL_SECONDS:-300}" \ -e "HEALTH_TIMEOUT_SECONDS=${HEALTH_TIMEOUT_SECONDS:-120}" \ -e "STOP_TIMEOUT_SECONDS=${STOP_TIMEOUT_SECONDS:-30}" \ -v /var/run/docker.sock:/var/run/docker.sock \ -v "${MM_ROOT}/state:/state" \ -v "${TARGETS}:/etc/updater/targets.json:ro" \ --log-driver json-file \ --log-opt max-size=10m \ --log-opt max-file=3 \ "${UPDATER_IMAGE}" echo echo "OK. UI: https://${MM_DOMAIN}" echo "DNS: A/AAAA for ${MM_DOMAIN} → this VPS, Cloudflare proxy OFF (DNS only)." echo "Check: curl -fsS https://${MM_DOMAIN}/health" echo " docker ps --filter name=mmapp-" echo " docker network inspect cdnmanager --format '{{range .Containers}}{{.Name}} {{end}}'"