const fs = require('node:fs') const path = require('node:path') function loadEnvFile() { const envPath = path.join(__dirname, '.env') if (!fs.existsSync(envPath)) return const lines = fs.readFileSync(envPath, 'utf8').split(/\r?\n/) for (const line of lines) { const trimmed = line.trim() if (!trimmed || trimmed.startsWith('#')) continue const separatorIndex = trimmed.indexOf('=') if (separatorIndex === -1) continue const key = trimmed.slice(0, separatorIndex).trim() let value = trimmed.slice(separatorIndex + 1).trim() if ( (value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'")) ) { value = value.slice(1, -1) } if (key && (!process.env[key] || process.env[key] === '')) { process.env[key] = value } } } loadEnvFile() // Crash-loop governor: after max_restarts attempts that each fail to stay up // min_uptime ms, PM2 marks the app `errored` and stops relaunching it, instead // of restarting forever and pegging the CPU. const RESTART_POLICY = { max_restarts: 10, min_uptime: 10000, exp_backoff_restart_delay: 200, } module.exports = { apps: [ { name: 'tssbot-web', ...RESTART_POLICY, script: 'server.cjs', cwd: __dirname, exec_mode: 'cluster', instances: process.env.WEB_INSTANCES || 2, wait_ready: true, listen_timeout: 10000, kill_timeout: 10000, env: { NODE_ENV: 'production', PORT: process.env.PORT || 3010, API_UPSTREAM: process.env.API_UPSTREAM || 'http://127.0.0.1:6000', PUBLIC_ORIGIN: process.env.PUBLIC_ORIGIN || '', UPTIME_STORAGE_DIR: process.env.UPTIME_STORAGE_DIR || '~/tsswebstorage', UPTIME_DATABASE_FILE: process.env.UPTIME_DATABASE_FILE || 'uptime.sqlite', UPTIME_SAMPLE_INTERVAL_MS: process.env.UPTIME_SAMPLE_INTERVAL_MS || 1800000, UPTIME_HISTORY_LIMIT: process.env.UPTIME_HISTORY_LIMIT || 336, API_CACHE_TTL_MS: process.env.API_CACHE_TTL_MS || 15000, API_RATE_LIMIT_WINDOW_MS: process.env.API_RATE_LIMIT_WINDOW_MS || 60000, API_RATE_LIMIT_MAX: process.env.API_RATE_LIMIT_MAX || 120, TRUST_PROXY: process.env.TRUST_PROXY || 'cloudflare', TRUSTED_UPSTREAM_IPS: process.env.TRUSTED_UPSTREAM_IPS || '127.0.0.1,::1,::ffff:127.0.0.1', SITE_SESSION_SECRET: process.env.SITE_SESSION_SECRET || process.env.API_SESSION_SECRET || process.env.TURNSTILE_SECRET_KEY || '', SITE_SESSION_TTL_SECONDS: process.env.SITE_SESSION_TTL_SECONDS || 43200, TURNSTILE_SECRET_KEY: process.env.TURNSTILE_SECRET_KEY || '', }, }, { name: 'tssbot-webhook', ...RESTART_POLICY, script: 'webhook.cjs', cwd: __dirname, autorestart: true, env: { NODE_ENV: 'production', WEBHOOK_PORT: process.env.WEBHOOK_PORT || 3011, WEBHOOK_PM2_NAME: process.env.WEBHOOK_PM2_NAME || 'tssbot-webhook', GITHUB_WEBHOOK_SECRET: process.env.GITHUB_WEBHOOK_SECRET || '', GITHUB_WEBHOOK_REFS: process.env.GITHUB_WEBHOOK_REFS || 'refs/heads/main', GITHUB_WEBHOOK_REPOSITORY: process.env.GITHUB_WEBHOOK_REPOSITORY || '', PM2_RESTART_TARGETS: process.env.PM2_RESTART_TARGETS || 'tssbot-web,tssbot-backend', DISCORD_WEBHOOK_URL: process.env.DISCORD_WEBHOOK_URL || '', DISCORD_INCLUDE_PATCH: process.env.DISCORD_INCLUDE_PATCH || 'false', }, }, { name: 'tssbot-backend', ...RESTART_POLICY, script: process.platform === 'win32' ? 'backend/target/release/tssbot-backend.exe' : 'backend/target/release/tssbot-backend', cwd: __dirname, autorestart: true, env: { NODE_ENV: 'production', BACKEND_PORT: process.env.BACKEND_PORT || 6000, BACKEND_HOST: process.env.BACKEND_HOST || '127.0.0.1', BACKEND_ALLOWED_ORIGINS: process.env.BACKEND_ALLOWED_ORIGINS || process.env.PUBLIC_ORIGIN || '', TSS_BATTLES_DB: process.env.TSS_BATTLES_DB || 'tss_battles.db', TSS_TEAMS_DB: process.env.TSS_TEAMS_DB || 'tss_teams.db', // Vehicle name + icon caches (built by the bots in the shared STORAGE volume). VEHICLE_TRANSLATIONS_JSON: process.env.VEHICLE_TRANSLATIONS_JSON || '/mnt/HC_Volume_105581488/STORAGE/CACHE/vehicle_translations.json', VEHICLE_DATA_CACHE_JSON: process.env.VEHICLE_DATA_CACHE_JSON || '/mnt/HC_Volume_105581488/STORAGE/CACHE/vehicle_data_cache.json', }, }, ], }