From 47db668ccc46fde9740018c9825661f084e7fd03 Mon Sep 17 00:00:00 2001 From: ARIA Date: Fri, 3 Jul 2026 02:56:23 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Installer=20mit=20automatischer=20VirtV?= =?UTF-8?q?iewer-Abh=C3=A4ngigkeit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Windows NSIS: lädt VirtViewer MSI herunter und installiert es während der App-Installation (via PowerShell + msiexec) - Linux .deb: Depends: virt-viewer — apt zieht es automatisch rein - Linux AppImage: Startup-Check mit pkexec-Dialog falls virt-viewer fehlt - BUILD.md aktualisiert: Installer-Verhalten dokumentiert --- BUILD.md | 66 ++++++++++++++++++++++++++++------------- build/installer.nsh | 55 ++++++++++++++++++++++++++++++++++ package.json | 10 +++++++ src/main.js | 72 ++++++++++++++++++++++++++++++++++++++------- 4 files changed, 172 insertions(+), 31 deletions(-) create mode 100644 build/installer.nsh diff --git a/BUILD.md b/BUILD.md index 4f284d6..8ae18a4 100644 --- a/BUILD.md +++ b/BUILD.md @@ -7,35 +7,62 @@ node >= 18 npm >= 9 ``` -Für Linux-Builds auf Linux-Host, für Windows-Builds entweder Windows oder Wine (cross-build eingeschränkt — lieber nativ bauen). +Für Linux-Builds auf Linux-Host. Windows-Builds entweder nativ auf Windows oder via Wine (cross-build eingeschränkt — nativ empfohlen). -## Entwicklung +--- + +## Entwicklung (ohne Build) ```bash npm install npm start ``` +--- + ## Produktions-Build -### Linux (AppImage + .deb) +### Linux — AppImage + .deb + ```bash npm run build:linux -# Output: dist/Proxmox SPICE Client-1.0.0.AppImage -# dist/proxmox-spice-client_1.0.0_amd64.deb +# Output: +# dist/Proxmox SPICE Client-1.0.0.AppImage ← portable, kein Install nötig +# dist/proxmox-spice-client_1.0.0_amd64.deb ← Paket für apt ``` -### Windows (.exe Installer) +**Abhängigkeiten:** +- **AppImage**: Selbst-enthalten (Electron inklusive). Beim ersten Start wird geprüft ob `virt-viewer` installiert ist. Falls nicht: Dialog mit automatischer Installation via `pkexec apt install virt-viewer`. +- **.deb**: Deklariert `virt-viewer` als `Depends` — apt installiert es automatisch mit: + ```bash + sudo apt install ./proxmox-spice-client_1.0.0_amd64.deb + ``` + +### Windows — Setup.exe (NSIS) + ```bash +# Am besten nativ auf Windows bauen: npm run build:win -# Output: dist/Proxmox SPICE Client Setup 1.0.0.exe +# Output: +# dist/Proxmox SPICE Client Setup 1.0.0.exe ``` +**Abhängigkeiten:** +Der NSIS-Installer prüft automatisch ob VirtViewer installiert ist. Falls nicht: +1. Fragt den Benutzer ob VirtViewer jetzt installiert werden soll +2. Lädt das MSI-Paket von virt-manager.org herunter (~30 MB, benötigt Internet) +3. Installiert VirtViewer still im Hintergrund + +Kein manueller Schritt nötig — der Installer kümmert sich um alles. + ### Beides auf einmal + ```bash npm run build:linux && npm run build:win ``` +--- + ## Icons Dateien in `assets/` müssen vor dem Build vorhanden sein: @@ -52,20 +79,7 @@ sudo apt install imagemagick convert assets/icon.png -resize 256x256 assets/icon.ico ``` -**PNG → ICNS (macOS):** -```bash -# mit electron-icon-maker: -npx electron-icon-maker --input=assets/icon.png --output=assets/ -``` - -## Abhängigkeit: virt-viewer - -Der Client startet `remote-viewer` (Teil von virt-viewer). Muss auf dem Zielrechner installiert sein: - -- **Linux:** `sudo apt install virt-viewer` -- **Windows:** https://virt-manager.org/download/ → "Windows MSI" - -Die App zeigt beim ersten Verbindungsversuch einen Dialog wenn `remote-viewer` nicht gefunden wird. +--- ## Verzeichnisstruktur @@ -85,6 +99,8 @@ proxmox-spice-client/ │ ├── icon.png # Quell-Icon (mind. 512×512) │ ├── icon.ico # Windows │ └── icon.icns # macOS (optional) +├── build/ +│ └── installer.nsh # Benutzerdefiniertes NSIS-Script (VirtViewer-Install) ├── daten/ # persistente Einstellungen (Docker-Mount) ├── Dockerfile ├── docker-compose.yml @@ -92,3 +108,11 @@ proxmox-spice-client/ ├── package.json └── BUILD.md ``` + +--- + +## Hinweise + +- `npm run build:win` auf Linux erfordert Wine (`sudo apt install wine`) für den cross-build. Nativ auf Windows ist stabiler. +- Der NSIS-Installer lädt VirtViewer zur Installationszeit herunter (benötigt Internetverbindung). Wer offline-fähig sein will: VirtViewer-MSI vorher separat verteilen und manuell installieren. +- USB-Redirect, Vollbild (`Shift+F11`), Multi-Monitor: alles via VirtViewer nativ — kein Extra-Setup. diff --git a/build/installer.nsh b/build/installer.nsh new file mode 100644 index 0000000..2d97070 --- /dev/null +++ b/build/installer.nsh @@ -0,0 +1,55 @@ +; Proxmox SPICE Client — Custom NSIS Install Script +; Prüft ob virt-viewer installiert ist und installiert es bei Bedarf. + +!macro customInstall + ; Prüfen ob remote-viewer.exe bereits vorhanden ist + IfFileExists "$PROGRAMFILES64\VirtViewer\bin\remote-viewer.exe" vv_found + IfFileExists "$PROGRAMFILES\VirtViewer\bin\remote-viewer.exe" vv_found + + ; Nicht gefunden — Benutzer fragen + MessageBox MB_YESNO|MB_ICONQUESTION \ + "VirtViewer wurde auf diesem System nicht gefunden.$\n$\n\ +VirtViewer wird für SPICE-Verbindungen zu Proxmox-VMs benötigt.$\n\ +Ohne VirtViewer können keine VMs geöffnet werden.$\n$\n\ +Jetzt automatisch herunterladen und installieren (ca. 30 MB)?" \ + IDYES vv_download IDNO vv_skip + + vv_download: + DetailPrint "Lade VirtViewer herunter..." + nsExec::ExecToLog 'powershell.exe -NonInteractive -Command \ + "Invoke-WebRequest \ + -Uri \"https://virt-manager.org/download/virt-viewer-x64.msi\" \ + -OutFile \"$TEMP\virt-viewer-setup.msi\" \ + -UseBasicParsing"' + Pop $0 + ${If} $0 != 0 + MessageBox MB_OK|MB_ICONEXCLAMATION \ + "Download fehlgeschlagen (Fehlercode: $0).$\n$\n\ +Bitte VirtViewer nach der Installation manuell installieren:$\n\ +https://virt-manager.org/download/" + Goto vv_skip + ${EndIf} + + DetailPrint "Installiere VirtViewer (bitte warten)..." + ExecWait 'msiexec /i "$TEMP\virt-viewer-setup.msi" /quiet /norestart' $0 + Delete "$TEMP\virt-viewer-setup.msi" + + ${If} $0 == 0 + DetailPrint "VirtViewer erfolgreich installiert." + ${Else} + MessageBox MB_OK|MB_ICONEXCLAMATION \ + "VirtViewer-Installation schlug fehl (Code: $0).$\n$\n\ +Bitte nach Abschluss manuell installieren:$\n\ +https://virt-manager.org/download/" + ${EndIf} + Goto vv_skip + + vv_found: + DetailPrint "VirtViewer bereits installiert — OK." + + vv_skip: +!macroend + +!macro customUnInstall + ; Nichts extra — VirtViewer bleibt installiert +!macroend diff --git a/package.json b/package.json index 4ccd4ef..10a5d84 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,20 @@ "category": "Network", "icon": "assets/icon.png" }, + "deb": { + "depends": ["libgtk-3-0", "libnotify4", "libnss3", "libxss1", "libxtst6", "xdg-utils", "libatspi2.0-0", "libuuid1", "virt-viewer"] + }, "win": { "target": ["nsis"], "icon": "assets/icon.ico" }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "installerHeaderIcon": "assets/icon.ico", + "include": "build/installer.nsh", + "warningsAsErrors": false + }, "mac": { "target": ["dmg"], "icon": "assets/icon.icns" diff --git a/src/main.js b/src/main.js index 7d4f24a..4a82036 100644 --- a/src/main.js +++ b/src/main.js @@ -2,7 +2,7 @@ const { app, BrowserWindow, ipcMain, dialog } = require('electron'); const path = require('path'); const fs = require('fs'); const os = require('os'); -const { spawn } = require('child_process'); +const { spawn, spawnSync } = require('child_process'); const Store = require('electron-store'); const ProxmoxClient = require('./proxmox'); @@ -38,6 +38,7 @@ function createWindow(file, width, height, resizable = false) { app.whenReady().then(() => { createWindow('login.html', 460, 500, false); + checkDependencies(); app.on('activate', () => { if (!mainWindow) createWindow('login.html', 460, 500, false); }); @@ -47,6 +48,63 @@ app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); +// ---------- Dependency check (AppImage / manual install on Linux) ---------- + +function checkDependencies() { + // On Windows the NSIS installer handles virt-viewer — no runtime check needed. + // On Linux (AppImage or manual install) we check here so the user knows before + // trying to connect for the first time. + if (process.platform !== 'linux') return; + + const candidates = ['remote-viewer', 'virt-viewer']; + const found = candidates.some(bin => { + const r = spawnSync('which', [bin], { encoding: 'utf8' }); + return r.status === 0; + }); + + if (!found) { + setImmediate(() => { + const choice = dialog.showMessageBoxSync(mainWindow, { + type: 'warning', + title: 'Abhängigkeit fehlt', + message: 'VirtViewer nicht gefunden', + detail: + 'VirtViewer (remote-viewer) wird für SPICE-Verbindungen benötigt und ist auf diesem System nicht installiert.\n\n' + + 'Installieren mit:\n sudo apt install virt-viewer\n\n' + + 'Soll die App versuchen, VirtViewer jetzt automatisch zu installieren?', + buttons: ['Jetzt installieren', 'Später (manuell)'], + defaultId: 0, + cancelId: 1, + }); + + if (choice === 0) { + const installer = spawn( + 'pkexec', + ['apt', 'install', '-y', 'virt-viewer'], + { detached: false, stdio: 'ignore' } + ); + installer.on('close', code => { + if (code === 0) { + dialog.showMessageBox(mainWindow, { + type: 'info', + title: 'VirtViewer installiert', + message: 'VirtViewer wurde erfolgreich installiert.', + buttons: ['OK'], + }); + } else { + dialog.showMessageBox(mainWindow, { + type: 'error', + title: 'Installation fehlgeschlagen', + message: `Installation schlug fehl (Code ${code}).\nBitte manuell ausführen:\n sudo apt install virt-viewer`, + buttons: ['OK'], + }); + } + }); + } + }); + } +} + // ---------- IPC: credentials ---------- ipcMain.handle('credentials:load', () => store.get('credentials', null)); @@ -65,7 +123,6 @@ ipcMain.handle('proxmox:login', async (_e, { host, username, password }) => { try { client = new ProxmoxClient(host); await client.login(username, password); - // Switch to VM list view createWindow('vms.html', 680, 520, true); return { success: true }; } catch (err) { @@ -103,7 +160,6 @@ ipcMain.handle('proxmox:connect', async (_e, { node, vmid }) => { function buildVVFile(params, vmid) { const lines = ['[virt-viewer]', `type=${params.type || 'spice'}`]; - // Proxmox spiceproxy returns 'proxy' as the SPICE host const host = params.proxy || params.host || client.host.split(':')[0]; lines.push(`host=${host}`); @@ -111,7 +167,6 @@ function buildVVFile(params, vmid) { if (params.port) lines.push(`port=${params.port}`); if (params.password) lines.push(`password=${params.password}`); - // Write CA cert to temp file if provided if (params.ca) { const caPath = path.join(os.tmpdir(), 'proxmox-spice-ca.pem'); fs.writeFileSync(caPath, params.ca); @@ -119,14 +174,13 @@ function buildVVFile(params, vmid) { } if (params['host-subject']) lines.push(`host-subject=${params['host-subject']}`); - // UX + USB lines.push('fullscreen=0'); lines.push('title=%d — SPICE'); - lines.push('delete-this-file=1'); // remote-viewer deletes the file after reading (hides password) + lines.push('delete-this-file=1'); lines.push('toggle-fullscreen=shift+f11'); lines.push('release-cursor=shift+f12'); lines.push('secure-attention=ctrl+alt+end'); - lines.push('usb-filter=-1,-1,-1,-1,0'); // allow all USB devices + lines.push('usb-filter=-1,-1,-1,-1,0'); const vvPath = path.join(os.tmpdir(), `spice-${vmid}-${Date.now()}.vv`); fs.writeFileSync(vvPath, lines.join('\n') + '\n', { mode: 0o600 }); @@ -143,8 +197,6 @@ function launchRemoteViewer(vvPath) { ] : ['remote-viewer', 'virt-viewer']; - let launched = false; - function tryNext(i) { if (i >= candidates.length) { dialog.showErrorBox( @@ -158,7 +210,7 @@ function launchRemoteViewer(vvPath) { } const child = spawn(candidates[i], [vvPath], { detached: true, stdio: 'ignore' }); child.on('error', () => tryNext(i + 1)); - child.on('spawn', () => { launched = true; }); + child.on('spawn', () => { }); child.unref(); }