Files
dashcaddy/dashcaddy-api/server.js
Krystie 5da1e572a1 refactor(server): Complete Phase 2.1 - Split monolithic server.js
MASSIVE REFACTOR:
- Created src/app.js (17KB) - Express setup, middleware, routes
- Slimmed server.js from 1997 lines → 230 lines (88% reduction!)
- Backed up original as server-old.js for reference

NEW STRUCTURE:
src/
├── app.js (Express application factory)
├── config/ (paths, site config, constants)
├── context/ (DI container, domain modules)
└── utils/ (http, logging, responses, async-handler)

STATS:
- Old server.js: 1997 lines (monolith)
- New server.js: 230 lines (entry point only)
- Modular code: 1729 lines across 14 files in src/
- Total reduction: 88% in main file

server.js now ONLY handles:
- App creation
- License loading
- HTTP server startup
- Feature module initialization
- Graceful shutdown

All business logic moved to src/ modules. Clean, testable, maintainable.

Phase 2.1 COMPLETE 
2026-03-29 19:46:41 -07:00

231 lines
7.6 KiB
JavaScript

/**
* 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'
});
// 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');