DC-101: Disk Space Monitor + Product Vision
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s

Backend:
- src/monitoring/disk-space-monitor.js: monitors Docker disk usage against
  user-configured budget, auto-cleans at thresholds, breaks down by category
- routes/disk-space.js: GET /disk, GET /disk/breakdown, POST /disk/config,
  POST /disk/cleanup endpoints
- src/app.js: wire DiskSpaceMonitor into startup, 10-min check interval
- All 1539 tests pass

Product Vision (PRODUCT-VISION.md):
- DashCaddy is a self-hosting platform, not just a dashboard
- Core value: 'Self-host anything in 30 seconds'
- Three pillars: One-click deploy, zero-config networking, self-healing infra
- vs Portainer/CasaOS/Yunohost positioning

New backlog tasks (P5 tier, DC-101–108):
- Disk budget, one-click deploy with auto Caddyfile+DNS, container
  auto-discovery, app catalog, smart wizard, visual Caddy builder,
  disaster recovery, multi-host fleet management

47 total backlog tasks, ~110 hr of work, cron running every 2h.
This commit is contained in:
Hermes
2026-08-12 02:41:40 -07:00
parent 5c02bfba1d
commit ff81d99021
6 changed files with 692 additions and 1 deletions
+64
View File
@@ -0,0 +1,64 @@
const express = require('express');
const { success, error: errorResponse } = require('../src/utils/responses');
/**
* Disk space management routes
*
* GET /disk — current usage snapshot (budget, breakdown, status)
* GET /disk/breakdown — detailed breakdown incl. per-container log sizes
* GET /disk/config — get disk budget settings
* POST /disk/config — update disk budget settings
* POST /disk/cleanup — trigger manual cleanup (standard|aggressive|logs-only)
*/
module.exports = function({ diskSpaceMonitor, asyncHandler, log }) {
const router = express.Router();
// Current disk usage snapshot
router.get('/', asyncHandler(async (req, res) => {
const snapshot = await diskSpaceMonitor.getSnapshot();
success(res, snapshot);
}, 'disk-get'));
// Detailed breakdown (includes per-container log sizes)
router.get('/breakdown', asyncHandler(async (req, res) => {
const breakdown = await diskSpaceMonitor.getDetailedBreakdown();
success(res, breakdown);
}, 'disk-breakdown'));
// Get disk budget config
router.get('/config', asyncHandler(async (req, res) => {
success(res, diskSpaceMonitor.getConfig());
}, 'disk-config-get'));
// Update disk budget config
router.post('/config', asyncHandler(async (req, res) => {
const { diskBudgetGB, warningThresholdPct, criticalThresholdPct, autoCleanup, enabled, cleanupAggressivePct } = req.body;
const updates = {};
if (typeof diskBudgetGB === 'number' && diskBudgetGB > 0) updates.diskBudgetGB = Math.min(diskBudgetGB, 1000);
if (typeof warningThresholdPct === 'number') updates.warningThresholdPct = Math.min(Math.max(warningThresholdPct, 50), 99);
if (typeof criticalThresholdPct === 'number') updates.criticalThresholdPct = Math.min(Math.max(criticalThresholdPct, 60), 99);
if (typeof cleanupAggressivePct === 'number') updates.cleanupAggressivePct = Math.min(Math.max(cleanupAggressivePct, 70), 99);
if (typeof autoCleanup === 'boolean') updates.autoCleanup = autoCleanup;
if (typeof enabled === 'boolean') updates.enabled = enabled;
const config = diskSpaceMonitor.configure(updates);
log.info('disk', 'Disk budget updated', updates);
success(res, { message: 'Disk budget updated', config });
}, 'disk-config-set'));
// Manual cleanup trigger
router.post('/cleanup', asyncHandler(async (req, res) => {
const level = req.body?.level || 'standard';
if (!['standard', 'aggressive', 'logs-only'].includes(level)) {
return errorResponse(res, 'Invalid cleanup level. Use: standard, aggressive, or logs-only', 400);
}
log.info('disk', 'Manual cleanup triggered', { level, by: req.auth?.user || 'api' });
const result = await diskSpaceMonitor.performCleanup(level);
success(res, result);
}, 'disk-cleanup'));
return router;
};