# Arquitectura de Despliegue - Workspace Multi-Proyecto ## Resumen Ejecutivo | Aspecto | Valor | |---------|-------| | **Servidor Principal** | 72.60.226.4 | | **Servidor Gamilit** | 74.208.126.102 | | **Reverse Proxy** | Nginx | | **Registry Git** | Gitea (72.60.226.4:3000) / GitHub (gamilit) | ### Métodos de Despliegue por Servidor | Servidor | Proyectos | Método CI/CD | Process Manager | |----------|-----------|--------------|-----------------| | **72.60.226.4** | trading-platform, erp-suite, pmc, betting, inmobiliaria | Jenkins + Docker | Docker Compose | | **74.208.126.102** | gamilit | **Manual (git pull + PM2)** | PM2 Cluster | > **IMPORTANTE:** Gamilit NO usa Jenkins. Se despliega manualmente con PM2. --- ## 1. Arquitectura de Servidores ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ SERVIDOR PRINCIPAL │ │ 72.60.226.4 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────┐ ┌─────────────────────────────────────────────────┐ │ │ │ NGINX │ │ DOCKER CONTAINERS │ │ │ │ (Port 80) │───>│ │ │ │ │ (Port 443) │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ └─────────────┘ │ │ trading- │ │ erp-suite │ │ │ │ │ │ │ platform │ │ (verticales)│ │ │ │ │ │ │ FE:3080 │ │ FE:3010-3070│ │ │ │ │ │ │ BE:3081 │ │ BE:3011-3071│ │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ │ │ │ pmc │ │ betting/ │ │ │ │ │ │ │ FE:3110 │ │ inmobiliaria│ │ │ │ │ │ │ BE:3111 │ │ (reservado) │ │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ │ ┌─────────────┐ ┌─────────────────────────────────────────────────┐ │ │ │ JENKINS │ │ INFRAESTRUCTURA │ │ │ │ (Port 8080)│ │ PostgreSQL: 5432 │ Redis: 6379 │ │ │ └─────────────┘ │ Gitea: 3000 │ Registry: 5000 │ │ │ └─────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────────────────────────────┐ │ SERVIDOR GAMILIT │ │ 74.208.126.102 │ ├─────────────────────────────────────────────────────────────────────────────┤ │ ┌─────────────┐ ┌─────────────────────────────────────────────────┐ │ │ │ NGINX │ │ PM2 CLUSTER │ │ │ │ (Port 80) │───>│ Backend: 2 instances (Port 3006) │ │ │ │ (Port 443) │ │ Frontend: 1 instance (Port 3005) │ │ │ └─────────────┘ └─────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ PostgreSQL: 5432 (gamilit_platform) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` --- ## 2. Estructura de Repositorios ### 2.1 Repositorios Independientes por Proyecto | Proyecto | Repositorio | Servidor Deploy | |----------|-------------|-----------------| | **gamilit** | `github.com/rckrdmrd/gamilit-workspace.git` | 74.208.126.102 | | **trading-platform** | `72.60.226.4:3000/rckrdmrd/trading-platform.git` | 72.60.226.4 | | **erp-suite** | `72.60.226.4:3000/rckrdmrd/erp-suite.git` | 72.60.226.4 | | **platform-marketing-content** | `72.60.226.4:3000/rckrdmrd/pmc.git` | 72.60.226.4 | | **betting-analytics** | `72.60.226.4:3000/rckrdmrd/betting-analytics.git` | 72.60.226.4 | | **inmobiliaria-analytics** | `72.60.226.4:3000/rckrdmrd/inmobiliaria-analytics.git` | 72.60.226.4 | ### 2.2 Estructura Interna de Cada Repositorio ``` proyecto/ ├── apps/ │ ├── backend/ │ │ ├── src/ │ │ ├── Dockerfile │ │ ├── package.json │ │ ├── .env.example │ │ └── .env.production │ └── frontend/ │ ├── src/ │ ├── Dockerfile │ ├── nginx.conf │ ├── package.json │ ├── .env.example │ └── .env.production ├── database/ │ ├── schemas/ │ ├── seeds/ │ └── migrations/ ├── docker/ │ ├── docker-compose.yml │ ├── docker-compose.prod.yml │ └── .env.docker ├── jenkins/ │ └── Jenkinsfile ├── nginx/ │ └── project.conf ├── scripts/ │ ├── deploy.sh │ ├── rollback.sh │ └── health-check.sh ├── .env.ports └── README.md ``` --- ## 3. Asignación de Subdominios ### 3.1 Servidor Principal (72.60.226.4) | Subdominio | Proyecto | Frontend | Backend API | |------------|----------|----------|-------------| | `trading.isem.dev` | trading-platform | 3080 | 3081 | | `api.trading.isem.dev` | trading-platform | - | 3081 | | `erp.isem.dev` | erp-core | 3010 | 3011 | | `api.erp.isem.dev` | erp-core | - | 3011 | | `construccion.erp.isem.dev` | construccion | 3020 | 3021 | | `vidrio.erp.isem.dev` | vidrio-templado | 3030 | 3031 | | `mecanicas.erp.isem.dev` | mecanicas-diesel | 3040 | 3041 | | `retail.erp.isem.dev` | retail | 3050 | 3051 | | `clinicas.erp.isem.dev` | clinicas | 3060 | 3061 | | `pos.erp.isem.dev` | pos-micro | 3070 | 3071 | | `pmc.isem.dev` | platform-marketing-content | 3110 | 3111 | | `api.pmc.isem.dev` | platform-marketing-content | - | 3111 | | `betting.isem.dev` | betting-analytics | 3090 | 3091 | | `inmobiliaria.isem.dev` | inmobiliaria-analytics | 3100 | 3101 | ### 3.2 Servidor Gamilit (74.208.126.102) | Subdominio | Proyecto | Frontend | Backend API | |------------|----------|----------|-------------| | `gamilit.com` | gamilit | 3005 | 3006 | | `api.gamilit.com` | gamilit | - | 3006 | | `app.gamilit.com` | gamilit | 3005 | - | --- ## 4. Configuración Nginx ### 4.1 Nginx Principal (72.60.226.4) Archivo: `/etc/nginx/nginx.conf` ```nginx user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; multi_accept on; use epoll; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # Logging log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; # Performance sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; client_max_body_size 100M; # Gzip gzip on; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_types text/plain text/css text/xml application/json application/javascript application/xml application/xml+rss text/javascript application/x-javascript; # Rate Limiting limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; limit_conn_zone $binary_remote_addr zone=conn_limit:10m; # Upstreams include /etc/nginx/upstreams/*.conf; # Virtual Hosts include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } ``` ### 4.2 Upstreams por Proyecto Archivo: `/etc/nginx/upstreams/projects.conf` ```nginx # ============================================================================= # TRADING PLATFORM # ============================================================================= upstream trading_frontend { server 127.0.0.1:3080; keepalive 32; } upstream trading_backend { server 127.0.0.1:3081; keepalive 32; } upstream trading_websocket { server 127.0.0.1:3082; keepalive 32; } # ============================================================================= # ERP SUITE # ============================================================================= upstream erp_core_frontend { server 127.0.0.1:3010; keepalive 32; } upstream erp_core_backend { server 127.0.0.1:3011; keepalive 32; } upstream erp_construccion_frontend { server 127.0.0.1:3020; keepalive 32; } upstream erp_construccion_backend { server 127.0.0.1:3021; keepalive 32; } upstream erp_vidrio_frontend { server 127.0.0.1:3030; keepalive 32; } upstream erp_vidrio_backend { server 127.0.0.1:3031; keepalive 32; } upstream erp_mecanicas_frontend { server 127.0.0.1:3040; keepalive 32; } upstream erp_mecanicas_backend { server 127.0.0.1:3041; keepalive 32; } upstream erp_retail_frontend { server 127.0.0.1:3050; keepalive 32; } upstream erp_retail_backend { server 127.0.0.1:3051; keepalive 32; } upstream erp_clinicas_frontend { server 127.0.0.1:3060; keepalive 32; } upstream erp_clinicas_backend { server 127.0.0.1:3061; keepalive 32; } upstream erp_pos_frontend { server 127.0.0.1:3070; keepalive 32; } upstream erp_pos_backend { server 127.0.0.1:3071; keepalive 32; } # ============================================================================= # PLATFORM MARKETING CONTENT # ============================================================================= upstream pmc_frontend { server 127.0.0.1:3110; keepalive 32; } upstream pmc_backend { server 127.0.0.1:3111; keepalive 32; } # ============================================================================= # BETTING ANALYTICS (RESERVADO) # ============================================================================= upstream betting_frontend { server 127.0.0.1:3090; keepalive 32; } upstream betting_backend { server 127.0.0.1:3091; keepalive 32; } # ============================================================================= # INMOBILIARIA ANALYTICS (RESERVADO) # ============================================================================= upstream inmobiliaria_frontend { server 127.0.0.1:3100; keepalive 32; } upstream inmobiliaria_backend { server 127.0.0.1:3101; keepalive 32; } ``` ### 4.3 Virtual Host Template Archivo: `/etc/nginx/conf.d/trading.conf` ```nginx # ============================================================================= # TRADING PLATFORM - trading.isem.dev # ============================================================================= # HTTP -> HTTPS redirect server { listen 80; server_name trading.isem.dev api.trading.isem.dev; return 301 https://$server_name$request_uri; } # Frontend server { listen 443 ssl http2; server_name trading.isem.dev; # SSL Configuration ssl_certificate /etc/letsencrypt/live/isem.dev/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/isem.dev/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:10m; ssl_session_timeout 1d; # Security Headers add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always; # Frontend proxy location / { proxy_pass http://trading_frontend; proxy_http_version 1.1; 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 $scheme; proxy_cache_bypass $http_upgrade; } # Static assets caching location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { proxy_pass http://trading_frontend; expires 1y; add_header Cache-Control "public, immutable"; } # WebSocket location /ws { proxy_pass http://trading_websocket; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 86400; } } # Backend API server { listen 443 ssl http2; server_name api.trading.isem.dev; ssl_certificate /etc/letsencrypt/live/isem.dev/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/isem.dev/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; # Rate limiting limit_req zone=api_limit burst=20 nodelay; limit_conn conn_limit 10; # API proxy location / { proxy_pass http://trading_backend; 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 $scheme; # CORS headers add_header Access-Control-Allow-Origin $http_origin always; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always; add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With" always; add_header Access-Control-Allow-Credentials "true" always; if ($request_method = OPTIONS) { return 204; } } # Health check endpoint location /health { proxy_pass http://trading_backend/health; access_log off; } } ``` --- ## 5. Despliegue con PM2 (Gamilit) > **IMPORTANTE:** Gamilit NO usa Jenkins ni Docker. Usa PM2 como gestor de procesos. ### 5.1 Configuración PM2 (74.208.126.102) **Archivo:** `ecosystem.config.js` ```javascript module.exports = { apps: [ { name: 'gamilit-backend', cwd: './apps/backend', script: 'dist/main.js', instances: 2, // Cluster mode con 2 instancias exec_mode: 'cluster', env_production: { NODE_ENV: 'production', PORT: 3006, }, max_memory_restart: '1G', error_file: '../../logs/backend-error.log', out_file: '../../logs/backend-out.log', }, { name: 'gamilit-frontend', cwd: './apps/frontend', script: 'npx', args: 'vite preview --port 3005 --host 0.0.0.0', instances: 1, exec_mode: 'fork', env_production: { NODE_ENV: 'production', }, max_memory_restart: '512M', }, ], }; ``` ### 5.2 Comandos de Despliegue Gamilit ```bash # Conectar al servidor ssh isem@74.208.126.102 # Ir al directorio del proyecto cd /home/isem/workspace/workspace-gamilit/gamilit/projects/gamilit # Pull cambios git pull origin main # Instalar dependencias npm install # Build backend y frontend npm run build:all # Reiniciar con PM2 pm2 reload ecosystem.config.js --env production # Guardar configuración pm2 save # Verificar estado pm2 status pm2 logs ``` ### 5.3 Script de Despliegue Automatizado **Archivo:** `apps/devops/scripts/deploy.sh` ```bash ./deploy.sh --env prod # Despliegue completo ./deploy.sh --env prod --skip-db # Sin reiniciar BD ./deploy.sh --env prod --dry-run # Simular despliegue ``` --- ## 6. Jenkins Pipeline (Solo 72.60.226.4) > **NOTA:** Esta sección solo aplica para proyectos en el servidor 72.60.226.4. > Gamilit usa PM2 (ver sección 5). ### 6.1 Estructura Jenkins ``` /var/jenkins_home/ ├── jobs/ │ ├── trading-platform/ │ │ ├── backend/ │ │ └── frontend/ │ ├── erp-suite/ │ │ ├── erp-core/ │ │ ├── construccion/ │ │ ├── vidrio-templado/ │ │ └── ... │ └── pmc/ │ ├── backend/ │ └── frontend/ ├── shared-libraries/ │ └── vars/ │ ├── deployNode.groovy │ ├── deployDocker.groovy │ └── notifySlack.groovy └── credentials/ ├── ssh-keys/ └── docker-registry/ ``` ### 6.2 Jenkinsfile Template Archivo: `jenkins/Jenkinsfile` ```groovy pipeline { agent any environment { PROJECT_NAME = 'trading-platform' DOCKER_REGISTRY = '72.60.226.4:5000' DEPLOY_SERVER = '72.60.226.4' DEPLOY_USER = 'deploy' // Credenciales DOCKER_CREDENTIALS = credentials('docker-registry') SSH_KEY = credentials('deploy-ssh-key') // Versión VERSION = "${env.BUILD_NUMBER}" GIT_COMMIT_SHORT = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim() } options { timeout(time: 30, unit: 'MINUTES') disableConcurrentBuilds() buildDiscarder(logRotator(numToKeepStr: '10')) } stages { stage('Checkout') { steps { checkout scm script { env.GIT_BRANCH = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim() } } } stage('Install Dependencies') { parallel { stage('Backend') { steps { dir('apps/backend') { sh 'npm ci' } } } stage('Frontend') { steps { dir('apps/frontend') { sh 'npm ci' } } } } } stage('Lint & Test') { parallel { stage('Backend Lint') { steps { dir('apps/backend') { sh 'npm run lint' } } } stage('Backend Test') { steps { dir('apps/backend') { sh 'npm run test' } } } stage('Frontend Lint') { steps { dir('apps/frontend') { sh 'npm run lint' } } } stage('Frontend Test') { steps { dir('apps/frontend') { sh 'npm run test' } } } } } stage('Build') { parallel { stage('Build Backend') { steps { dir('apps/backend') { sh 'npm run build' } } } stage('Build Frontend') { steps { dir('apps/frontend') { sh 'npm run build' } } } } } stage('Docker Build & Push') { when { anyOf { branch 'main' branch 'develop' } } parallel { stage('Backend Image') { steps { dir('apps/backend') { script { def image = docker.build("${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:${VERSION}") docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry') { image.push() image.push('latest') } } } } } stage('Frontend Image') { steps { dir('apps/frontend') { script { def image = docker.build("${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:${VERSION}") docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry') { image.push() image.push('latest') } } } } } } } stage('Deploy to Staging') { when { branch 'develop' } steps { script { deployToServer('staging') } } } stage('Deploy to Production') { when { branch 'main' } steps { input message: 'Deploy to Production?', ok: 'Deploy' script { deployToServer('production') } } } stage('Health Check') { steps { script { def healthUrl = env.GIT_BRANCH == 'main' ? "https://api.trading.isem.dev/health" : "https://staging.api.trading.isem.dev/health" retry(3) { sleep(time: 10, unit: 'SECONDS') sh "curl -f ${healthUrl} || exit 1" } } } } } post { success { slackSend( color: 'good', message: "✅ ${PROJECT_NAME} deployed successfully! Build #${BUILD_NUMBER}" ) } failure { slackSend( color: 'danger', message: "❌ ${PROJECT_NAME} deployment failed! Build #${BUILD_NUMBER}" ) } always { cleanWs() } } } def deployToServer(String environment) { def composeFile = environment == 'production' ? 'docker/docker-compose.prod.yml' : 'docker/docker-compose.staging.yml' sshagent(['deploy-ssh-key']) { sh """ ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_SERVER} ' cd /opt/apps/${PROJECT_NAME} docker-compose -f ${composeFile} pull docker-compose -f ${composeFile} up -d docker system prune -f ' """ } } ``` ### 6.3 Pipeline para ERP-Suite (Multi-Vertical) Archivo: `erp-suite/jenkins/Jenkinsfile` ```groovy pipeline { agent any parameters { choice( name: 'VERTICAL', choices: ['erp-core', 'construccion', 'vidrio-templado', 'mecanicas-diesel', 'retail', 'clinicas', 'pos-micro', 'all'], description: 'Select vertical to deploy' ) choice( name: 'ENVIRONMENT', choices: ['staging', 'production'], description: 'Target environment' ) } environment { DOCKER_REGISTRY = '72.60.226.4:5000' DEPLOY_SERVER = '72.60.226.4' } stages { stage('Determine Verticals') { steps { script { if (params.VERTICAL == 'all') { env.VERTICALS = 'erp-core,construccion,vidrio-templado,mecanicas-diesel,retail,clinicas,pos-micro' } else { env.VERTICALS = params.VERTICAL } } } } stage('Build & Deploy Verticals') { steps { script { def verticals = env.VERTICALS.split(',') def parallelStages = [:] verticals.each { vertical -> parallelStages[vertical] = { stage("Build ${vertical}") { dir("apps/${getVerticalPath(vertical)}") { sh 'npm ci && npm run build' } } stage("Docker ${vertical}") { dir("apps/${getVerticalPath(vertical)}") { def backendImage = docker.build("${DOCKER_REGISTRY}/erp-${vertical}-backend:${BUILD_NUMBER}") def frontendImage = docker.build("${DOCKER_REGISTRY}/erp-${vertical}-frontend:${BUILD_NUMBER}") docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry') { backendImage.push() frontendImage.push() } } } } } parallel parallelStages } } } stage('Deploy') { steps { script { def verticals = env.VERTICALS.split(',') verticals.each { vertical -> deployVertical(vertical, params.ENVIRONMENT) } } } } } } def getVerticalPath(String vertical) { if (vertical == 'erp-core') return 'erp-core' if (vertical == 'pos-micro') return 'products/pos-micro' return "verticales/${vertical}" } def deployVertical(String vertical, String environment) { def ports = getVerticalPorts(vertical) sshagent(['deploy-ssh-key']) { sh """ ssh deploy@${DEPLOY_SERVER} ' cd /opt/apps/erp-suite/${vertical} docker-compose -f docker-compose.${environment}.yml pull docker-compose -f docker-compose.${environment}.yml up -d ' """ } } def getVerticalPorts(String vertical) { def portMap = [ 'erp-core': [fe: 3010, be: 3011], 'construccion': [fe: 3020, be: 3021], 'vidrio-templado': [fe: 3030, be: 3031], 'mecanicas-diesel': [fe: 3040, be: 3041], 'retail': [fe: 3050, be: 3051], 'clinicas': [fe: 3060, be: 3061], 'pos-micro': [fe: 3070, be: 3071] ] return portMap[vertical] } ``` --- ## 7. Variables de Entorno ### 7.1 Estructura por Ambiente ``` .env.development # Desarrollo local .env.staging # Ambiente de pruebas .env.production # Producción .env.example # Template con placeholders ``` ### 7.2 Template Variables Backend Archivo: `.env.production.template` ```bash # ============================================================================= # [PROJECT_NAME] Backend - Production Environment # ============================================================================= # Application NODE_ENV=production PORT=${BACKEND_PORT} API_PREFIX=api API_VERSION=v1 # Server SERVER_URL=https://api.${SUBDOMAIN}.isem.dev FRONTEND_URL=https://${SUBDOMAIN}.isem.dev # Database DB_HOST=${DB_HOST:-localhost} DB_PORT=${DB_PORT:-5432} DB_NAME=${DB_NAME} DB_USER=${DB_USER} DB_PASSWORD=${DB_PASSWORD} DB_SSL=true DB_POOL_MAX=20 # Redis REDIS_HOST=${REDIS_HOST:-localhost} REDIS_PORT=${REDIS_PORT:-6379} REDIS_PASSWORD=${REDIS_PASSWORD} # JWT (CHANGE IN PRODUCTION!) JWT_SECRET=${JWT_SECRET} JWT_EXPIRES_IN=15m JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET} JWT_REFRESH_EXPIRES_IN=7d # CORS CORS_ORIGIN=https://${SUBDOMAIN}.isem.dev # Security ENABLE_SWAGGER=false RATE_LIMIT_WINDOW_MS=60000 RATE_LIMIT_MAX=100 # Logging LOG_LEVEL=warn LOG_TO_FILE=true LOG_FILE_PATH=/var/log/${PROJECT_NAME}/app.log ``` ### 7.3 Template Variables Frontend Archivo: `.env.production.template` ```bash # ============================================================================= # [PROJECT_NAME] Frontend - Production Environment # ============================================================================= # Application VITE_APP_NAME=${PROJECT_NAME} VITE_APP_VERSION=${VERSION} VITE_APP_ENV=production # API Configuration VITE_API_URL=https://api.${SUBDOMAIN}.isem.dev VITE_API_PREFIX=api/v1 VITE_API_TIMEOUT=30000 # WebSocket VITE_WS_URL=wss://api.${SUBDOMAIN}.isem.dev/ws # Features VITE_ENABLE_DEBUG=false VITE_ENABLE_ANALYTICS=true VITE_LOG_LEVEL=error # External Services VITE_SENTRY_DSN=${SENTRY_DSN} VITE_GOOGLE_ANALYTICS_ID=${GA_ID} ``` ### 7.4 Matriz de Variables por Proyecto | Variable | gamilit | trading | erp-core | pmc | |----------|---------|---------|----------|-----| | **BACKEND_PORT** | 3006 | 3081 | 3011 | 3111 | | **FRONTEND_PORT** | 3005 | 3080 | 3010 | 3110 | | **DB_NAME** | gamilit_platform | orbiquant_platform | erp_generic | pmc_dev | | **DB_USER** | gamilit_user | orbiquant_user | erp_admin | pmc_user | | **SUBDOMAIN** | gamilit.com | trading.isem.dev | erp.isem.dev | pmc.isem.dev | | **SERVER** | 74.208.126.102 | 72.60.226.4 | 72.60.226.4 | 72.60.226.4 | --- ## 8. Docker Compose Producción Archivo: `docker/docker-compose.prod.yml` ```yaml version: '3.8' services: backend: image: ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:${VERSION:-latest} container_name: ${PROJECT_NAME}-backend restart: unless-stopped ports: - "${BACKEND_PORT}:${BACKEND_PORT}" environment: - NODE_ENV=production env_file: - ../apps/backend/.env.production volumes: - backend-logs:/var/log/${PROJECT_NAME} healthcheck: test: ["CMD", "curl", "-f", "http://localhost:${BACKEND_PORT}/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: - app-network deploy: resources: limits: cpus: '1' memory: 512M reservations: cpus: '0.5' memory: 256M frontend: image: ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:${VERSION:-latest} container_name: ${PROJECT_NAME}-frontend restart: unless-stopped ports: - "${FRONTEND_PORT}:80" depends_on: - backend healthcheck: test: ["CMD", "curl", "-f", "http://localhost:80"] interval: 30s timeout: 10s retries: 3 networks: - app-network deploy: resources: limits: cpus: '0.5' memory: 128M volumes: backend-logs: networks: app-network: external: true name: isem-network ``` --- ## 9. Scripts de Despliegue ### 9.1 Script de Deploy Automatizado Archivo: `scripts/deploy.sh` ```bash #!/bin/bash # ============================================================================= # Deploy Script - Multi-Project # ============================================================================= set -e # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # Configuration PROJECT_NAME="${PROJECT_NAME:-}" ENVIRONMENT="${ENVIRONMENT:-production}" VERSION="${VERSION:-latest}" DEPLOY_SERVER="${DEPLOY_SERVER:-72.60.226.4}" DOCKER_REGISTRY="${DOCKER_REGISTRY:-72.60.226.4:5000}" # Functions log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } check_prerequisites() { log_info "Checking prerequisites..." command -v docker >/dev/null 2>&1 || { log_error "Docker not installed"; exit 1; } command -v ssh >/dev/null 2>&1 || { log_error "SSH not available"; exit 1; } if [ -z "$PROJECT_NAME" ]; then log_error "PROJECT_NAME not set" exit 1 fi } build_images() { log_info "Building Docker images..." # Backend docker build -t ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:${VERSION} \ -f apps/backend/Dockerfile apps/backend/ # Frontend docker build -t ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:${VERSION} \ -f apps/frontend/Dockerfile apps/frontend/ } push_images() { log_info "Pushing images to registry..." docker push ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:${VERSION} docker push ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:${VERSION} # Tag as latest docker tag ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:${VERSION} \ ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:latest docker tag ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:${VERSION} \ ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:latest docker push ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:latest docker push ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:latest } deploy_to_server() { log_info "Deploying to ${DEPLOY_SERVER}..." ssh deploy@${DEPLOY_SERVER} << EOF cd /opt/apps/${PROJECT_NAME} # Pull latest images docker-compose -f docker-compose.${ENVIRONMENT}.yml pull # Stop current containers docker-compose -f docker-compose.${ENVIRONMENT}.yml down # Start new containers docker-compose -f docker-compose.${ENVIRONMENT}.yml up -d # Cleanup docker system prune -f # Health check sleep 10 curl -f http://localhost:${BACKEND_PORT}/health || exit 1 EOF } rollback() { log_warn "Rolling back to previous version..." ssh deploy@${DEPLOY_SERVER} << EOF cd /opt/apps/${PROJECT_NAME} docker-compose -f docker-compose.${ENVIRONMENT}.yml down # Use previous tag sed -i 's/:latest/:previous/g' docker-compose.${ENVIRONMENT}.yml docker-compose -f docker-compose.${ENVIRONMENT}.yml up -d EOF } # Main main() { check_prerequisites case "$1" in build) build_images ;; push) push_images ;; deploy) deploy_to_server ;; full) build_images push_images deploy_to_server ;; rollback) rollback ;; *) echo "Usage: $0 {build|push|deploy|full|rollback}" exit 1 ;; esac } main "$@" ``` ### 9.2 Script de Configuración de Nginx Archivo: `scripts/setup-nginx.sh` ```bash #!/bin/bash # ============================================================================= # Setup Nginx for Multi-Project # ============================================================================= set -e NGINX_CONF_DIR="/etc/nginx" PROJECTS_CONF_DIR="${NGINX_CONF_DIR}/conf.d" UPSTREAMS_DIR="${NGINX_CONF_DIR}/upstreams" # Project configurations declare -A PROJECTS=( ["trading"]="3080:3081:3082" ["erp-core"]="3010:3011" ["construccion"]="3020:3021" ["vidrio"]="3030:3031" ["mecanicas"]="3040:3041" ["retail"]="3050:3051" ["clinicas"]="3060:3061" ["pos"]="3070:3071" ["pmc"]="3110:3111" ["betting"]="3090:3091" ["inmobiliaria"]="3100:3101" ) create_upstream() { local name=$1 local ports=$2 IFS=':' read -ra PORT_ARRAY <<< "$ports" local fe_port=${PORT_ARRAY[0]} local be_port=${PORT_ARRAY[1]} local ws_port=${PORT_ARRAY[2]:-} cat > "${UPSTREAMS_DIR}/${name}.conf" << EOF upstream ${name}_frontend { server 127.0.0.1:${fe_port}; keepalive 32; } upstream ${name}_backend { server 127.0.0.1:${be_port}; keepalive 32; } EOF if [ -n "$ws_port" ]; then cat >> "${UPSTREAMS_DIR}/${name}.conf" << EOF upstream ${name}_websocket { server 127.0.0.1:${ws_port}; keepalive 32; } EOF fi } create_vhost() { local name=$1 local subdomain="${name}.isem.dev" cat > "${PROJECTS_CONF_DIR}/${name}.conf" << EOF server { listen 80; server_name ${subdomain} api.${subdomain}; return 301 https://\$server_name\$request_uri; } server { listen 443 ssl http2; server_name ${subdomain}; ssl_certificate /etc/letsencrypt/live/isem.dev/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/isem.dev/privkey.pem; location / { proxy_pass http://${name}_frontend; 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 \$scheme; } } server { listen 443 ssl http2; server_name api.${subdomain}; ssl_certificate /etc/letsencrypt/live/isem.dev/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/isem.dev/privkey.pem; location / { proxy_pass http://${name}_backend; 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 \$scheme; } } EOF } # Main mkdir -p "${UPSTREAMS_DIR}" for project in "${!PROJECTS[@]}"; do echo "Configuring ${project}..." create_upstream "$project" "${PROJECTS[$project]}" create_vhost "$project" done # Test configuration nginx -t # Reload systemctl reload nginx echo "Nginx configured successfully!" ``` --- ## 10. Checklist de Implementación ### Fase 1: Infraestructura Base (Servidor 72.60.226.4) - [ ] Instalar Docker y Docker Compose - [ ] Configurar Docker Registry privado (puerto 5000) - [ ] Instalar y configurar Jenkins - [ ] Instalar Nginx - [ ] Obtener certificados SSL (Let's Encrypt wildcard para *.isem.dev) - [ ] Configurar red Docker compartida (`isem-network`) - [ ] Crear directorios base `/opt/apps/{proyecto}` ### Fase 2: Separación de Repositorios - [ ] Crear repositorios en Gitea para cada proyecto - [ ] Migrar código de monorepo a repos individuales - [ ] Configurar webhooks de Gitea a Jenkins - [ ] Actualizar referencias de git remotes ### Fase 3: CI/CD Jenkins - [ ] Crear jobs de Jenkins por proyecto - [ ] Configurar credenciales (SSH, Docker Registry) - [ ] Configurar shared libraries - [ ] Probar pipelines en ambiente staging ### Fase 4: Nginx y DNS - [ ] Configurar upstreams por proyecto - [ ] Crear virtual hosts - [ ] Configurar DNS para subdominios - [ ] Verificar SSL/TLS ### Fase 5: Despliegue - [ ] Desplegar trading-platform - [ ] Desplegar erp-suite (por verticales) - [ ] Desplegar platform-marketing-content - [ ] Verificar health checks - [ ] Configurar monitoreo --- ## 11. Referencias - **Inventario de Puertos:** `/home/isem/workspace/core/orchestration/inventarios/DEVENV-PORTS-INVENTORY.yml` - **Inventario de Despliegue:** `/home/isem/workspace/core/orchestration/inventarios/DEPLOYMENT-INVENTORY.yml` - **Gitea (72.60.226.4):** http://72.60.226.4:3000 ### Gamilit (PM2) - **Servidor:** 74.208.126.102 - **Repositorio:** https://github.com/rckrdmrd/gamilit-workspace.git - **ecosystem.config.js:** `/home/isem/workspace/projects/gamilit/ecosystem.config.js` - **Deploy Script:** `/home/isem/workspace/projects/gamilit/apps/devops/scripts/deploy.sh` - **Documentación:** `/home/isem/workspace/projects/gamilit/README.md` --- *Documento generado por DevEnv Agent - 2025-12-12* *Versión: 1.0.0*