/** * PM2 config for blut24-landing. Deployed to /var/www/blut24.com/ecosystem.config.cjs * — i.e. it lives OUTSIDE the release directories, so it survives every deploy. * * Zero-downtime hinges on two details here: * * 1. `script` points through the `current` SYMLINK, never at a release path. deploy.sh * swaps that symlink and then runs `pm2 reload`; each worker PM2 spawns resolves * the symlink afresh and therefore boots the NEW release. * * 2. `exec_mode: 'cluster'` — only cluster mode gives a rolling reload (spawn the * replacement, wait for it, then retire the old one). In fork mode `pm2 reload` * degrades to a stop/start and drops requests. */ const fs = require('fs') const path = require('path') // ISPConfig-managed host: code lives in the site's docroot (nginx proxies, so // nothing here is ever served as a file); secrets and the SQLite databases live // in the site's private/ dir, OUTSIDE the docroot. const APP_ROOT = '/var/www/blut24.com/web' const SHARED_ROOT = '/var/www/blut24.com/private/blut24-landing' /** Read shared/.env into an object. Parsed here so a deploy picks up env changes * on `pm2 reload --update-env` without editing this file. */ function loadEnvFile(file) { const out = {} if (!fs.existsSync(file)) return out for (const raw of fs.readFileSync(file, 'utf8').split('\n')) { const line = raw.trim() if (!line || line.startsWith('#')) continue const eq = line.indexOf('=') if (eq < 0) continue let val = line.slice(eq + 1).trim() if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) { val = val.slice(1, -1) } out[line.slice(0, eq).trim()] = val } return out } const env = loadEnvFile(path.join(SHARED_ROOT, 'shared', '.env')) module.exports = { apps: [ { name: 'blut24-landing', script: path.join(APP_ROOT, 'current', '.output', 'server', 'index.mjs'), cwd: path.join(APP_ROOT, 'current'), exec_mode: 'cluster', // Two workers so a reload always has a live one serving. SQLite stays safe // because registrations.db runs in WAL with a busy_timeout, and clinics.db // is opened query_only. instances: 2, interpreter: '/root/.nvm/versions/node/v22.22.3/bin/node', max_memory_restart: '512M', // Give a worker time to bind before PM2 retires its predecessor. listen_timeout: 10000, kill_timeout: 5000, // Never let a crash-looping bad build hammer the box; deploy.sh health-checks // and rolls back, this is just the backstop. max_restarts: 10, restart_delay: 2000, out_file: path.join(SHARED_ROOT, 'logs', 'out.log'), error_file: path.join(SHARED_ROOT, 'logs', 'error.log'), merge_logs: true, time: true, env: { NODE_ENV: 'production', NITRO_PORT: env.NITRO_PORT || '3002', NITRO_HOST: env.NITRO_HOST || '127.0.0.1', ...env, }, }, ], }