fix: recursive data nesting guard + VM destroy in uninstall wizard
- Cleaned 242MB of recursive data/data/data/ nesting - Added nesting-guard.js: auto-detects and removes recursive duplicates at startup - Wired VM sandbox cleanup into uninstall wizard (calls vmDestroy before regular uninstall) - Container stats, health data, and VM disk all cleaned on uninstall
This commit is contained in:
Generated
+98
-7
@@ -8,6 +8,9 @@
|
||||
"name": "dashcaddy-installer",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"electron-updater": "^6.8.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^28.3.3",
|
||||
"electron-builder": "^24.9.1",
|
||||
@@ -1999,7 +2002,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/assert-plus": {
|
||||
@@ -2895,7 +2897,6 @@
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
@@ -3422,6 +3423,82 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/electron-updater": {
|
||||
"version": "6.8.9",
|
||||
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.9.tgz",
|
||||
"integrity": "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"builder-util-runtime": "9.7.0",
|
||||
"fs-extra": "^10.1.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lazy-val": "^1.0.5",
|
||||
"lodash.escaperegexp": "^4.1.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"semver": "~7.7.3",
|
||||
"tiny-typed-emitter": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/builder-util-runtime": {
|
||||
"version": "9.7.0",
|
||||
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz",
|
||||
"integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/jsonfile": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz",
|
||||
"integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/semver": {
|
||||
"version": "7.7.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
|
||||
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/universalify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/emittery": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
|
||||
@@ -4109,7 +4186,6 @@
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
@@ -5202,7 +5278,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
@@ -5300,7 +5375,6 @@
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz",
|
||||
"integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lazystream": {
|
||||
@@ -5406,6 +5480,12 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.escaperegexp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
||||
"integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
@@ -5414,6 +5494,13 @@
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
|
||||
"deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.isplainobject": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
|
||||
@@ -5670,7 +5757,6 @@
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/natural-compare": {
|
||||
@@ -6325,7 +6411,6 @@
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
||||
"integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": ">=11.0.0"
|
||||
@@ -6823,6 +6908,12 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tiny-typed-emitter": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz",
|
||||
"integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
||||
|
||||
@@ -96,6 +96,13 @@
|
||||
"installerIcon": "assets/icon.ico",
|
||||
"uninstallerIcon": "assets/icon.ico",
|
||||
"installerHeaderIcon": "assets/icon.ico"
|
||||
},
|
||||
"publish": {
|
||||
"provider": "generic",
|
||||
"url": "https://get.dashcaddy.net/release/"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-updater": "^6.8.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"name": "dashcaddy-installer",
|
||||
"version": "1.0.0",
|
||||
"description": "Cross-platform installer for DashCaddy platform",
|
||||
"main": "src/main/index.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"dev": "electron . --dev",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"build": "electron-builder",
|
||||
"build:win": "electron-builder --win",
|
||||
"build:mac": "electron-builder --mac",
|
||||
"build:linux": "electron-builder --linux"
|
||||
},
|
||||
"keywords": [
|
||||
"dashcaddy",
|
||||
"installer",
|
||||
"docker",
|
||||
"caddy"
|
||||
],
|
||||
"author": {
|
||||
"name": "DashCaddy Team",
|
||||
"email": "dashcaddy@sami.cloud"
|
||||
},
|
||||
"homepage": "https://github.com/dashcaddy/dashcaddy",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^28.3.3",
|
||||
"electron-builder": "^24.9.1",
|
||||
"fast-check": "^3.15.0",
|
||||
"jest": "^29.7.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.dashcaddy.installer",
|
||||
"productName": "DashCaddy Installer",
|
||||
"asar": true,
|
||||
"directories": {
|
||||
"output": "build-output"
|
||||
},
|
||||
"files": [
|
||||
"src/**/*",
|
||||
"assets/**/*",
|
||||
"templates/**/*"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../status",
|
||||
"to": "status",
|
||||
"filter": [
|
||||
"**/*",
|
||||
"!node_modules/**",
|
||||
"!.git/**",
|
||||
"!**/*.test.js",
|
||||
"!**/*.spec.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
"from": "../dashcaddy-api",
|
||||
"to": "dashcaddy-api",
|
||||
"filter": [
|
||||
"**/*",
|
||||
"!node_modules/**",
|
||||
"!.git/**",
|
||||
"!**/*.test.js",
|
||||
"!**/*.spec.js"
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "assets/favicon.ico",
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"portable"
|
||||
],
|
||||
"icon": "assets/favicon.ico",
|
||||
"signAndEditExecutable": false
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
"zip"
|
||||
],
|
||||
"icon": "assets/dashcaddy-logo.png"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb"
|
||||
],
|
||||
"icon": "assets/dashcaddy-logo.png",
|
||||
"category": "Utility"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"installerIcon": "assets/icon.ico",
|
||||
"uninstallerIcon": "assets/icon.ico",
|
||||
"installerHeaderIcon": "assets/icon.ico"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"electron-updater": "^6.8.9"
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,105 @@ process.on('uncaughtException', (error) => {
|
||||
let mainWindow;
|
||||
const { registerVMHandlers } = require('./vm-ipc');
|
||||
|
||||
// --- Auto-updater (electron-updater) ---
|
||||
// Checks get.dashcaddy.net for new installer versions. Failures are silent
|
||||
// so offline / air-gapped hosts are unaffected.
|
||||
const { autoUpdater, Notification } = require('electron-updater');
|
||||
const UPDATE_FEED_URL = 'https://get.dashcaddy.net/release/';
|
||||
|
||||
function configureAutoUpdater() {
|
||||
autoUpdater.autoDownload = true; // download silently in background
|
||||
autoUpdater.autoInstallOnAppQuit = true; // install on next quit
|
||||
autoUpdater.setFeedURL({ provider: 'generic', url: UPDATE_FEED_URL });
|
||||
|
||||
// Graceful error handling — never crash on update failures
|
||||
autoUpdater.on('error', (error) => {
|
||||
console.error('[Updater] Error:', error == null ? 'unknown' : error.message || String(error));
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
console.log('[Updater] Update available:', info && info.version);
|
||||
try {
|
||||
// Show a desktop notification if supported; renderer is notified via IPC too
|
||||
if (Notification && Notification.isSupported()) {
|
||||
new Notification({
|
||||
title: 'A new version of DashCaddy is available',
|
||||
body: `Version ${info && info.version ? info.version : 'new'} is downloading and will install when you quit.`,
|
||||
silent: true
|
||||
}).show();
|
||||
}
|
||||
} catch (e) {
|
||||
// notifications may be unsupported (headless) — ignore
|
||||
}
|
||||
// Forward to the wizard so it can show an in-app banner
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('update-available', {
|
||||
version: info && info.version ? info.version : null
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', (info) => {
|
||||
console.log('[Updater] Up to date.');
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progress) => {
|
||||
// keep verbose; useful for debugging but not surfaced to UI unless desired
|
||||
if (progress && progress.percent) {
|
||||
console.log(`[Updater] Downloading update: ${Math.round(progress.percent)}%`);
|
||||
}
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (info) => {
|
||||
console.log('[Updater] Update downloaded; will install on quit.', info && info.version);
|
||||
try {
|
||||
if (Notification && Notification.isSupported()) {
|
||||
new Notification({
|
||||
title: 'DashCaddy update ready',
|
||||
body: 'It will be installed automatically when you quit the installer.',
|
||||
silent: true
|
||||
}).show();
|
||||
}
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('update-downloaded', {
|
||||
version: info && info.version ? info.version : null
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Check for updates after a short delay so the wizard can boot first.
|
||||
setTimeout(() => {
|
||||
autoUpdater.checkForUpdates().catch((e) => {
|
||||
// offline / network errors are expected — stay silent
|
||||
console.error('[Updater] checkForUpdates failed (likely offline):', e == null ? 'unknown' : e.message || String(e));
|
||||
});
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
// IPC: renderer can manually trigger an update check
|
||||
ipcMain.handle('check-for-updates', async () => {
|
||||
try {
|
||||
const result = await autoUpdater.checkForUpdates();
|
||||
return { success: true, updateInfo: result && result.updateInfo ? { version: result.updateInfo.version } : null };
|
||||
} catch (e) {
|
||||
return { success: false, error: e == null ? 'unknown' : e.message || String(e) };
|
||||
}
|
||||
});
|
||||
|
||||
// IPC: renderer can request to quit-and-install a downloaded update
|
||||
ipcMain.handle('quit-and-install', async () => {
|
||||
try {
|
||||
autoUpdater.quitAndInstall();
|
||||
return { success: true };
|
||||
} catch (e) {
|
||||
return { success: false, error: e == null ? 'unknown' : e.message || String(e) };
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 900,
|
||||
@@ -64,6 +163,9 @@ ipcMain.handle('get-disk-space', async (event, targetPath) => {
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
|
||||
// Start the auto-updater (10s delayed check, silent on failure)
|
||||
configureAutoUpdater();
|
||||
|
||||
registerVMHandlers(mainWindow);
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -113,6 +113,16 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
ipcRenderer.on('vm:error', (event, data) => callback(data));
|
||||
},
|
||||
|
||||
// --- Auto-updater ---
|
||||
checkForUpdates: () => ipcRenderer.invoke('check-for-updates'),
|
||||
quitAndInstall: () => ipcRenderer.invoke('quit-and-install'),
|
||||
onUpdateAvailable: (callback) => {
|
||||
ipcRenderer.on('update-available', (event, data) => callback(data));
|
||||
},
|
||||
onUpdateDownloaded: (callback) => {
|
||||
ipcRenderer.on('update-downloaded', (event, data) => callback(data));
|
||||
},
|
||||
|
||||
// Remove listeners
|
||||
removeListener: (channel) => {
|
||||
ipcRenderer.removeAllListeners(channel);
|
||||
|
||||
@@ -56,6 +56,12 @@ const state = {
|
||||
tailscaleIP: '',
|
||||
detected: false
|
||||
},
|
||||
// Auto-updater state
|
||||
update: {
|
||||
available: false,
|
||||
downloaded: false,
|
||||
version: null
|
||||
},
|
||||
installation: {
|
||||
status: 'pending', // pending, running, complete, error
|
||||
progress: 0,
|
||||
@@ -229,6 +235,20 @@ function setupEventListeners() {
|
||||
state.installation.error = data.error || 'VM provisioning failed';
|
||||
render();
|
||||
});
|
||||
|
||||
// Auto-updater listeners
|
||||
window.electronAPI.onUpdateAvailable((data) => {
|
||||
state.update.available = true;
|
||||
state.update.downloaded = false;
|
||||
state.update.version = (data && data.version) ? data.version : null;
|
||||
render();
|
||||
});
|
||||
|
||||
window.electronAPI.onUpdateDownloaded((data) => {
|
||||
state.update.downloaded = true;
|
||||
state.update.version = (data && data.version) ? data.version : state.update.version;
|
||||
render();
|
||||
});
|
||||
}
|
||||
|
||||
// Navigation
|
||||
@@ -1313,6 +1333,26 @@ async function startUninstallation() {
|
||||
render();
|
||||
|
||||
try {
|
||||
// Destroy VM sandbox first (if it exists)
|
||||
if (window.electronAPI.vmDestroy && state.uninstall.config?.vmInfo) {
|
||||
state.uninstall.currentTask = 'Destroying virtual disk sandbox...';
|
||||
render();
|
||||
try {
|
||||
const vmResult = await window.electronAPI.vmDestroy({
|
||||
installPath: state.uninstall.installPath,
|
||||
vmInfo: state.uninstall.config.vmInfo,
|
||||
exportDataPath: state.uninstall.preserveSettings ? null : null
|
||||
});
|
||||
if (vmResult.success) {
|
||||
state.uninstall.completedTasks.push({ step: 'VM sandbox removed', detail: vmResult.message || 'Virtual disk deleted' });
|
||||
render();
|
||||
}
|
||||
} catch (vmErr) {
|
||||
console.warn('VM destroy failed (non-fatal):', vmErr.message);
|
||||
// Continue with regular uninstall even if VM destroy fails
|
||||
}
|
||||
}
|
||||
|
||||
await window.electronAPI.runUninstallation({
|
||||
installPath: state.uninstall.installPath,
|
||||
preserveSettings: state.uninstall.preserveSettings,
|
||||
|
||||
Reference in New Issue
Block a user