Files
tssbot.web/ecosystem.config.cjs
T
2026-06-29 12:58:02 +00:00

115 lines
4.4 KiB
JavaScript

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',
},
},
],
}