diff --git a/dashcaddy-api/src/managers/resource-monitor.js b/dashcaddy-api/src/managers/resource-monitor.js index 4278870..a66f25f 100644 --- a/dashcaddy-api/src/managers/resource-monitor.js +++ b/dashcaddy-api/src/managers/resource-monitor.js @@ -19,6 +19,7 @@ const STATS_HOURLY_FILE = process.env.STATS_HOURLY_FILE || path.join(platformPat const STATS_DAILY_FILE = process.env.STATS_DAILY_FILE || path.join(platformPaths.dataDir, 'container-stats-daily.json'); const ALERT_CONFIG_FILE = process.env.ALERT_CONFIG_FILE || path.join(platformPaths.dataDir, 'alert-config.json'); const ALERT_HISTORY_FILE = process.env.ALERT_HISTORY_FILE || path.join(platformPaths.dataDir, 'alert-history.json'); +const MAX_STATS_PER_CONTAINER = parseInt(process.env.STATS_MAX_ENTRIES || '500', 10); // Cap to prevent disk explosion const STATS_RETENTION_HOURS = parseInt(process.env.STATS_RETENTION_HOURS || '168', 10); // 7 days raw const STATS_HOURLY_RETENTION_DAYS = parseInt(process.env.STATS_HOURLY_RETENTION_DAYS || '30', 10); // 30 days hourly const STATS_DAILY_RETENTION_DAYS = parseInt(process.env.STATS_DAILY_RETENTION_DAYS || '365', 10); // 365 days daily @@ -242,6 +243,11 @@ class ResourceMonitor extends EventEmitter { containerStats.history = containerStats.history.filter(s => new Date(s.timestamp).getTime() > cutoffTime ); + + // Also cap total entries per container (disk explosion fix) + if (containerStats.history.length > MAX_STATS_PER_CONTAINER) { + containerStats.history = containerStats.history.slice(-MAX_STATS_PER_CONTAINER); + } } /** @@ -620,7 +626,7 @@ class ResourceMonitor extends EventEmitter { saveStats() { try { const data = Object.fromEntries(this.stats); - fs.writeFileSync(STATS_FILE, JSON.stringify(data, null, 2)); + fs.writeFileSync(STATS_FILE, JSON.stringify(data)); // Compact JSON to reduce file size } catch (error) { log.error('monitor', error, { operation: 'saveStats' }); } diff --git a/dashcaddy-api/src/monitoring/health-checker.js b/dashcaddy-api/src/monitoring/health-checker.js index a1309f7..35e5819 100644 --- a/dashcaddy-api/src/monitoring/health-checker.js +++ b/dashcaddy-api/src/monitoring/health-checker.js @@ -30,6 +30,7 @@ const LEGACY_HEALTH_CONFIG_FILE = path.join(__dirname, 'health-config.json'); const LEGACY_HEALTH_HISTORY_FILE = path.join(__dirname, 'health-history.json'); const CHECK_INTERVAL = parseInt(process.env.HEALTH_CHECK_INTERVAL || '30000', 10); // 30 seconds const MAX_CHECK_INTERVAL = parseInt(process.env.HEALTH_CHECK_MAX_INTERVAL || '300000', 10); // 5 minutes max backoff +const MAX_ENTRIES_PER_SERVICE = parseInt(process.env.HEALTH_MAX_ENTRIES || '500', 10); // Cap to prevent disk explosion const HISTORY_RETENTION_DAYS = parseInt(process.env.HEALTH_HISTORY_RETENTION || '30', 10); class HealthChecker extends EventEmitter { @@ -217,7 +218,7 @@ class HealthChecker extends EventEmitter { statusCode: res.statusCode, message: healthy ? 'Service is healthy' : 'Service check failed', details: { - headers: res.headers, + headers: res.headers ? { server: res.headers.server } : undefined, // Compact: disk explosion fix bodyLength: data.length } }); @@ -285,6 +286,11 @@ class HealthChecker extends EventEmitter { } this.history[serviceId].push(status); + + // Cap entries to prevent unbounded growth (disk explosion fix) + if (this.history[serviceId].length > MAX_ENTRIES_PER_SERVICE) { + this.history[serviceId] = this.history[serviceId].slice(-MAX_ENTRIES_PER_SERVICE); + } // Emit status event this.emit('status-check', status); @@ -565,6 +571,10 @@ class HealthChecker extends EventEmitter { this.history[serviceId] = this.history[serviceId].filter(h => new Date(h.timestamp).getTime() > cutoffTime ); + // Also cap total entries per service + if (this.history[serviceId].length > MAX_ENTRIES_PER_SERVICE) { + this.history[serviceId] = this.history[serviceId].slice(-MAX_ENTRIES_PER_SERVICE); + } } } @@ -616,7 +626,7 @@ class HealthChecker extends EventEmitter { */ saveHistory() { try { - fs.writeFileSync(HEALTH_HISTORY_FILE, JSON.stringify(this.history, null, 2)); + fs.writeFileSync(HEALTH_HISTORY_FILE, JSON.stringify(this.history)); // Compact JSON (no pretty-print) to reduce file size } catch (error) { this.emit('log', 'error', `Error saving history: ${error.message}`); }