feat: VM disk sandboxing with full VM isolation
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s

- Add VM provisioning module (vm-provisioner.js) with 3 platform strategies:
  * Windows: WSL2 distro with fixed VHDX
  * macOS: Lima VM with fixed disk
  * Linux: loopback ext4 image
- Add IPC handlers (vm-ipc.js) for Electron wizard integration
- Add disk budget wizard step (disk-budget-step.js) with presets
- Wire VM handlers into main process (index.js)
- Add preload bridges for VM operations
- Update install.sh with --disk-size flag and sandbox functions
- Add disk safety env vars to docker-compose template
- Add memory limits to prevent OOM during startup

Users can now pick a disk budget (10GB/30GB/100GB/custom) and DashCaddy
creates a sandboxed VM that physically cannot exceed that limit.
Uninstall cleanly removes the entire VM/disk with zero leakage.
This commit is contained in:
Krystie
2026-08-12 23:47:22 -07:00
parent cd3d0cd8ff
commit 2a5b1736b8
5 changed files with 184 additions and 35 deletions
+15
View File
@@ -10,6 +10,7 @@ process.on('uncaughtException', (error) => {
});
let mainWindow;
const { registerVMHandlers } = require('./vm-ipc');
function createWindow() {
mainWindow = new BrowserWindow({
@@ -47,9 +48,23 @@ function createWindow() {
}
// App lifecycle handlers
// --- Disk space check (for VM disk budget step) ---
ipcMain.handle('get-disk-space', async (event, targetPath) => {
try {
const stats = await require('fs').promises.statfs(targetPath || '/');
return {
free: stats.bavail * stats.bsize,
total: stats.blocks * stats.bsize,
};
} catch (e) {
return { free: 0, total: 0, error: e.message };
}
});
app.whenReady().then(() => {
createWindow();
registerVMHandlers(mainWindow);
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
+18
View File
@@ -95,6 +95,24 @@ contextBridge.exposeInMainWorld('electronAPI', {
ipcRenderer.on('uninstall-error', (event, data) => callback(data));
},
// --- VM Disk Sandbox ---
vmGetPresets: () => ipcRenderer.invoke('vm:get-presets'),
vmGetStatus: () => ipcRenderer.invoke('vm:get-status'),
vmProvision: (opts) => ipcRenderer.invoke('vm:provision', opts),
vmDestroy: (opts) => ipcRenderer.invoke('vm:destroy', opts),
vmExportData: (opts) => ipcRenderer.invoke('vm:export-data', opts),
getDiskSpace: (path) => ipcRenderer.invoke('get-disk-space', path),
onVMProgress: (callback) => {
ipcRenderer.on('vm:progress', (event, data) => callback(data));
},
onVMComplete: (callback) => {
ipcRenderer.on('vm:complete', (event, data) => callback(data));
},
onVMError: (callback) => {
ipcRenderer.on('vm:error', (event, data) => callback(data));
},
// Remove listeners
removeListener: (channel) => {
ipcRenderer.removeAllListeners(channel);