From c983c744ac90aa8c57c5d9d525021bbae3dbc38a Mon Sep 17 00:00:00 2001 From: Aduanasoft Date: Wed, 15 Jul 2026 17:19:21 -0600 Subject: [PATCH] chore(deploy): nginx + entorno + runbook para testing.crm.aduanasoft.com MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - deploy/nginx/testing.crm.aduanasoft.com.conf: reverse proxy TLS, app (:5173) y API (:8000) en el mismo origen, headers de seguridad, límite 30 MB de subida. - deploy/env.testing.example: plantilla de entorno SIN secretos, con banderas de seguridad (ENVIRONMENT=production, DEV_LOCAL_AUTH=false) y URLs del dominio. - deploy/README.md: runbook (acceso por llave, build de producción, migraciones, nginx+certbot, smoke test). Los secretos y las migraciones los ejecuta el operador. Co-Authored-By: Claude Opus 4.8 (1M context) --- deploy/README.md | 84 ++++++++++++++++++++ deploy/env.testing.example | 47 +++++++++++ deploy/nginx/testing.crm.aduanasoft.com.conf | 81 +++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 deploy/README.md create mode 100644 deploy/env.testing.example create mode 100644 deploy/nginx/testing.crm.aduanasoft.com.conf diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..7d75058 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,84 @@ +# Despliegue — testing.crm.aduanasoft.com (entorno de pruebas) + +Guía para publicar el CRM Agente de Carga en el servidor de pruebas. El estándar +Aduanasoft es desplegar vía **Jenkins (CI/CD)**; este runbook manual es para el +levantamiento inicial o cuando el pipeline aún no está conectado. + +> **Servidor:** `deploy@216.250.125.140:3232` · **DNS:** `testing.crm.aduanasoft.com` → `216.250.125.140` + +## 0. Reglas de seguridad (no negociables) +- `ENVIRONMENT=production` y `DEV_LOCAL_AUTH=false`. En `development` el RBAC + auto-bootstrapea **super_admin** a cualquiera y se activa el login local — inaceptable + en un dominio público. +- Los secretos (DB, S3, `SECRET_KEY`, Keycloak) los captura **el operador en el servidor**, + nunca se versionan ni se comparten por chat. +- Las **migraciones** las ejecuta el operador/CI, no de forma automática. No correr + migraciones contra producción. + +## 1. Acceso por llave (una vez) +Autoriza la llave pública del operador en el servidor (desde una máquina que ya entre): +```bash +ssh -p 3232 deploy@216.250.125.140 \ + "mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" +``` + +## 2. Código y entorno +```bash +ssh -p 3232 deploy@216.250.125.140 +git clone https://git.aduanasoft.com/ADUANASOFT/CRM_AGENTES_CARGA.git +cd CRM_AGENTES_CARGA +git checkout feature/crm-cumplimiento-pdf # o la rama/tag liberado +cp deploy/env.testing.example .env +$EDITOR .env # rellenar TODOS los CHANGE_ME +``` + +## 3. Build de producción (importante) +El `docker-compose.yml` del repo está orientado a desarrollo (vite dev + `--reload`). +Para un sitio público hay que servir la **build** de producción: +- **Frontend:** `npm ci && npm run build` (adapter-node) y arrancar con `node build` + escuchando en `5173`, con `ORIGIN=https://testing.crm.aduanasoft.com`. +- **Backend:** `uvicorn main:app --host 0.0.0.0 --port 8000` **sin** `--reload`. +- Publica los puertos SOLO en loopback (override): + ```yaml + # docker-compose.testing.yml (ejemplo de override) + services: + backend: + ports: ["127.0.0.1:8000:8000"] + command: ["uvicorn","main:app","--host","0.0.0.0","--port","8000"] + frontend: + ports: ["127.0.0.1:5173:5173"] + command: ["node","build"] + ``` + ```bash + docker compose -f docker-compose.yml -f docker-compose.testing.yml up -d --build + ``` + +## 4. Migraciones y datos base +```bash +docker compose exec backend alembic upgrade head +# Catálogo de permisos + roles por carril (Ventas/Operaciones/Facturación/Consulta): +docker compose exec backend python -c "from api.v1.modules.core.permissions.service import PermissionService; from core.database import CoreSessionLocal; PermissionService(CoreSessionLocal()).sync_permissions()" +``` +> En producción NO se usa el auto-bootstrap de dev: asigna los roles a los usuarios +> reales desde el módulo de Roles y permisos. + +## 5. Nginx + TLS +```bash +sudo cp deploy/nginx/testing.crm.aduanasoft.com.conf /etc/nginx/sites-available/ +sudo ln -s /etc/nginx/sites-available/testing.crm.aduanasoft.com.conf /etc/nginx/sites-enabled/ +sudo mkdir -p /var/www/certbot +sudo certbot certonly --webroot -w /var/www/certbot -d testing.crm.aduanasoft.com +sudo nginx -t && sudo systemctl reload nginx +``` + +## 6. Smoke test +```bash +curl -fsS https://testing.crm.aduanasoft.com/api/health && echo OK +# Abre la app y valida login (Keycloak), listar clientes, y un flujo end-to-end. +``` + +## Notas +- App y API van en el **mismo origen** (`/` y `/api/`) para no requerir CORS entre hosts. +- MinIO: si el navegador debe abrir URLs prefirmadas, `S3_ENDPOINT_URL` debe resolver a un + host accesible públicamente (o publicar MinIO detrás de nginx en otro subdominio). Revisar + según la política de red del entorno de pruebas. diff --git a/deploy/env.testing.example b/deploy/env.testing.example new file mode 100644 index 0000000..006d666 --- /dev/null +++ b/deploy/env.testing.example @@ -0,0 +1,47 @@ +# ========================================================================== +# Plantilla de entorno para testing.crm.aduanasoft.com (entorno de PRUEBAS) +# Copia este archivo como `.env` EN EL SERVIDOR y rellena los valores reales. +# NO subas el .env con secretos al repositorio. +# ========================================================================== + +# ---- SEGURIDAD (CRÍTICO) ---- +# Debe ser 'production'. NUNCA 'development' en un dominio público: el RBAC +# hace auto-bootstrap de super_admin al usuario en 'development' (cualquiera +# quedaría como administrador total). +ENVIRONMENT=production +# NUNCA 'true' en público: activa el login local "Entrar como dev" y salta Keycloak. +DEV_LOCAL_AUTH=false +# Genera uno fuerte: openssl rand -hex 32 +SECRET_KEY=CHANGE_ME_openssl_rand_hex_32 + +# ---- Autenticación (Keycloak / Hub) ---- +HUB_URL=https://CHANGE_ME_hub_o_keycloak/ +# (agrega aquí los claims/realm/cliente que use tu integración real) + +# ---- Base de datos (PostgreSQL) ---- +CORE_DB_HOST=postgres +CORE_DB_PORT=5432 +CORE_DB_NAME=crm_core +CORE_DB_USER=CHANGE_ME +CORE_DB_PASSWORD=CHANGE_ME + +# ---- Almacenamiento de objetos (MinIO / S3) ---- +S3_ENDPOINT_URL=http://minio:9000 +S3_ACCESS_KEY=CHANGE_ME +S3_SECRET_KEY=CHANGE_ME +S3_BUCKET=crm +S3_REGION=us-east-1 +S3_USE_SSL=false + +# ---- URLs públicas / CORS (mismo origen que nginx) ---- +APP_PUBLIC_URL=https://testing.crm.aduanasoft.com +CORS_ORIGINS=https://testing.crm.aduanasoft.com + +# ---- Frontend (SvelteKit adapter-node) ---- +# API en el mismo origen a través de nginx (/api/): +VITE_API_URL=https://testing.crm.aduanasoft.com/api/ +# Llamadas servidor->servidor dentro de la red de Docker: +BACKEND_URL=http://backend:8000 +INTERNAL_API_URL=http://backend:8000/api/ +# adapter-node valida el Origin contra ORIGIN; debe ser la URL pública: +ORIGIN=https://testing.crm.aduanasoft.com diff --git a/deploy/nginx/testing.crm.aduanasoft.com.conf b/deploy/nginx/testing.crm.aduanasoft.com.conf new file mode 100644 index 0000000..af625f2 --- /dev/null +++ b/deploy/nginx/testing.crm.aduanasoft.com.conf @@ -0,0 +1,81 @@ +# nginx — testing.crm.aduanasoft.com +# CRM Agente de Carga (entorno de PRUEBAS). App (SvelteKit adapter-node :5173) + API (FastAPI :8000) +# servidas en el MISMO origen para evitar CORS. TLS con Let's Encrypt (certbot). +# +# Instalar en el servidor: +# sudo cp testing.crm.aduanasoft.com.conf /etc/nginx/sites-available/ +# sudo ln -s /etc/nginx/sites-available/testing.crm.aduanasoft.com.conf /etc/nginx/sites-enabled/ +# sudo certbot certonly --webroot -w /var/www/certbot -d testing.crm.aduanasoft.com +# sudo nginx -t && sudo systemctl reload nginx +# +# IMPORTANTE (seguridad): publica los puertos de la app SOLO en loopback del servidor +# (127.0.0.1:5173 y 127.0.0.1:8000) para que nginx sea el único acceso público. + +# ---- HTTP: reto ACME + redirección a HTTPS ---- +server { + listen 80; + listen [::]:80; + server_name testing.crm.aduanasoft.com; + + # Renovación de certificados (webroot) + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$host$request_uri; + } +} + +# ---- HTTPS ---- +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name testing.crm.aduanasoft.com; + + ssl_certificate /etc/letsencrypt/live/testing.crm.aduanasoft.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/testing.crm.aduanasoft.com/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 1d; + + # Encabezados de seguridad + add_header Strict-Transport-Security "max-age=63072000" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Referrer-Policy "strict-origin-when-cross-origin" always; + + # Subida de documentos (máx. 25 MB en la app) + margen + client_max_body_size 30m; + + gzip on; + gzip_types text/plain text/css application/javascript application/json image/svg+xml; + gzip_min_length 1024; + + # ---- API (FastAPI) — mismo origen: /api/... -> backend :8000 (conserva el path) ---- + location /api/ { + proxy_pass http://127.0.0.1:8000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_read_timeout 120s; + } + + # ---- App (SvelteKit adapter-node) — todo lo demás -> frontend :5173 ---- + location / { + proxy_pass http://127.0.0.1:5173; + proxy_http_version 1.1; + # WebSocket / upgrade (SSR streaming y HMR si corriera en modo dev) + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_read_timeout 120s; + } +}