From 4894e07469bf9f06c2ce8f17d4fa6d4876ae2bc1 Mon Sep 17 00:00:00 2001 From: Krystie Date: Thu, 13 Aug 2026 01:27:43 -0700 Subject: [PATCH] wire disk budget step + VM provisioning into installer wizard --- dashcaddy-installer/src/renderer/index.html | 1 + dashcaddy-installer/src/renderer/wizard.js | 72 +++++++++++++++++---- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/dashcaddy-installer/src/renderer/index.html b/dashcaddy-installer/src/renderer/index.html index 52d61b8..1a5689b 100644 --- a/dashcaddy-installer/src/renderer/index.html +++ b/dashcaddy-installer/src/renderer/index.html @@ -18,6 +18,7 @@ + diff --git a/dashcaddy-installer/src/renderer/wizard.js b/dashcaddy-installer/src/renderer/wizard.js index 4c20b71..13ae664 100644 --- a/dashcaddy-installer/src/renderer/wizard.js +++ b/dashcaddy-installer/src/renderer/wizard.js @@ -44,6 +44,12 @@ const state = { email: '', caName: 'DashCaddy Local CA' }, + // Disk budget / VM sandbox configuration + diskBudget: { + preset: 'balanced', + diskSizeGB: 30, + customSizeGB: 30 + }, // Detected network IPs network: { lanIP: '', @@ -91,6 +97,7 @@ const steps = [ { id: 'welcome', title: 'Welcome' }, { id: 'dependencies', title: 'Dependencies' }, { id: 'folder', title: 'Install Path' }, + { id: 'disk', title: 'Storage' }, { id: 'tier', title: 'Tier' }, { id: 'access', title: 'Access' }, { id: 'dns', title: 'DNS' }, @@ -179,7 +186,7 @@ function setupEventListeners() { state.result.dashboardUrl = data.dashboardUrl; state.result.installPath = data.installPath; state.result.health = data.health || null; - state.currentStep = 8; // Move to complete step + state.currentStep = 9; // Move to complete step render(); }); @@ -207,6 +214,21 @@ function setupEventListeners() { state.uninstall.error = data.error; render(); }); + + // VM provisioning progress / error listeners + window.electronAPI.onVMProgress((data) => { + if (state.installation.status === 'running') { + state.installation.progress = Math.min(data.progress || 0, 5); + state.installation.currentTask = data.task || 'Provisioning sandboxed virtual disk...'; + render(); + } + }); + + window.electronAPI.onVMError((data) => { + state.installation.status = 'error'; + state.installation.error = data.error || 'VM provisioning failed'; + render(); + }); } // Navigation @@ -219,7 +241,7 @@ function nextStep() { case 1: // Dependencies checkDependencies(); break; - case 7: // Installation + case 8: // Installation startInstallation(); break; } @@ -420,6 +442,33 @@ async function startInstallation() { render(); try { + // ── Provision sandboxed VM disk before installation ───────── + if (state.diskBudget && state.diskBudget.diskSizeGB) { + state.installation.currentTask = 'Provisioning sandboxed virtual disk...'; + state.installation.progress = 1; + render(); + + const domain = state.domainMode === 'public' + ? state.domain.publicDomain + : state.domainMode === 'custom-tld' + ? state.domain.tld + : null; + + const vmResult = await window.electronAPI.vmProvision({ + diskSizeGB: state.diskBudget.diskSizeGB, + installPath: state.paths.install, + apiPort: state.branding.apiPort, + domain + }); + + if (!vmResult || !vmResult.success) { + throw new Error((vmResult && vmResult.error) || 'VM provisioning failed'); + } + state.installation.completedTasks.push('Virtual disk provisioned'); + state.installation.progress = 5; + render(); + } + await window.electronAPI.runInstallation({ installPath: state.paths.install, dockerDataPath: state.paths.dockerData, @@ -511,12 +560,13 @@ function renderCurrentStep() { case 0: return renderWelcome(); case 1: return renderDependencies(); case 2: return renderFolderSelection(); - case 3: return renderTierSelection(); - case 4: return renderAccessMode(); - case 5: return renderDNSConfiguration(); - case 6: return renderDashboardSetup(); - case 7: return renderInstallation(); - case 8: return renderComplete(); + case 3: return renderDiskBudgetStep(); + case 4: return renderTierSelection(); + case 5: return renderAccessMode(); + case 6: return renderDNSConfiguration(); + case 7: return renderDashboardSetup(); + case 8: return renderInstallation(); + case 9: return renderComplete(); default: return ''; } } @@ -1125,7 +1175,7 @@ function renderComplete() { function renderFooter() { const isFirst = state.currentStep === 0; const isLast = state.currentStep === steps.length - 1; - const isInstalling = state.currentStep === 7 && state.installation.status === 'running'; + const isInstalling = state.currentStep === 8 && state.installation.status === 'running'; // Determine if user can proceed let canProceed = true; @@ -1136,7 +1186,7 @@ function renderFooter() { case 2: // Folder canProceed = !!state.paths.install; break; - case 4: // Access mode + case 5: // Access mode if (state.domainMode === 'public') { canProceed = !!state.domain.publicDomain && !!state.domain.email; } else if (state.domainMode === 'custom-tld') { @@ -1165,7 +1215,7 @@ function renderFooter() { // Button text let nextLabel = 'Next'; - if (state.currentStep === 6) nextLabel = 'Install'; + if (state.currentStep === 7) nextLabel = 'Install'; return `