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.
114 lines
5.2 KiB
JavaScript
114 lines
5.2 KiB
JavaScript
/**
|
|
* VM Disk Budget Step — rendered inside the Electron wizard.
|
|
* Shows disk size presets, a custom slider, and real-time space check.
|
|
* Add to wizard.js as a new render step between 'folder' and 'tier'.
|
|
*
|
|
* Exported function: renderDiskBudgetStep()
|
|
* State updates: state.diskBudget.preset, state.diskBudget.customSizeGB
|
|
*/
|
|
|
|
function renderDiskBudgetStep() {
|
|
const presets = [
|
|
{ id: 'minimal', icon: '💽', sizeGB: 10, label: 'Minimal', desc: 'DashCaddy only, a few small apps' },
|
|
{ id: 'balanced', icon: '💿', sizeGB: 30, label: 'Balanced', desc: 'DashCaddy + media tools + containers' },
|
|
{ id: 'power', icon: '🧊', sizeGB: 100, label: 'Power', desc: 'DashCaddy + heavy apps + lots of containers' },
|
|
{ id: 'custom', icon: '⚙️', sizeGB: 0, label: 'Custom', desc: 'Pick your own size' },
|
|
];
|
|
|
|
const selectedPreset = state.diskBudget?.preset || 'balanced';
|
|
const selectedSize = state.diskBudget?.customSizeGB || presets.find(p => p.id === selectedPreset)?.sizeGB || 30;
|
|
|
|
return `
|
|
<div>
|
|
<h2>Storage Budget</h2>
|
|
<p>DashCaddy creates a <strong>sandboxed virtual disk</strong> for all its data.
|
|
It can never exceed this limit — your main drive stays safe.</p>
|
|
<p class="hint" style="margin-bottom: 20px;">
|
|
💡 The disk starts nearly empty and only grows as you add apps and data.
|
|
Deleting DashCaddy removes the entire disk instantly.
|
|
</p>
|
|
|
|
<div class="disk-presets" style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 20px;">
|
|
${presets.map(p => `
|
|
<div class="disk-preset-card ${selectedPreset === p.id ? 'selected' : ''}"
|
|
onclick="selectDiskPreset('${p.id}', ${p.sizeGB})"
|
|
style="padding: 16px; border: 2px solid ${selectedPreset === p.id ? '#6366f1' : 'var(--border, #333)'}; border-radius: 10px; cursor: pointer; transition: all 0.2s; ${selectedPreset === p.id ? 'background: rgba(99, 102, 241, 0.1);' : ''}">
|
|
<div style="font-size: 2rem; margin-bottom: 8px;">${p.icon}</div>
|
|
<div style="font-weight: 600; font-size: 1.05rem;">${p.label}</div>
|
|
<div style="font-size: 0.85rem; color: var(--muted, #888); margin-top: 4px;">
|
|
${p.sizeGB > 0 ? p.sizeGB + 'GB' : 'Custom'} — ${p.desc}
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
|
|
${selectedPreset === 'custom' ? `
|
|
<div class="folder-input" style="margin-bottom: 16px;">
|
|
<label>Custom Disk Size</label>
|
|
<div class="input-row" style="display: flex; align-items: center; gap: 12px;">
|
|
<input type="range" id="disk-slider"
|
|
min="5" max="500" step="5"
|
|
value="${selectedSize}"
|
|
oninput="updateDiskSize(this.value)"
|
|
style="flex: 1;">
|
|
<span id="disk-size-display" style="font-size: 1.3rem; font-weight: 700; min-width: 80px; text-align: right;">
|
|
${selectedSize}GB
|
|
</span>
|
|
</div>
|
|
<p class="hint">Min 5GB, Max 500GB. DashCaddy uses a sparse image — it only consumes real disk space as data fills.</p>
|
|
</div>
|
|
` : `
|
|
<div style="padding: 12px 16px; background: rgba(99, 102, 241, 0.08); border-radius: 8px; border: 1px solid rgba(99, 102, 241, 0.2); margin-bottom: 16px;">
|
|
<strong>${selectedSize}GB</strong> virtual disk will be created.
|
|
The sandbox isolates Docker, all containers, and all DashCaddy data inside it.
|
|
</div>
|
|
`}
|
|
|
|
<div id="disk-space-check" style="margin-top: 12px;"></div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// State management helpers — call from wizard.js
|
|
function selectDiskPreset(presetId, sizeGB) {
|
|
if (!state.diskBudget) state.diskBudget = {};
|
|
state.diskBudget.preset = presetId;
|
|
if (presetId !== 'custom') {
|
|
state.diskBudget.diskSizeGB = sizeGB;
|
|
}
|
|
checkDiskSpace(sizeGB);
|
|
render(); // re-render the step
|
|
}
|
|
|
|
function updateDiskSize(val) {
|
|
const sizeGB = parseInt(val);
|
|
if (!state.diskBudget) state.diskBudget = {};
|
|
state.diskBudget.diskSizeGB = sizeGB;
|
|
state.diskBudget.customSizeGB = sizeGB;
|
|
document.getElementById('disk-size-display').textContent = sizeGB + 'GB';
|
|
checkDiskSpace(sizeGB);
|
|
}
|
|
|
|
async function checkDiskSpace(sizeGB) {
|
|
const el = document.getElementById('disk-space-check');
|
|
if (!el) return;
|
|
|
|
try {
|
|
const info = await window.electronAPI.getDiskSpace(state.paths.install || '');
|
|
const freeGB = Math.round(info.free / 1024 / 1024 / 1024);
|
|
const neededGB = sizeGB + 2; // 2GB buffer for DashCaddy itself
|
|
|
|
if (freeGB < neededGB) {
|
|
el.innerHTML = `<div style="padding: 10px 14px; background: rgba(239, 68, 68, 0.1); border-radius: 6px; border: 1px solid rgba(239, 68, 68, 0.3); color: #f87171; font-size: 0.85rem;">
|
|
⚠️ Not enough free space. You have ${freeGB}GB free, but need ${neededGB}GB.
|
|
</div>`;
|
|
} else {
|
|
el.innerHTML = `<div style="padding: 10px 14px; background: rgba(34, 197, 94, 0.1); border-radius: 6px; border: 1px solid rgba(34, 197, 94, 0.2); color: #4ade80; font-size: 0.85rem;">
|
|
✓ You have ${freeGB}GB free — plenty of room for a ${sizeGB}GB disk.
|
|
</div>`;
|
|
}
|
|
} catch {
|
|
el.innerHTML = '';
|
|
}
|
|
}
|