feat: VM disk sandboxing — bounded virtual disk per platform
ARCHITECTURE: - Windows: dedicated WSL2 distro with fixed VHDX, Docker inside - macOS: Lima VM with fixed disk, Docker inside - Linux: sparse ext4 loopback image, Docker data-root inside NEW FILES: - vm-provisioner.js: core provisioning engine (create/start/destroy/export) - Disk presets: Minimal(10GB), Balanced(30GB), Power(100GB), Custom - Sparse images that grow on demand (start at ~0 bytes) - Full lifecycle: provision → deploy DashCaddy → destroy (clean removal) - Data export before uninstall for users who want to migrate - vm-ipc.js: Electron IPC handlers connecting wizard to provisioner - vm:provision, vm:destroy, vm:get-status, vm:export-data, vm:get-presets - disk-budget-step.js: wizard UI step with preset cards + custom slider - Real-time free space check against selected disk size - Plain English description of what each tier handles UPDATED: - caddyfile-generator.js: docker-compose now includes disk safety env vars (health retention, stats caps, memory limits) as defense-in-depth even inside the VM sandbox GUARANTEE: DashCaddy physically cannot exceed the storage budget. The OS enforces the limit at the disk/image level, not our code.
This commit is contained in:
@@ -7,6 +7,10 @@ const { DEFAULT_PORTS } = require('../shared/constants');
|
||||
*
|
||||
* Generates production-grade configs that match the patterns used by the
|
||||
* running DashCaddy deployment (CORS snippets, admin origins, PKI, etc.)
|
||||
*
|
||||
* DISK SAFETY: All generated configs include sensible defaults for storage
|
||||
* limits — health retention, stats caps, and memory limits — so a fresh
|
||||
* install will never silently fill a user's disk.
|
||||
*/
|
||||
class CaddyfileGenerator {
|
||||
/**
|
||||
@@ -279,19 +283,43 @@ class CaddyfileGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate docker-compose.yml for running the API server
|
||||
* Generate docker-compose.yml for running the API server.
|
||||
*
|
||||
* DISK SAFETY: Includes env vars for health retention, stats caps, and
|
||||
* memory limits derived from the disk budget the user selected during
|
||||
* install. These prevent the disk-explosion bugs seen in early versions.
|
||||
*
|
||||
* @param {string} installPath - Installation directory
|
||||
* @param {Object} options - Configuration options
|
||||
* @param {number} options.apiPort - API server port
|
||||
* @param {string} options.lanIP - Host LAN IP address
|
||||
* @param {string} options.tailscaleIP - Host Tailscale IP address
|
||||
* @param {string} options.domainMode - Domain mode (local, public, custom-tld)
|
||||
* @param {Object} [options.disk] - Disk budget settings
|
||||
* @param {number} [options.disk.healthRetentionDays=14] - Health history retention
|
||||
* @param {number} [options.disk.healthMaxEntries=500] - Max health entries per service
|
||||
* @param {number} [options.disk.healthCheckInterval=30000] - Health check interval (ms)
|
||||
* @param {number} [options.disk.statsMaxEntries=2000] - Max container stats entries
|
||||
* @param {number} [options.disk.auditMaxEntries=1000] - Max audit log entries
|
||||
* @param {number} [options.disk.backupLimitGB=10] - Backup storage limit
|
||||
* @param {string} [options.dockerDataPath] - Docker data root override
|
||||
* @param {number} [options.memoryLimitMB=1024] - Container memory limit
|
||||
*/
|
||||
generateDockerCompose(installPath, options = {}) {
|
||||
const apiPort = options.apiPort || DEFAULT_PORTS.API;
|
||||
const adminPort = DEFAULT_PORTS.CADDY_ADMIN;
|
||||
const p = this._p.bind(this);
|
||||
|
||||
// Disk budget settings with safe defaults
|
||||
const disk = options.disk || {};
|
||||
const healthRetentionDays = disk.healthRetentionDays || 14;
|
||||
const healthMaxEntries = disk.healthMaxEntries || 500;
|
||||
const healthCheckInterval = disk.healthCheckInterval || 30000;
|
||||
const statsMaxEntries = disk.statsMaxEntries || 2000;
|
||||
const auditMaxEntries = disk.auditMaxEntries || 1000;
|
||||
const backupLimitGB = disk.backupLimitGB || 10;
|
||||
const memoryLimitMB = options.memoryLimitMB || 1024;
|
||||
|
||||
// Core volume mounts
|
||||
let volumes = ` - ${p(installPath)}/Caddyfile:/caddyfile:rw
|
||||
- ${p(installPath)}/services.json:/app/services.json:rw
|
||||
@@ -308,12 +336,19 @@ class CaddyfileGenerator {
|
||||
volumes += `\n - ${p(installPath)}/certs/pki/authorities/local:/app/pki:ro`;
|
||||
}
|
||||
|
||||
// Environment variables
|
||||
// Environment variables — disk safety baked in
|
||||
let envVars = ` - CADDYFILE_PATH=/caddyfile
|
||||
- CADDY_ADMIN_URL=http://host.docker.internal:${adminPort}
|
||||
- ASSETS_PATH=/app/assets
|
||||
- CREDENTIALS_FILE=/app/credentials.json
|
||||
- NODE_ENV=production`;
|
||||
- NODE_ENV=production
|
||||
# --- Disk Safety ---
|
||||
- HEALTH_HISTORY_RETENTION=${healthRetentionDays}
|
||||
- HEALTH_MAX_ENTRIES=${healthMaxEntries}
|
||||
- HEALTH_CHECK_INTERVAL=${healthCheckInterval}
|
||||
- CONTAINER_STATS_MAX_ENTRIES=${statsMaxEntries}
|
||||
- AUDIT_MAX_ENTRIES=${auditMaxEntries}
|
||||
- BACKUP_MAX_STORAGE_BYTES=${backupLimitGB * 1024 * 1024 * 1024}`;
|
||||
|
||||
if (options.domainMode === 'custom-tld') {
|
||||
envVars += `\n - CA_CERT_PATH=/app/pki/root.crt`;
|
||||
@@ -339,6 +374,9 @@ ${envVars}
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
restart: unless-stopped
|
||||
# Memory limit prevents OOM during startup when all managers init
|
||||
mem_limit: ${memoryLimitMB}m
|
||||
memswap_limit: ${(memoryLimitMB * 2)}m
|
||||
`;
|
||||
|
||||
return dockerCompose;
|
||||
|
||||
Reference in New Issue
Block a user