From f0cf3634082644109c112b071852add2f0abfc7e Mon Sep 17 00:00:00 2001 From: Benoit Date: Fri, 19 Jun 2026 13:24:51 +0200 Subject: [PATCH] feat: setup automatique certificats SSL au premier lancement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nouveau : setup-helper.js - Détecte si mkcert est installé - Installe mkcert automatiquement (Homebrew macOS, curl Linux) - Installe CA locale (mkcert -install) - Génère certificats pour localhost + IP réseau - Détection automatique IP WiFi Intégration dans main.js : - Vérifie certificats au démarrage - Si absents : dialog onboarding + setup auto - Dialog progress "Configuration en cours..." - Dialog success avec IP réseau - Dialog error si échec (fallback manuel) - Ne démarre serveur que si certificats OK Expérience utilisateur : 1. Lancer l'app (première fois) 2. Dialog : "Première utilisation, configuration..." 3. Cliquer "Continuer" 4. Attendre 1-2 min (installation mkcert + CA + certificats) 5. Dialog : "Configuration terminée, IP: 192.168.x.x" 6. Serveur démarre automatiquement Prochaines fois : détection certificats OK → démarre direct L'utilisateur n'a RIEN à faire manuellement ! --- electron/main.js | 58 ++++++++- electron/setup-helper.js | 249 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 electron/setup-helper.js diff --git a/electron/main.js b/electron/main.js index dc0c4d0..daf9f44 100644 --- a/electron/main.js +++ b/electron/main.js @@ -3,10 +3,11 @@ * Intègre le serveur Node.js existant dans une application Electron */ -const { app, BrowserWindow, ipcMain, Menu, Tray } = require('electron'); +const { app, BrowserWindow, ipcMain, Menu, Tray, dialog } = require('electron'); const path = require('path'); const { spawn } = require('child_process'); const http = require('http'); +const setupHelper = require('./setup-helper'); // État de l'application let mainWindow = null; @@ -305,6 +306,61 @@ app.whenReady().then(async () => { createWindow(); createTray(); + // Vérifier setup automatique (certificats) + console.log('🔍 Vérification configuration...'); + const projectRoot = path.join(__dirname, '..'); + const certsDir = path.join(projectRoot, 'certs'); + + if (!setupHelper.certificatesExist(certsDir)) { + console.log('⚠️ Certificats SSL manquants, configuration automatique...\n'); + + // Afficher dialog d'information + const infoResult = await dialog.showMessageBox(mainWindow, { + type: 'info', + title: 'Configuration initiale', + message: 'Première utilisation de PTT Live', + detail: 'Configuration des certificats SSL en cours...\nCela peut prendre 1-2 minutes.\n\nmkcert sera installé automatiquement.', + buttons: ['Continuer', 'Annuler'] + }); + + if (infoResult.response === 1) { + console.log('⚠️ Configuration annulée par l\'utilisateur'); + return; + } + + // Lancer setup auto + const setupResult = await setupHelper.autoSetup(projectRoot); + + if (!setupResult.success) { + // Échec du setup automatique + await dialog.showMessageBox(mainWindow, { + type: 'error', + title: 'Configuration échouée', + message: 'Impossible de configurer automatiquement les certificats SSL', + detail: setupResult.manual + ? 'Veuillez exécuter manuellement :\n./setup-certificates.sh\n\nOu installer mkcert : https://github.com/FiloSottile/mkcert' + : setupResult.error, + buttons: ['OK'] + }); + + console.error('❌ Setup automatique échoué'); + return; // Ne pas démarrer le serveur + } + + // Setup réussi + await dialog.showMessageBox(mainWindow, { + type: 'info', + title: 'Configuration terminée', + message: 'Certificats SSL configurés avec succès !', + detail: `Votre IP réseau : ${setupResult.networkIP}\n\nLe serveur va démarrer...`, + buttons: ['OK'] + }); + + console.log('✅ Setup automatique terminé\n'); + } else { + console.log('✅ Certificats présents\n'); + } + // Démarrer le serveur automatiquement console.log('🔄 Démarrage automatique du serveur...'); await startServer(); diff --git a/electron/setup-helper.js b/electron/setup-helper.js new file mode 100644 index 0000000..4078749 --- /dev/null +++ b/electron/setup-helper.js @@ -0,0 +1,249 @@ +/** + * PTT Live Desktop - Setup Helper + * Automatise l'installation des dépendances et certificats + */ + +const { exec } = require('child_process'); +const { promisify } = require('util'); +const { existsSync } = require('fs'); +const { join } = require('path'); +const os = require('os'); + +const execPromise = promisify(exec); + +/** + * Vérifie si mkcert est installé + */ +async function isMkcertInstalled() { + try { + await execPromise('mkcert -version'); + return true; + } catch (error) { + return false; + } +} + +/** + * Installe mkcert automatiquement + */ +async function installMkcert() { + const platform = os.platform(); + + console.log('📦 Installation de mkcert...'); + + try { + if (platform === 'darwin') { + // macOS - via Homebrew + if (await isHomebrewInstalled()) { + await execPromise('brew install mkcert nss'); + console.log('✅ mkcert installé via Homebrew'); + return true; + } else { + throw new Error('Homebrew requis sur macOS'); + } + } else if (platform === 'linux') { + // Linux - téléchargement direct + await execPromise('curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"'); + await execPromise('chmod +x mkcert-v*-linux-amd64'); + await execPromise('sudo mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert'); + console.log('✅ mkcert installé'); + return true; + } else { + throw new Error(`Plateforme non supportée: ${platform}`); + } + } catch (error) { + console.error('❌ Erreur installation mkcert:', error.message); + return false; + } +} + +/** + * Vérifie si Homebrew est installé + */ +async function isHomebrewInstalled() { + try { + await execPromise('brew --version'); + return true; + } catch (error) { + return false; + } +} + +/** + * Installe la CA locale + */ +async function installCA() { + try { + console.log('🔑 Installation de la Certificate Authority locale...'); + await execPromise('mkcert -install'); + console.log('✅ CA locale installée'); + return true; + } catch (error) { + console.error('❌ Erreur installation CA:', error.message); + return false; + } +} + +/** + * Détecte l'IP réseau locale + */ +function getNetworkIP() { + const interfaces = os.networkInterfaces(); + + // Priorité : WiFi > Ethernet + const priority = ['en0', 'en1', 'eth0', 'wlan0']; + + for (const name of priority) { + const iface = interfaces[name]; + if (iface) { + for (const net of iface) { + if (net.family === 'IPv4' && !net.internal) { + return net.address; + } + } + } + } + + // Fallback : première IP non-interne + for (const name of Object.keys(interfaces)) { + for (const net of interfaces[name]) { + if (net.family === 'IPv4' && !net.internal) { + return net.address; + } + } + } + + return '192.168.1.100'; // Fallback ultime +} + +/** + * Génère les certificats SSL + */ +async function generateCertificates(certsDir) { + try { + const networkIP = getNetworkIP(); + const hostname = os.hostname(); + + console.log('📜 Génération des certificats...'); + console.log(` IP réseau : ${networkIP}`); + + // Créer répertoire si nécessaire + if (!existsSync(certsDir)) { + await execPromise(`mkdir -p "${certsDir}"`); + } + + // Générer certificats + const cmd = `cd "${certsDir}" && mkcert localhost 127.0.0.1 ::1 "${networkIP}" "*.local" "${hostname}.local"`; + await execPromise(cmd); + + // Renommer pour simplifier + const files = await execPromise(`ls "${certsDir}"/*.pem`); + const fileList = files.stdout.trim().split('\n'); + + // Trouver les fichiers générés + const certFile = fileList.find(f => !f.includes('-key.pem')); + const keyFile = fileList.find(f => f.includes('-key.pem')); + + if (certFile && keyFile) { + // Copier avec noms standards + await execPromise(`cp "${certFile}" "${join(certsDir, 'localhost.pem')}"`); + await execPromise(`cp "${keyFile}" "${join(certsDir, 'localhost-key.pem')}"`); + } + + console.log('✅ Certificats générés'); + return { networkIP, certPath: join(certsDir, 'localhost.pem'), keyPath: join(certsDir, 'localhost-key.pem') }; + } catch (error) { + console.error('❌ Erreur génération certificats:', error.message); + return null; + } +} + +/** + * Vérifie si les certificats existent et sont valides + */ +function certificatesExist(certsDir) { + const certPath = join(certsDir, 'localhost.pem'); + const keyPath = join(certsDir, 'localhost-key.pem'); + + return existsSync(certPath) && existsSync(keyPath); +} + +/** + * Setup complet automatique + */ +async function autoSetup(projectRoot) { + const certsDir = join(projectRoot, 'certs'); + + console.log('🚀 Configuration automatique PTT Live...\n'); + + // 1. Vérifier certificats existants + if (certificatesExist(certsDir)) { + console.log('✅ Certificats déjà présents'); + return { success: true, needsRestart: false }; + } + + console.log('⚠️ Certificats SSL non trouvés\n'); + + // 2. Vérifier mkcert + const hasMkcert = await isMkcertInstalled(); + + if (!hasMkcert) { + console.log('📦 mkcert non installé, installation...\n'); + + const installed = await installMkcert(); + if (!installed) { + return { + success: false, + error: 'Installation mkcert échouée', + manual: true, + instructions: 'Installez mkcert manuellement : https://github.com/FiloSottile/mkcert' + }; + } + } else { + console.log('✅ mkcert déjà installé\n'); + } + + // 3. Installer CA locale + const caInstalled = await installCA(); + if (!caInstalled) { + return { + success: false, + error: 'Installation CA échouée', + manual: true + }; + } + + console.log(''); + + // 4. Générer certificats + const result = await generateCertificates(certsDir); + if (!result) { + return { + success: false, + error: 'Génération certificats échouée', + manual: true + }; + } + + console.log('\n✅ Configuration terminée !'); + console.log(` Certificats : ${certsDir}`); + console.log(` IP réseau : ${result.networkIP}\n`); + + return { + success: true, + needsRestart: false, + networkIP: result.networkIP, + certPath: result.certPath, + keyPath: result.keyPath + }; +} + +module.exports = { + isMkcertInstalled, + installMkcert, + installCA, + generateCertificates, + certificatesExist, + getNetworkIP, + autoSetup +};