# Guía de Migración: Nginx Proxy Manager → Traefik **Fecha**: 5 de Octubre, 2025 **Servidor**: nucleo002 **Configuraciones Extraídas**: 18 Proxy Hosts --- ## 📋 Índice 1. [Resumen de Configuraciones NPM](#resumen-de-configuraciones-npm) 2. [Configuración Base de Traefik](#configuración-base-de-traefik) 3. [Traducción de Cada Proxy Host](#traducción-de-cada-proxy-host) 4. [Middlewares Comunes](#middlewares-comunes) 5. [Certificados SSL](#certificados-ssl) 6. [Docker Compose Completo](#docker-compose-completo) 7. [Scripts de Migración](#scripts-de-migración) --- ## 1. Resumen de Configuraciones NPM ### Proxy Hosts Detectados | ID | Dominio | Backend | Puerto | SSL | Características Especiales | |----|---------|---------|--------|-----|----------------------------| | 2 | portainer.interno.com | portainer | 9443 | Custom | - | | 3 | gitea.interno.com | gitea | 3000 | Custom | - | | 4 | geoids.interno.com | geo-ids | 443 | Custom | **Authentik SSO** | | 5 | amigos.nucleoriofrio.com | amigos-app | 3001 | Let's Encrypt | **Authentik SSO** (parcial) | | 7 | whatsappbot.interno.com | whatsapp-router | 3001 | Custom | - | | 8 | conversation-layer.interno.com | - | - | Custom | - | | 9 | Nginx.interno.com | npm | 81 | Custom | Admin Panel | | 10 | planilla.interno.com | planilla-ui | 80 | Custom | **Multi-location** (/, /api, /mcp, /events) | | 11 | nucleobot.interno.com | whatsapp-router | 3001 | Custom | - | | 16 | nucleoriofrio.com | z590.interno.com | 3000 | Let's Encrypt | **Custom locations** (/api/chat/stream, /api/chat/) | | 17 | musica.interno.com | z590.interno.com | 3000 | Custom | **HTTP/2**, **HMR**, Multiple locations | | 18 | musica.nucleoriofrio.com | repodructor | 3000 | Let's Encrypt | Multiple locations, Cache headers | | 19 | snatchgame.interno.com | z590.interno.com | 3004 | Custom | **7 locations** (/ws, /colyseus, /api/*) | | 20 | snatchgame.nucleoriofrio.com | snatchgame | 2567 | Let's Encrypt | **6 locations**, Asset caching | | 21 | duplicati.interno.com | duplicati | - | Custom | - | | 22 | internet.nucleoriofrio.com | z590.interno.com | 80 | Let's Encrypt | - | | 23 | wifi.nucleoriofrio.com | radiusnucleo-node-1 | - | Let's Encrypt | - | ### Características Globales - **HSTS**: Habilitado en todos (`max-age=63072000; preload`) - **Force SSL**: Mayoría de hosts - **Block Exploits**: Todos - **WebSocket Support**: Habilitado donde se necesita - **HTTP/2**: Solo musica.interno.com --- ## 2. Configuración Base de Traefik ### 2.1 Docker Compose - Traefik ```yaml version: '3.8' services: traefik: image: traefik:v3.1 container_name: traefik restart: unless-stopped security_opt: - no-new-privileges:true networks: - principal ports: - "80:80" - "443:443" - "8080:8080" # Dashboard environment: - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} # Si usas Cloudflare - CF_ZONE_API_TOKEN=${CF_ZONE_API_TOKEN} volumes: - /etc/localtime:/etc/localtime:ro - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik.yml:/traefik.yml:ro - ./config/:/config/:ro - ./acme.json:/acme.json - ./certificates/:/certificates/:ro labels: - "traefik.enable=true" # Dashboard - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.interno.com`)" - "traefik.http.routers.traefik-dashboard.entrypoints=https" - "traefik.http.routers.traefik-dashboard.tls=true" - "traefik.http.routers.traefik-dashboard.service=api@internal" - "traefik.http.routers.traefik-dashboard.middlewares=auth@file" networks: principal: external: true ``` ### 2.2 traefik.yml (Static Configuration) ```yaml api: dashboard: true debug: false entryPoints: http: address: ":80" http: redirections: entryPoint: to: https scheme: https permanent: true https: address: ":443" http: tls: options: default certResolver: letsencrypt domains: - main: "*.interno.com" - main: "*.nucleoriofrio.com" http2: maxConcurrentStreams: 250 metrics: address: ":8082" providers: docker: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false network: principal watch: true file: directory: /config watch: true certificatesResolvers: letsencrypt: acme: email: admin@nucleoriofrio.com storage: /acme.json httpChallenge: entryPoint: http # O para wildcard con DNS challenge: # dnsChallenge: # provider: cloudflare # delayBeforeCheck: 0 custom: acme: tlsChallenge: {} storage: /acme.json # Global HTTP settings serversTransport: insecureSkipVerify: true # Solo si usas certificados autofirmados en backend # Logging log: level: INFO filePath: /logs/traefik.log format: json accessLog: filePath: /logs/access.log format: json bufferingSize: 100 # Metrics (opcional) metrics: prometheus: addEntryPointsLabels: true addServicesLabels: true entryPoint: metrics ``` --- ## 3. Traducción de Cada Proxy Host ### 3.1 Portainer (portainer.interno.com) **NPM Original**: - Backend: `portainer:9443` (HTTPS) - SSL: Custom certificate - Force SSL **Traefik Labels**: ```yaml # En docker-compose del stack portainer services: portainer: # ... resto de config labels: - "traefik.enable=true" - "traefik.http.routers.portainer.rule=Host(`portainer.interno.com`)" - "traefik.http.routers.portainer.entrypoints=https" - "traefik.http.routers.portainer.tls=true" - "traefik.http.routers.portainer.tls.certresolver=custom" - "traefik.http.services.portainer.loadbalancer.server.port=9443" - "traefik.http.services.portainer.loadbalancer.server.scheme=https" # Middlewares - "traefik.http.routers.portainer.middlewares=hsts-headers,block-exploits" networks: - principal ``` --- ### 3.2 Gitea (gitea.interno.com) **NPM Original**: - Backend: `gitea:3000` (HTTP) - SSL: Custom - No Force SSL **Traefik Labels**: ```yaml services: gitea: labels: - "traefik.enable=true" - "traefik.http.routers.gitea.rule=Host(`gitea.interno.com`)" - "traefik.http.routers.gitea.entrypoints=https" - "traefik.http.routers.gitea.tls=true" - "traefik.http.routers.gitea.tls.certresolver=custom" - "traefik.http.services.gitea.loadbalancer.server.port=3000" - "traefik.http.routers.gitea.middlewares=hsts-headers,block-exploits" networks: - principal ``` --- ### 3.3 GeoIDs con Authentik SSO (geoids.interno.com) **NPM Original**: - Backend: `geo-ids:443` (HTTPS) - Auth: Authentik via `auth_request` - WebSocket support - Custom buffers **Traefik Labels + Middleware File**: ```yaml services: geo-ids: labels: - "traefik.enable=true" - "traefik.http.routers.geoids.rule=Host(`geoids.interno.com`)" - "traefik.http.routers.geoids.entrypoints=https" - "traefik.http.routers.geoids.tls=true" - "traefik.http.routers.geoids.tls.certresolver=custom" - "traefik.http.services.geoids.loadbalancer.server.port=443" - "traefik.http.services.geoids.loadbalancer.server.scheme=https" # Middlewares (orden importa!) - "traefik.http.routers.geoids.middlewares=authentik-geoids@file,hsts-headers,websocket-headers" networks: - principal ``` **Middleware File** (`/config/middlewares-authentik.yml`): ```yaml http: middlewares: authentik-geoids: forwardAuth: address: "http://authentik-server:9000/outpost.goauthentik.io/auth/nginx" trustForwardHeader: true authResponseHeaders: - "X-authentik-username" - "X-authentik-groups" - "X-authentik-email" - "X-authentik-name" - "X-authentik-uid" - "Set-Cookie" ``` --- ### 3.4 Planilla Multi-Location (planilla.interno.com) **NPM Original**: - `/` → `planilla-ui:80` - `/api` → `planilla-api:4000` - `/mcp` → `planilla-mcp:5000` - `/events` → `planilla-api:4000` - WebSocket en todos **Traefik Dynamic Config** (`/config/planilla.yml`): ```yaml http: routers: planilla-ui: rule: "Host(`planilla.interno.com`)" entryPoints: - https service: planilla-ui-svc middlewares: - hsts-headers - websocket-headers tls: certResolver: custom priority: 1 planilla-api: rule: "Host(`planilla.interno.com`) && PathPrefix(`/api`)" entryPoints: - https service: planilla-api-svc middlewares: - hsts-headers - websocket-headers tls: certResolver: custom priority: 10 planilla-mcp: rule: "Host(`planilla.interno.com`) && PathPrefix(`/mcp`)" entryPoints: - https service: planilla-mcp-svc middlewares: - hsts-headers - websocket-headers tls: certResolver: custom priority: 10 planilla-events: rule: "Host(`planilla.interno.com`) && PathPrefix(`/events`)" entryPoints: - https service: planilla-api-svc # Mismo que /api middlewares: - hsts-headers - websocket-headers tls: certResolver: custom priority: 10 services: planilla-ui-svc: loadBalancer: servers: - url: "http://planilla-ui:80" planilla-api-svc: loadBalancer: servers: - url: "http://planilla-api:4000" planilla-mcp-svc: loadBalancer: servers: - url: "http://planilla-mcp:5000" ``` --- ### 3.5 Música con HMR (musica.interno.com) **NPM Original**: - HTTP/2: ON - Multiple locations: `/_nuxt/`, `/__nuxt_devtools__/`, `/api/` - WebSocket para HMR - Proxy buffering OFF - Large client body **Traefik Config** (`/config/musica.yml`): ```yaml http: routers: musica-nuxt: rule: "Host(`musica.interno.com`) && PathPrefix(`/_nuxt/`)" entryPoints: - https service: musica-z590-svc middlewares: - websocket-headers - no-buffering tls: certResolver: custom priority: 20 musica-devtools: rule: "Host(`musica.interno.com`) && PathPrefix(`/__nuxt_devtools__/`)" entryPoints: - https service: musica-z590-svc middlewares: - websocket-headers - no-buffering tls: certResolver: custom priority: 20 musica-api: rule: "Host(`musica.interno.com`) && PathPrefix(`/api/`)" entryPoints: - https service: musica-z590-svc middlewares: - no-buffering - large-timeout tls: certResolver: custom priority: 15 musica-root: rule: "Host(`musica.interno.com`)" entryPoints: - https service: musica-z590-svc middlewares: - hsts-headers tls: certResolver: custom priority: 1 services: musica-z590-svc: loadBalancer: servers: - url: "http://192.168.87.135:3000" # z590.interno.com passHostHeader: true middlewares: no-buffering: buffering: maxRequestBodyBytes: 104857600 # 100MB memRequestBodyBytes: 104857600 maxResponseBodyBytes: 0 # Sin límite memResponseBodyBytes: 0 retryExpression: "IsNetworkError() && Attempts() < 2" large-timeout: buffering: maxRequestBodyBytes: 104857600 # Los timeouts se configuran en service o entrypoint ``` --- ### 3.6 SnatchGame Complejo (snatchgame.interno.com) **NPM Original**: - 7 locations diferentes - WebSocket en todos - Asset caching **Traefik Config** (`/config/snatchgame.yml`): ```yaml http: routers: snatch-ws: rule: "Host(`snatchgame.interno.com`) && PathPrefix(`/ws`)" entryPoints: - https service: snatch-z590-svc middlewares: - websocket-headers - asset-cache tls: certResolver: custom priority: 30 snatch-colyseus: rule: "Host(`snatchgame.interno.com`) && PathPrefix(`/colyseus`)" entryPoints: - https service: snatch-z590-svc middlewares: - websocket-headers - asset-cache tls: certResolver: custom priority: 25 snatch-api-stream: rule: "Host(`snatchgame.interno.com`) && Path(`/api/dashboard-stream`)" entryPoints: - https service: snatch-z590-svc middlewares: - websocket-headers tls: certResolver: custom priority: 40 snatch-api-uuids-stream: rule: "Host(`snatchgame.interno.com`) && Path(`/api/uuids-stream`)" entryPoints: - https service: snatch-z590-svc middlewares: - websocket-headers tls: certResolver: custom priority: 40 snatch-api-admin: rule: "Host(`snatchgame.interno.com`) && PathPrefix(`/api/admin/`)" entryPoints: - https service: snatch-z590-svc middlewares: - asset-cache tls: certResolver: custom priority: 35 snatch-api-wildcard: rule: "Host(`snatchgame.interno.com`) && PathRegexp(`^/api/.*`)" entryPoints: - https service: snatch-z590-svc middlewares: - asset-cache tls: certResolver: custom priority: 20 snatch-root: rule: "Host(`snatchgame.interno.com`)" entryPoints: - https service: snatch-z590-svc middlewares: - hsts-headers - asset-cache tls: certResolver: custom priority: 1 services: snatch-z590-svc: loadBalancer: servers: - url: "http://z590.interno.com:3004" ``` --- ### 3.7 Nucleoriofrio.com con API Chat **NPM Original**: - `/api/chat/stream` → Streaming con buffering OFF - `/api/chat/` → API normal - CORS headers - Large timeouts **Traefik Config** (`/config/nucleoriofrio.yml`): ```yaml http: routers: nucleo-chat-stream: rule: "Host(`nucleoriofrio.com`) && Path(`/api/chat/stream`)" entryPoints: - https service: nucleo-z590-alt-svc middlewares: - no-buffering - cors-permissive - large-timeout tls: certResolver: letsencrypt priority: 50 nucleo-chat-api: rule: "Host(`nucleoriofrio.com`) && PathPrefix(`/api/chat/`)" entryPoints: - https service: nucleo-z590-alt-svc middlewares: - cors-permissive tls: certResolver: letsencrypt priority: 40 nucleo-root: rule: "Host(`nucleoriofrio.com`)" entryPoints: - https service: nucleo-z590-svc middlewares: - hsts-headers tls: certResolver: letsencrypt priority: 1 services: nucleo-z590-svc: loadBalancer: servers: - url: "https://z590.interno.com:3000" nucleo-z590-alt-svc: loadBalancer: servers: - url: "https://192.168.87.135:3000" middlewares: cors-permissive: headers: accessControlAllowOriginList: - "*" accessControlAllowMethods: - "GET" - "POST" - "OPTIONS" accessControlAllowHeaders: - "Accept" - "Authorization" - "Cache-Control" - "Content-Type" - "DNT" - "If-Modified-Since" - "Keep-Alive" - "Origin" - "User-Agent" - "X-Requested-With" accessControlMaxAge: 100 addVaryHeader: true ``` --- ### 3.8 Música Pública (musica.nucleoriofrio.com) **NPM Original**: - Multiple locations con diferentes propósitos - Caching de assets - Security headers - Streaming optimizations **Traefik Config** (`/config/musica-public.yml`): ```yaml http: routers: musica-pub-nuxt: rule: "Host(`musica.nucleoriofrio.com`) && PathPrefix(`/_nuxt/`)" entryPoints: - https service: repodructor-svc middlewares: - asset-cache-long tls: certResolver: letsencrypt priority: 30 musica-pub-music: rule: "Host(`musica.nucleoriofrio.com`) && PathPrefix(`/music/`)" entryPoints: - https service: repodructor-svc middlewares: - no-buffering - music-streaming - music-cache tls: certResolver: letsencrypt priority: 25 musica-pub-api: rule: "Host(`musica.nucleoriofrio.com`) && PathPrefix(`/api/`)" entryPoints: - https service: repodructor-svc middlewares: - api-timeout tls: certResolver: letsencrypt priority: 20 musica-pub-root: rule: "Host(`musica.nucleoriofrio.com`)" entryPoints: - https service: repodructor-svc middlewares: - hsts-headers - security-headers tls: certResolver: letsencrypt priority: 1 services: repodructor-svc: loadBalancer: servers: - url: "http://repodructor:3000" middlewares: asset-cache-long: headers: customResponseHeaders: Cache-Control: "public, max-age=31536000, immutable" music-cache: headers: customResponseHeaders: Cache-Control: "public, max-age=3600" music-streaming: buffering: maxRequestBodyBytes: 0 # Sin límite memRequestBodyBytes: 0 maxResponseBodyBytes: 0 memResponseBodyBytes: 0 api-timeout: buffering: maxRequestBodyBytes: 10485760 # 10MB security-headers: headers: customResponseHeaders: X-Frame-Options: "SAMEORIGIN" X-Content-Type-Options: "nosniff" X-XSS-Protection: "1; mode=block" ``` --- ## 4. Middlewares Comunes ### 4.1 Archivo de Middlewares Globales (`/config/middlewares.yml`) ```yaml http: middlewares: # HSTS Headers hsts-headers: headers: stsSeconds: 63072000 stsIncludeSubdomains: true stsPreload: true forceSTSHeader: true # WebSocket Support websocket-headers: headers: customRequestHeaders: X-Forwarded-Proto: "https" customResponseHeaders: Upgrade: "$http_upgrade" Connection: "$http_connection" # Block Common Exploits block-exploits: headers: browserXssFilter: true contentTypeNosniff: true frameDeny: true customFrameOptionsValue: "SAMEORIGIN" ipWhiteList: sourceRange: - "127.0.0.1/32" - "192.168.0.0/16" - "172.16.0.0/12" - "10.0.0.0/8" # Asset Caching asset-cache: headers: customResponseHeaders: Cache-Control: "public, max-age=86400" # Security Headers Strict security-strict: headers: sslRedirect: true stsSeconds: 31536000 stsIncludeSubdomains: true stsPreload: true forceSTSHeader: true frameDeny: true contentTypeNosniff: true browserXssFilter: true referrerPolicy: "strict-origin-when-cross-origin" permissionsPolicy: "camera=(), microphone=(), geolocation=()" customResponseHeaders: X-Robots-Tag: "none,noarchive,nosnippet,notranslate,noimageindex" # Rate Limiting rate-limit: rateLimit: average: 100 burst: 200 period: 1m # Basic Auth (ejemplo) auth-basic: basicAuth: users: - "admin:$apr1$..." # htpasswd hash removeHeader: true # IP Whitelist (ejemplo para admin panels) admin-whitelist: ipWhiteList: sourceRange: - "192.168.87.0/24" - "172.19.0.0/16" # Compress compress: compress: {} # Strip Prefix (útil para algunos backends) strip-api-prefix: stripPrefix: prefixes: - "/api" forceSlash: false # Redirect Scheme (HTTP → HTTPS) redirect-https: redirectScheme: scheme: https permanent: true # Custom Headers for Backend backend-headers: headers: customRequestHeaders: X-Forwarded-Proto: "https" X-Forwarded-Scheme: "https" X-Real-IP: "" # Traefik lo rellena automáticamente X-Forwarded-For: "" ``` --- ## 5. Certificados SSL ### 5.1 Certificado Custom (*.interno.com) **Opción A: Usar el certificado autofirmado existente** ```yaml # En /config/tls.yml tls: stores: default: defaultCertificate: certFile: /certificates/interno.com/fullchain.pem keyFile: /certificates/interno.com/privkey.pem certificates: - certFile: /certificates/interno.com/fullchain.pem keyFile: /certificates/interno.com/privkey.pem stores: - default ``` Copiar certificados: ```bash mkdir -p /srv/traefik/certificates/interno.com cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/fullchain.pem \ /srv/traefik/certificates/interno.com/ cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/privkey.pem \ /srv/traefik/certificates/interno.com/ ``` ### 5.2 Let's Encrypt (*.nucleoriofrio.com) Ya configurado en `traefik.yml`: ```yaml certificatesResolvers: letsencrypt: acme: email: admin@nucleoriofrio.com storage: /acme.json httpChallenge: entryPoint: http ``` Para wildcard con DNS Challenge (Cloudflare): ```yaml certificatesResolvers: letsencrypt: acme: email: admin@nucleoriofrio.com storage: /acme.json dnsChallenge: provider: cloudflare delayBeforeCheck: 10 resolvers: - "1.1.1.1:53" - "1.0.0.1:53" ``` Variables de entorno necesarias: ```bash CF_DNS_API_TOKEN=your_cloudflare_token CF_ZONE_API_TOKEN=your_zone_token ``` --- ## 6. Docker Compose Completo ### 6.1 Stack Traefik Principal ```yaml version: '3.8' services: traefik: image: traefik:v3.1 container_name: traefik restart: unless-stopped security_opt: - no-new-privileges:true networks: - principal ports: - "80:80" - "443:443" - "8080:8080" # Dashboard (cambiar puerto si hay conflicto) environment: - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN} - CF_ZONE_API_TOKEN=${CF_ZONE_API_TOKEN} - TZ=America/Argentina/Buenos_Aires volumes: - /etc/localtime:/etc/localtime:ro - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik.yml:/traefik.yml:ro - ./config/:/config/:ro - ./acme.json:/acme.json - ./certificates/:/certificates/:ro - ./logs/:/logs/ labels: - "traefik.enable=true" # Dashboard - "traefik.http.routers.dashboard.rule=Host(`traefik.interno.com`)" - "traefik.http.routers.dashboard.entrypoints=https" - "traefik.http.routers.dashboard.tls=true" - "traefik.http.routers.dashboard.tls.certresolver=custom" - "traefik.http.routers.dashboard.service=api@internal" - "traefik.http.routers.dashboard.middlewares=admin-whitelist" networks: principal: external: true ``` ### 6.2 Estructura de Directorios ``` /srv/traefik/ ├── traefik.yml # Config estática ├── acme.json # Certificados LE (chmod 600) ├── docker-compose.yml ├── .env ├── config/ │ ├── middlewares.yml # Middlewares globales │ ├── middlewares-authentik.yml │ ├── tls.yml # Certificados custom │ ├── planilla.yml # Config dinámica planilla │ ├── musica.yml │ ├── snatchgame.yml │ ├── nucleoriofrio.yml │ └── musica-public.yml ├── certificates/ │ └── interno.com/ │ ├── fullchain.pem │ └── privkey.pem └── logs/ ├── traefik.log └── access.log ``` ### 6.3 Preparación ```bash # Crear estructura mkdir -p /srv/traefik/{config,certificates/interno.com,logs} # Crear acme.json touch /srv/traefik/acme.json chmod 600 /srv/traefik/acme.json # Copiar certificados cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/fullchain.pem \ /srv/traefik/certificates/interno.com/ cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/privkey.pem \ /srv/traefik/certificates/interno.com/ # Crear archivos de config touch /srv/traefik/traefik.yml touch /srv/traefik/config/{middlewares,tls,planilla,musica,snatchgame}.yml ``` --- ## 7. Scripts de Migración ### 7.1 Script de Pre-Migración ```bash #!/bin/bash # pre-migration.sh set -e echo "🔍 Pre-Migration Checklist para Traefik" # Backup de NPM echo "📦 Backing up NPM configs..." mkdir -p /srv/backups/npm-$(date +%Y%m%d) cp -r /srv/nginx-proxy-manager/data/nginx /srv/backups/npm-$(date +%Y%m%d)/ cp -r /srv/nginx-proxy-manager/data/custom_ssl /srv/backups/npm-$(date +%Y%m%d)/ # Verificar red principal echo "🌐 Verificando red principal..." docker network inspect principal > /dev/null 2>&1 || { echo "❌ Red 'principal' no existe" exit 1 } # Verificar contenedores backend echo "🔍 Verificando backends..." backends=( "portainer" "gitea" "geo-ids" "planilla-ui" "planilla-api" "planilla-mcp" "whatsapp-router" "repodructor" "amigos-app" "duplicati" "radiusnucleo-node-1" ) for backend in "${backends[@]}"; do docker ps --filter "name=$backend" --format "{{.Names}}" | grep -q "$backend" || { echo "⚠️ Backend '$backend' no encontrado o detenido" } done # Crear estructura Traefik echo "📁 Creando estructura de directorios..." mkdir -p /srv/traefik/{config,certificates/interno.com,logs} touch /srv/traefik/acme.json chmod 600 /srv/traefik/acme.json # Copiar certificados echo "🔐 Copiando certificados SSL..." cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/fullchain.pem \ /srv/traefik/certificates/interno.com/ cp /srv/nginx-proxy-manager/data/custom_ssl/npm-1/privkey.pem \ /srv/traefik/certificates/interno.com/ echo "✅ Pre-migration completa" echo "" echo "Próximos pasos:" echo "1. Crear archivos de configuración de Traefik" echo "2. Actualizar labels en docker-compose de cada stack" echo "3. Ejecutar migration.sh" ``` ### 7.2 Script de Migración ```bash #!/bin/bash # migration.sh set -e echo "🚀 Iniciando migración NPM → Traefik" # Detener NPM echo "🛑 Deteniendo NPM..." cd /srv/nginx-proxy-manager docker-compose down # Levantar Traefik echo "🚀 Levantando Traefik..." cd /srv/traefik docker-compose up -d # Esperar a que Traefik esté listo echo "⏳ Esperando a que Traefik esté listo..." sleep 10 # Verificar Traefik echo "✅ Verificando Traefik..." docker logs traefik --tail 50 # Verificar dashboard echo "🌐 Dashboard disponible en: https://traefik.interno.com" # Restart de cada stack para aplicar labels echo "🔄 Reiniciando stacks con nuevas labels..." stacks=( "/srv/gitea" "/srv/planilla" "/srv/conversation-layer" # ... más stacks ) for stack in "${stacks[@]}"; do if [ -d "$stack" ]; then echo " Reiniciando $stack..." cd "$stack" docker-compose up -d --force-recreate fi done echo "✅ Migración completa!" echo "" echo "Verificar:" echo "1. Dashboard Traefik: https://traefik.interno.com" echo "2. Logs: docker logs traefik -f" echo "3. Probar cada dominio manualmente" ``` ### 7.3 Script de Rollback ```bash #!/bin/bash # rollback.sh set -e echo "⏪ Rollback a NPM" # Detener Traefik echo "🛑 Deteniendo Traefik..." cd /srv/traefik docker-compose down # Levantar NPM echo "🚀 Levantando NPM..." cd /srv/nginx-proxy-manager docker-compose up -d echo "✅ Rollback completo" echo "NPM disponible en: http://Nginx.interno.com:81" ``` ### 7.4 Script de Verificación ```bash #!/bin/bash # verify-migration.sh domains=( "portainer.interno.com" "gitea.interno.com" "geoids.interno.com" "planilla.interno.com" "musica.interno.com" "nucleoriofrio.com" "snatchgame.interno.com" "amigos.nucleoriofrio.com" ) echo "🔍 Verificando dominios..." for domain in "${domains[@]}"; do echo -n "Testing $domain... " if curl -skI "https://$domain" | head -1 | grep -q "200\|301\|302"; then echo "✅ OK" else echo "❌ FAIL" fi done echo "" echo "Verificar manualmente:" echo "1. Autenticación Authentik en geoids.interno.com" echo "2. WebSocket en musica.interno.com" echo "3. Multi-location en planilla.interno.com" echo "4. API streaming en nucleoriofrio.com/api/chat/stream" ``` --- ## 8. Tabla de Mapeo Completa ### NPM → Traefik Equivalencias | Característica NPM | Equivalente Traefik | |-------------------|---------------------| | `proxy_pass` | `service.loadBalancer.server.url` | | `server_name` | `router.rule=Host(...)` | | `location /path` | `router.rule=PathPrefix(...)` + `priority` | | `proxy_set_header` | `middleware.headers.customRequestHeaders` | | `add_header` | `middleware.headers.customResponseHeaders` | | `auth_request` | `middleware.forwardAuth` | | `ssl_certificate` | `tls.certificates` o `tls.certResolver` | | `include force-ssl.conf` | `entrypoint.http.redirections` | | `proxy_buffering off` | `middleware.buffering` | | `client_max_body_size` | `middleware.buffering.maxRequestBodyBytes` | | `proxy_read_timeout` | `service.loadBalancer.responseForwarding.timeout` | | `upstream` con múltiples IPs | `service.loadBalancer.servers` (array) | | `map` para variables | Middlewares condicionales o dynamic config | | `if` statements | No soportado directamente, usar priorities | | `limit_req` | `middleware.rateLimit` | | `access_log` | `accessLog` en traefik.yml | | `error_log` | `log` en traefik.yml | --- ## 9. Checklist de Migración ### Antes de Migrar - [ ] Backup completo de NPM configs - [ ] Backup de base de datos de NPM - [ ] Copiar certificados SSL - [ ] Verificar que todos los backends están en red `principal` - [ ] Documentar configuraciones custom ### Durante la Migración - [ ] Crear estructura de directorios de Traefik - [ ] Configurar `traefik.yml` - [ ] Crear middlewares comunes - [ ] Crear configs dinámicas por servicio - [ ] Actualizar docker-compose de cada stack con labels - [ ] Detener NPM - [ ] Levantar Traefik - [ ] Verificar dashboard de Traefik ### Después de Migrar - [ ] Probar cada dominio manualmente - [ ] Verificar certificados SSL - [ ] Verificar autenticación Authentik - [ ] Verificar WebSockets - [ ] Verificar multi-location routing - [ ] Verificar streaming endpoints - [ ] Monitorear logs de Traefik - [ ] Configurar alertas (opcional) - [ ] Actualizar documentación ### En Caso de Problemas - [ ] Rollback a NPM - [ ] Revisar logs de Traefik - [ ] Verificar conectividad de red - [ ] Verificar resolución DNS interna - [ ] Verificar permisos de certificados