// ecosystem-test.config.cjs // PM2 configuration for the TEST environment only. // NOTE: The filename must end with `.config.cjs` for PM2 to recognize it as a config file. // This file defines test-specific apps that run alongside production apps. // // Test apps: flyer-crawler-api-test, flyer-crawler-worker-test, flyer-crawler-analytics-worker-test // // These apps: // - Run from /var/www/flyer-crawler-test.projectium.com // - Use NODE_ENV='staging' (enables file logging in logger.server.ts) // - Use Redis database 1 (isolated from production which uses database 0) // - Have distinct PM2 process names to avoid conflicts with production // --- Load Environment Variables from .env file --- // This allows PM2 to start without requiring the CI/CD pipeline to inject variables. // The .env file should be created on the server with the required secrets. // NOTE: We implement a simple .env parser since dotenv may not be installed. const path = require('path'); const fs = require('fs'); const envPath = path.join('/var/www/flyer-crawler-test.projectium.com', '.env'); if (fs.existsSync(envPath)) { console.log('[ecosystem-test.config.cjs] Loading environment from:', envPath); const envContent = fs.readFileSync(envPath, 'utf8'); const lines = envContent.split('\n'); for (const line of lines) { // Skip comments and empty lines const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; // Parse KEY=value const eqIndex = trimmed.indexOf('='); if (eqIndex > 0) { const key = trimmed.substring(0, eqIndex); let value = trimmed.substring(eqIndex + 1); // Remove quotes if present if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1); } // Only set if not already in environment (don't override CI/CD vars) if (!process.env[key]) { process.env[key] = value; } } } console.log('[ecosystem-test.config.cjs] Environment loaded successfully'); } else { console.warn('[ecosystem-test.config.cjs] No .env file found at:', envPath); console.warn( '[ecosystem-test.config.cjs] Environment variables must be provided by the shell or CI/CD.' ); } // --- Environment Variable Validation --- // NOTE: We only WARN about missing secrets, not exit. // Calling process.exit(1) prevents PM2 from reading the apps array. // The actual application will fail to start if secrets are missing, // which PM2 will handle with its restart logic. const requiredSecrets = ['DB_HOST', 'JWT_SECRET', 'GEMINI_API_KEY']; const missingSecrets = requiredSecrets.filter(key => !process.env[key]); if (missingSecrets.length > 0) { console.warn('\n[ecosystem.config.test.cjs] WARNING: The following environment variables are MISSING:'); missingSecrets.forEach(key => console.warn(` - ${key}`)); console.warn('[ecosystem.config.test.cjs] The application may fail to start if these are required.\n'); } else { console.log('[ecosystem.config.test.cjs] Critical environment variables are present.'); } // --- Shared Environment Variables --- const sharedEnv = { DB_HOST: process.env.DB_HOST, DB_USER: process.env.DB_USER, DB_PASSWORD: process.env.DB_PASSWORD, DB_NAME: process.env.DB_NAME, REDIS_URL: process.env.REDIS_URL, REDIS_PASSWORD: process.env.REDIS_PASSWORD, FRONTEND_URL: process.env.FRONTEND_URL, JWT_SECRET: process.env.JWT_SECRET, GEMINI_API_KEY: process.env.GEMINI_API_KEY, GOOGLE_MAPS_API_KEY: process.env.GOOGLE_MAPS_API_KEY, SMTP_HOST: process.env.SMTP_HOST, SMTP_PORT: process.env.SMTP_PORT, SMTP_SECURE: process.env.SMTP_SECURE, SMTP_USER: process.env.SMTP_USER, SMTP_PASS: process.env.SMTP_PASS, SMTP_FROM_EMAIL: process.env.SMTP_FROM_EMAIL, SENTRY_DSN: process.env.SENTRY_DSN, SENTRY_ENVIRONMENT: process.env.SENTRY_ENVIRONMENT, SENTRY_ENABLED: process.env.SENTRY_ENABLED, }; module.exports = { apps: [ // ========================================================================= // TEST APPS // ========================================================================= { // --- Test API Server --- name: 'flyer-crawler-api-test', script: './node_modules/.bin/tsx', args: 'server.ts', cwd: '/var/www/flyer-crawler-test.projectium.com', max_memory_restart: '500M', // Test environment: single instance (no cluster) to conserve resources instances: 1, exec_mode: 'fork', kill_timeout: 5000, log_date_format: 'YYYY-MM-DD HH:mm:ss Z', max_restarts: 40, exp_backoff_restart_delay: 100, min_uptime: '10s', env: { NODE_ENV: 'staging', PORT: 3002, WORKER_LOCK_DURATION: '120000', ...sharedEnv, }, }, { // --- Test General Worker --- name: 'flyer-crawler-worker-test', script: './node_modules/.bin/tsx', args: 'src/services/worker.ts', cwd: '/var/www/flyer-crawler-test.projectium.com', max_memory_restart: '1G', kill_timeout: 10000, log_date_format: 'YYYY-MM-DD HH:mm:ss Z', max_restarts: 40, exp_backoff_restart_delay: 100, min_uptime: '10s', env: { NODE_ENV: 'staging', ...sharedEnv, }, }, { // --- Test Analytics Worker --- name: 'flyer-crawler-analytics-worker-test', script: './node_modules/.bin/tsx', args: 'src/services/worker.ts', cwd: '/var/www/flyer-crawler-test.projectium.com', max_memory_restart: '1G', kill_timeout: 10000, log_date_format: 'YYYY-MM-DD HH:mm:ss Z', max_restarts: 40, exp_backoff_restart_delay: 100, min_uptime: '10s', env: { NODE_ENV: 'staging', ...sharedEnv, }, }, ], };