/** * DashCaddy API Server - Entry Point * Minimal startup script - all logic moved to src/ */ const { createApp } = require('./src/app'); const platformPaths = require('./platform-paths'); // Unhandled error handlers process.on('unhandledRejection', (reason) => { console.error('[FATAL] Unhandled Promise Rejection:', reason); process.exit(1); }); process.on('uncaughtException', (error) => { console.error('[FATAL] Uncaught Exception:', error); setTimeout(() => process.exit(1), 1000).unref(); }); // Main startup (async () => { try { // Create and configure Express app const { app, log, config, licenseManager } = await createApp(); // Load license await licenseManager.load(); const PORT = process.env.PORT || 3001; const CADDYFILE_PATH = process.env.CADDYFILE_PATH || platformPaths.caddyfile; const CADDY_ADMIN_URL = process.env.CADDY_ADMIN_URL || platformPaths.caddyAdminUrl; const SERVICES_FILE = process.env.SERVICES_FILE || platformPaths.servicesFile; const CONFIG_FILE = process.env.CONFIG_FILE || platformPaths.servicesFile.replace('services.json', 'config.json'); // Validate startup configuration const { validateStartupConfig } = require('./startup-validator'); await validateStartupConfig({ log, CADDYFILE_PATH, SERVICES_FILE, CONFIG_FILE, CADDY_ADMIN_URL, PORT }); // Start HTTP server const server = app.listen(PORT, '0.0.0.0', () => { log.info('server', 'DashCaddy API server started', { port: PORT, caddyfile: CADDYFILE_PATH, caddyAdmin: CADDY_ADMIN_URL, services: SERVICES_FILE, environment: process.env.NODE_ENV || 'production' }); // Attach WebSocket exec handler const attachExecWS = require('./routes/exec'); attachExecWS(server, log); log.info('server', 'WebSocket exec handler attached'); // Start feature modules const resourceMonitor = require('./resource-monitor'); const backupManager = require('./backup-manager'); const healthChecker = require('./health-checker'); const updateManager = require('./update-manager'); const selfUpdater = require('./self-updater'); const portLockManager = require('./port-lock-manager'); // Optional modules let dockerMaintenance, logDigest; try { dockerMaintenance = require('./docker-maintenance'); } catch (_) {} try { logDigest = require('./log-digest'); } catch (_) {} log.info('server', 'Starting feature modules'); // Clean up stale port locks portLockManager.cleanupStaleLocks() .then(() => log.info('server', 'Port lock cleanup completed')) .catch(err => log.error('server', 'Port lock cleanup failed', { error: err.message })); // Resource monitoring try { resourceMonitor.start(); log.info('server', 'Resource monitoring started'); } catch (err) { log.error('server', 'Resource monitoring failed to start', { error: err.message }); } // Backup manager try { backupManager.start(); log.info('server', 'Backup manager started'); } catch (err) { log.error('server', 'Backup manager failed to start', { error: err.message }); } // Health checker (with service sync) (async () => { try { const { syncHealthCheckerServices } = require('./startup-validator'); const StateManager = require('./state-manager'); const servicesStateManager = new StateManager(SERVICES_FILE); await syncHealthCheckerServices({ log, SERVICES_FILE, servicesStateManager, healthChecker, buildServiceUrl: (subdomain) => config.routingMode === 'subdirectory' && config.domain ? `https://${config.domain}/${subdomain}` : `https://${subdomain}${config.tld}`, siteConfig: config, APP: require('./constants').APP }); healthChecker.start(); log.info('server', 'Health checker started'); } catch (err) { log.error('server', 'Health checker failed to start', { error: err.message }); } })(); // Update manager try { updateManager.start(); log.info('server', 'Update manager started'); } catch (err) { log.error('server', 'Update manager failed to start', { error: err.message }); } // Self-updater try { selfUpdater.start(); log.info('server', 'Self-updater started', { interval: selfUpdater.config.checkInterval, url: selfUpdater.config.updateUrl }); selfUpdater.checkPostUpdateResult() .then(result => { if (result) { log.info('server', 'Post-update result', result); } }) .catch(() => {}); } catch (err) { log.error('server', 'Self-updater failed to start', { error: err.message }); } // Docker maintenance (optional) if (dockerMaintenance) { try { dockerMaintenance.start(); log.info('server', 'Docker maintenance started'); dockerMaintenance.on('maintenance-complete', (result) => { const saved = Math.round(result.spaceReclaimed.total / 1024 / 1024); if (saved > 0 || result.warnings.length > 0) { log.info('maintenance', 'Docker maintenance completed', { spaceReclaimedMB: saved, pruned: result.pruned, warnings: result.warnings.length }); } if (result.warnings.length > 0) { for (const w of result.warnings) log.warn('maintenance', w); } }); } catch (err) { log.error('server', 'Docker maintenance failed to start', { error: err.message }); } } // Log digest (optional) if (logDigest) { try { logDigest.start(platformPaths.digestDir); log.info('server', 'Log digest started', { digestDir: platformPaths.digestDir }); logDigest.on('digest-generated', ({ date }) => { log.info('digest', `Daily digest generated for ${date}`); }); } catch (err) { log.error('server', 'Log digest failed to start', { error: err.message }); } } log.info('server', 'All feature modules initialized'); }); // Graceful shutdown function shutdown(signal) { log.info('shutdown', `${signal} received, draining connections...`); const resourceMonitor = require('./resource-monitor'); const backupManager = require('./backup-manager'); const healthChecker = require('./health-checker'); const updateManager = require('./update-manager'); const selfUpdater = require('./self-updater'); resourceMonitor.stop(); backupManager.stop(); healthChecker.stop(); updateManager.stop(); selfUpdater.stop(); try { const dockerMaintenance = require('./docker-maintenance'); dockerMaintenance.stop(); } catch (_) {} try { const logDigest = require('./log-digest'); logDigest.stop(); } catch (_) {} server.close(() => { log.info('shutdown', 'HTTP server closed'); process.exit(0); }); // Force exit after 5s if connections don't drain setTimeout(() => process.exit(0), 5000).unref(); } process.on('SIGTERM', () => shutdown('SIGTERM')); process.on('SIGINT', () => shutdown('SIGINT')); } catch (error) { console.error('[FATAL] Server startup failed:', error); process.exit(1); } })(); // Export for testing module.exports = require('./src/app');