docs(deploy): runbook de despliegue a producción (auth SIWEB + RBAC)
Pasos para que un operador con acceso a prod despliegue con seguridad: env, build del frontend en CI, up del stack, seed + sync de permisos, smoke test y rollback. Incluye feature-flag SESSION_STORE_ENABLED como mitigación rápida. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
162
deploy/RUNBOOK-produccion.md
Normal file
162
deploy/RUNBOOK-produccion.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Runbook — Despliegue a PRODUCCIÓN · CRM Agente de Carga
|
||||
|
||||
> **Quién ejecuta:** un operador con acceso al servidor de producción y a la base de
|
||||
> datos de prod. **Este runbook no lo ejecuta ningún agente automático.**
|
||||
> **Prerrequisito de proceso:** el PR de `feature/crm-workspace-altas-org-usuario`
|
||||
> debe estar **revisado y mergeado** a la rama que corresponda antes de desplegar.
|
||||
>
|
||||
> **Antes de empezar: respaldo.** Toma un backup de la base `crm_core` de prod
|
||||
> (`pg_dump`) y del `.env` actual. Sin respaldo verificado, no continúes.
|
||||
|
||||
Este cambio es grande (auth/RBAC): login 100% vía Workspace/Hub, **sesión local
|
||||
(patrón SIWEB)**, gestión de compañías/usuarios/roles y provisión vía Hub. Léelo
|
||||
completo antes de tocar prod.
|
||||
|
||||
---
|
||||
|
||||
## 0. Alcance del cambio
|
||||
|
||||
- **Auth:** el login deja de hablar directo con Keycloak; todo pasa por el Hub
|
||||
(App Launcher → `/auth/sso?relay=<uuid>` → `POST {HUB_URL}/api/v1/auth/sso-exchange`).
|
||||
- **Sesión local:** cookie de sesión propia (HS256) + token KC guardado en **valkey**
|
||||
por `crm_sid`. Requiere valkey arriba. Se controla con `SESSION_STORE_ENABLED`.
|
||||
- **RBAC/Compañías:** compañías en `a76.company` por tenant; roles por carril
|
||||
(Ventas, Operaciones, Facturación, Consulta); permisos por módulo.
|
||||
|
||||
**Migraciones de esquema:** este set **no** agrega tablas nuevas (usa `core.*`,
|
||||
`a76.company` y valkey). Aun así, **verifica migraciones pendientes** en prod antes
|
||||
de desplegar (paso 5). No corras migraciones a ciegas.
|
||||
|
||||
---
|
||||
|
||||
## 1. Prerrequisitos de infraestructura
|
||||
|
||||
- [ ] DNS de prod (p. ej. `crm.aduanasoft.com`) apuntando al server de prod.
|
||||
- [ ] Docker + Docker Compose en el server.
|
||||
- [ ] Servicios del stack: `backend`, `frontend`, `postgres`, `valkey`, `minio`,
|
||||
`celery_worker`, `celery_beat`.
|
||||
- [ ] **valkey** operativo (lo usan la sesión local y `hub_token`).
|
||||
- [ ] nginx + TLS (certbot) para servir app + API en el **mismo origen**.
|
||||
- [ ] Acceso al **Hub de producción** (no el de testing) y al client/realm correctos.
|
||||
|
||||
---
|
||||
|
||||
## 2. Variables de entorno (`.env` en el server de prod)
|
||||
|
||||
Basado en `deploy/env.testing.example`, pero con **hosts, dominio y secretos de
|
||||
producción**. Nunca subas el `.env` con secretos al repo.
|
||||
|
||||
```env
|
||||
# --- Seguridad ---
|
||||
ENVIRONMENT=production
|
||||
DEV_LOCAL_AUTH=false
|
||||
SECRET_KEY=<openssl rand -hex 32>
|
||||
|
||||
# --- Sesión local (patrón SIWEB) ---
|
||||
SESSION_STORE_ENABLED=true
|
||||
SESSION_IDLE_MINUTES=30
|
||||
SESSION_MAX_HOURS=10
|
||||
VALKEY_URL=redis://valkey:6379/0
|
||||
|
||||
# --- Workspace / Hub de PRODUCCIÓN (mismo Hub que genera el relay) ---
|
||||
WORKSPACE_URL=https://<hub-produccion>
|
||||
HUB_URL=https://<hub-produccion>
|
||||
INTERNAL_HUB_URL=https://<hub-produccion>
|
||||
VITE_HUB_URL=https://<hub-produccion>
|
||||
|
||||
# --- Keycloak (single-realm / single-client) ---
|
||||
KEYCLOAK_URL=https://<keycloak-produccion>/kcauth
|
||||
VITE_KEYCLOAK_URL=https://<keycloak-produccion>/kcauth
|
||||
KEYCLOAK_REALM=master
|
||||
KEYCLOAK_CLIENT_ID=aduanasoft
|
||||
KEYCLOAK_CLIENT_SECRET=<secret del producto provisionado en prod>
|
||||
|
||||
# --- Dominio del CRM (mismo origen app + API vía nginx) ---
|
||||
ORIGIN=https://<dominio-crm-produccion>
|
||||
APP_PUBLIC_URL=https://<dominio-crm-produccion>
|
||||
VITE_API_URL=https://<dominio-crm-produccion>/api/
|
||||
INTERNAL_API_URL=http://backend:8000/api/
|
||||
CORS_ORIGINS=https://<dominio-crm-produccion>
|
||||
|
||||
# --- PostgreSQL ---
|
||||
CORE_DB_HOST=postgres
|
||||
CORE_DB_PORT=5432
|
||||
CORE_DB_NAME=crm_core
|
||||
CORE_DB_USER=<usuario>
|
||||
POSTGRES_APP_PASSWORD=<password fuerte>
|
||||
|
||||
# --- MinIO / S3 ---
|
||||
S3_ENDPOINT_URL=http://minio:9000
|
||||
S3_ACCESS_KEY=<access>
|
||||
S3_SECRET_KEY=<secret>
|
||||
S3_BUCKET=crm
|
||||
S3_REGION=us-east-1
|
||||
S3_USE_SSL=false
|
||||
```
|
||||
|
||||
> **Importante:** `ENVIRONMENT=production` hace que el bootstrap de permisos solo dé
|
||||
> `super_admin` al **primer** usuario (no a todos). Es el comportamiento deseado en prod.
|
||||
|
||||
---
|
||||
|
||||
## 3. Build del frontend — **en CI, no en el server**
|
||||
|
||||
La VM de prod no debe compilar el frontend (el build satura RAM). Compila la imagen
|
||||
en **Jenkins/CI** y publícala al registry, o compílala en una máquina de build:
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml build frontend
|
||||
# push al registry interno si aplica
|
||||
```
|
||||
|
||||
El backend usa la imagen/código directamente (uvicorn sin --reload).
|
||||
|
||||
---
|
||||
|
||||
## 4. Despliegue
|
||||
|
||||
```bash
|
||||
# En el server de prod, en el directorio del proyecto:
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull # si usas registry
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
|
||||
docker compose ps # verifica que todos queden healthy
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Post-despliegue (datos) — **operador humano, con respaldo hecho**
|
||||
|
||||
1. **Verificar migraciones pendientes** (si el proyecto usa Alembic u otro):
|
||||
revisar y aplicar **solo** las que correspondan, con backup previo.
|
||||
2. **Seed base** (compañía por tenant + carriles), equivalente a `seed_crm.py`
|
||||
(`ensure_company` + roles Ventas/Operaciones/Facturación/Consulta).
|
||||
3. **Sync de permisos** (registra el catálogo por módulo):
|
||||
|
||||
```bash
|
||||
docker compose exec backend python -c "from api.v1.modules.core.permissions.service import PermissionService; from core.database import CoreSessionLocal; PermissionService(CoreSessionLocal()).sync_permissions()"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Verificación / smoke test
|
||||
|
||||
```bash
|
||||
curl -fsS https://<dominio-crm-produccion>/api/health && echo OK
|
||||
```
|
||||
|
||||
- [ ] Login vía **App Launcher del Workspace** entra sin bucle.
|
||||
- [ ] El dashboard carga compañías del tenant (switcher con el tenant correcto).
|
||||
- [ ] **Usuarios**: lista carga tras refrescar; alta por invitación crea enlace.
|
||||
- [ ] **Roles y permisos**: catálogo por módulo carga; marcar un permiso lo guarda
|
||||
(toast) y persiste al refrescar.
|
||||
- [ ] Licencia válida (sin bucle 401 / "sesión expirada").
|
||||
|
||||
---
|
||||
|
||||
## 7. Rollback
|
||||
|
||||
1. `docker compose ... up -d` con la **imagen/tag anterior** del frontend/backend.
|
||||
2. Restaurar `.env` anterior si se cambió.
|
||||
3. Restaurar el backup de `crm_core` **solo** si hubo cambios de datos irreversibles.
|
||||
4. `SESSION_STORE_ENABLED=false` revierte al comportamiento previo de sesión sin
|
||||
redeploy de código (feature-flag), como mitigación rápida.
|
||||
Reference in New Issue
Block a user