From 324ff11be94139327a13dde95ffd36c904e98de3 Mon Sep 17 00:00:00 2001 From: Benoit Date: Wed, 27 May 2026 13:27:53 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20scripts=20portables=20et=20API=20d?= =?UTF-8?q?=C3=A9tection=20devices=20audio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. API /admin/devices/list - Auto-détection devices audio macOS (sox) - Support Linux (JACK/PipeWire/PulseAudio) - Fallback Windows (placeholder Phase 3) 2. Scripts d'installation multi-OS - install.sh : détection OS automatique - install/linux.sh : génération .env auto (comme macOS) - Messages améliorés avec IP détectée 3. Script start.sh unifié - Lance serveur + client (dev ou prod) - Détection IP réseau au démarrage - Modes : ./start.sh (prod) ou ./start.sh --dev - Cleanup propre (trap SIGINT/SIGTERM) Améliore drastiquement la portabilité du projet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- install.sh | 47 +++++++++++++ install/linux.sh | 74 ++++++++++++++++++-- server/api/admin.js | 120 ++++++++++++++++++++++++++++++++ start.sh | 163 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 399 insertions(+), 5 deletions(-) create mode 100755 install.sh create mode 100755 start.sh diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..245abc5 --- /dev/null +++ b/install.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# PTT Live - Script d'installation multi-OS +# Détecte automatiquement le système et lance l'installeur approprié + +set -e + +# Couleurs +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' + +echo -e "${BLUE}==================================" +echo "🚀 PTT Live - Installation" +echo "==================================${NC}" +echo "" + +# Détection du système d'exploitation +if [[ "$OSTYPE" == "darwin"* ]]; then + echo -e "${GREEN}📱 Système détecté : macOS${NC}" + echo "" + exec ./install/macos.sh +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + echo -e "${GREEN}🐧 Système détecté : Linux${NC}" + echo "" + exec ./install/linux.sh +elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "cygwin" ]] || [[ "$OSTYPE" == "win32" ]]; then + echo -e "${YELLOW}🪟 Système détecté : Windows${NC}" + echo "" + echo -e "${RED}❌ Windows n'est pas encore supporté (Phase 3)${NC}" + echo "" + echo "Plateformes supportées :" + echo " • macOS (via Homebrew)" + echo " • Linux (Debian/Ubuntu/Fedora)" + echo "" + exit 1 +else + echo -e "${RED}❌ Système non reconnu : $OSTYPE${NC}" + echo "" + echo "Plateformes supportées :" + echo " • macOS" + echo " • Linux" + echo "" + exit 1 +fi diff --git a/install/linux.sh b/install/linux.sh index 8ab1890..e5dec61 100755 --- a/install/linux.sh +++ b/install/linux.sh @@ -222,6 +222,61 @@ install_node_deps() { echo "Dépendances Node.js installées !" } +# Configuration réseau et génération .env +configure_network() { + echo "" + echo "Configuration réseau..." + + # Détection IP réseau + NETWORK_IP=$(hostname -I | awk '{print $1}') + + if [ -z "$NETWORK_IP" ]; then + echo "⚠️ IP réseau non détectée, utilisation localhost" + NETWORK_IP="localhost" + else + echo "✓ IP réseau détectée : ${NETWORK_IP}" + fi + + # Générer .env serveur + echo "Génération configuration serveur..." + + cat > "$PROJECT_ROOT/server/.env" << EOF +# Configuration PTT Live Server +# Généré automatiquement par install/linux.sh + +USE_LOCAL_LIVEKIT=true + +# LiveKit Configuration +# AUTO = détection automatique IP réseau au démarrage +LIVEKIT_URL=AUTO +# En mode --dev, LiveKit utilise ces clés par défaut +LIVEKIT_API_KEY=devkey +LIVEKIT_API_SECRET=secret + +# Server Configuration +PORT=3000 +NODE_ENV=development +EOF + + echo "✓ Configuration serveur générée (server/.env)" + + # Générer .env client + echo "Génération configuration client..." + + cat > "$PROJECT_ROOT/client/.env" << EOF +# Configuration PTT Live Client +# Généré automatiquement par install/linux.sh + +# En développement local, utilise le proxy Vite +VITE_API_URL=/api + +# Pour accès réseau (autres devices), décommentez et mettez l'IP du serveur : +# VITE_API_URL=http://${NETWORK_IP}:3000 +EOF + + echo "✓ Configuration client générée (client/.env)" +} + # Configuration audio configure_audio() { echo "" @@ -259,10 +314,10 @@ configure_audio() { print_summary() { echo "" echo "========================================" - echo " Installation terminée !" + echo " ✅ Installation terminée !" echo "========================================" echo "" - echo "Prochaines étapes :" + echo "📝 Prochaines étapes :" echo "" echo "1. Démarrer le serveur :" echo " cd $PROJECT_ROOT/server" @@ -272,10 +327,18 @@ print_summary() { echo " cd $PROJECT_ROOT/client" echo " npm run dev" echo "" - echo "3. Accéder à l'interface :" - echo " http://localhost:5173" + echo "3. Accéder à l'application :" + echo " • Développement local : http://localhost:5173" + echo " • Depuis autre appareil (WiFi) : http://${NETWORK_IP}:5173" + echo "" + echo "💡 Configuration réseau :" + echo " IP serveur détectée : ${NETWORK_IP}" + echo " LiveKit URL : AUTO (détection dynamique)" + echo "" + echo "📖 Documentation :" + echo " • README.md - Guide complet" + echo " • README-PORTABLE.md - Déploiement portable" echo "" - echo "Documentation : $PROJECT_ROOT/README.md" echo "========================================" echo "" } @@ -286,6 +349,7 @@ main() { install_system_deps install_livekit_server install_node_deps + configure_network configure_audio print_summary } diff --git a/server/api/admin.js b/server/api/admin.js index 06833a9..a17f566 100644 --- a/server/api/admin.js +++ b/server/api/admin.js @@ -664,4 +664,124 @@ router.post('/audio/device', (req, res) => { } }); +/** + * GET /admin/devices/list + * Liste tous les devices audio disponibles (auto-détection) + * Supporte macOS (CoreAudio), Linux (JACK/PipeWire), Windows (WASAPI) + */ +router.get('/devices/list', async (req, res) => { + try { + const devices = { + inputs: [], + outputs: [], + platform: process.platform + }; + + // Détection selon la plateforme + if (process.platform === 'darwin') { + // macOS : utiliser CoreAudio via sox + const { exec } = await import('child_process'); + const { promisify } = await import('util'); + const execPromise = promisify(exec); + + try { + // Utiliser sox pour lister les devices audio + const { stdout } = await execPromise('sox -V6 2>&1'); + + // Parser la sortie sox pour extraire les devices + // Format typique : "Input Device [0]: MacBook Pro Microphone" + const inputMatches = stdout.matchAll(/Input Device \[(\d+)\]: (.+)/g); + const outputMatches = stdout.matchAll(/Output Device \[(\d+)\]: (.+)/g); + + for (const match of inputMatches) { + devices.inputs.push({ + id: parseInt(match[1], 10), + name: match[2].trim() + }); + } + + for (const match of outputMatches) { + devices.outputs.push({ + id: parseInt(match[1], 10), + name: match[2].trim() + }); + } + } catch (soxError) { + console.warn('⚠️ sox non disponible, devices limités:', soxError.message); + + // Fallback : devices par défaut macOS + devices.inputs.push({ id: 0, name: 'Default Input (Built-in Microphone)', isDefault: true }); + devices.outputs.push({ id: 0, name: 'Default Output (Built-in Speakers)', isDefault: true }); + } + + } else if (process.platform === 'linux') { + // Linux : JACK ou PipeWire + const { exec } = await import('child_process'); + const { promisify } = await import('util'); + const execPromise = promisify(exec); + + try { + // Essayer JACK d'abord + const { stdout: jackPorts } = await execPromise('jack_lsp 2>/dev/null || echo ""'); + + if (jackPorts.trim()) { + // Parser les ports JACK + const ports = jackPorts.split('\n').filter(Boolean); + + ports.forEach(port => { + if (port.includes('capture')) { + devices.inputs.push({ id: port, name: port }); + } else if (port.includes('playback')) { + devices.outputs.push({ id: port, name: port }); + } + }); + } else { + // Fallback : PipeWire via pactl + const { stdout: paDevices } = await execPromise('pactl list short sources 2>/dev/null || echo ""'); + const { stdout: paSinks } = await execPromise('pactl list short sinks 2>/dev/null || echo ""'); + + if (paDevices.trim()) { + paDevices.split('\n').filter(Boolean).forEach((line, idx) => { + const name = line.split('\t')[1] || `Device ${idx}`; + devices.inputs.push({ id: idx, name }); + }); + } + + if (paSinks.trim()) { + paSinks.split('\n').filter(Boolean).forEach((line, idx) => { + const name = line.split('\t')[1] || `Device ${idx}`; + devices.outputs.push({ id: idx, name }); + }); + } + } + } catch (linuxError) { + console.warn('⚠️ Détection devices Linux échouée:', linuxError.message); + devices.inputs.push({ id: 0, name: 'Default Input', isDefault: true }); + devices.outputs.push({ id: 0, name: 'Default Output', isDefault: true }); + } + + } else if (process.platform === 'win32') { + // Windows : WASAPI (Phase 3) + // TODO: implémenter détection WASAPI + devices.inputs.push({ id: 0, name: 'Default Input (Windows)', isDefault: true }); + devices.outputs.push({ id: 0, name: 'Default Output (Windows)', isDefault: true }); + } + + addLog('info', 'Audio devices listed', { + inputsCount: devices.inputs.length, + outputsCount: devices.outputs.length + }); + + res.json(devices); + + } catch (error) { + console.error('Erreur GET /admin/devices/list:', error); + res.status(500).json({ + error: 'Failed to list audio devices', + message: error.message, + platform: process.platform + }); + } +}); + export default router; diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..2faf420 --- /dev/null +++ b/start.sh @@ -0,0 +1,163 @@ +#!/bin/bash + +# PTT Live - Script de démarrage unifié +# Lance le serveur et le client en mode production + +set -e + +# Couleurs +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +BLUE='\033[0;34m' +NC='\033[0m' + +# Détection IP réseau +get_network_ip() { + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS + ifconfig | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | head -n 1 + elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + # Linux + hostname -I | awk '{print $1}' + else + echo "localhost" + fi +} + +NETWORK_IP=$(get_network_ip) + +echo -e "${BLUE}==================================" +echo "🚀 PTT Live - Démarrage" +echo "==================================${NC}" +echo "" + +echo -e "${GREEN}📡 IP réseau détectée : ${NETWORK_IP}${NC}" +echo "" + +# Vérifier que les dépendances sont installées +if [ ! -d "server/node_modules" ]; then + echo -e "${RED}❌ Dépendances serveur manquantes${NC}" + echo " Exécutez d'abord : ./install/macos.sh (ou linux.sh)" + exit 1 +fi + +if [ ! -d "client/node_modules" ]; then + echo -e "${RED}❌ Dépendances client manquantes${NC}" + echo " Exécutez d'abord : ./install/macos.sh (ou linux.sh)" + exit 1 +fi + +# Créer fichier PID pour cleanup +PID_FILE="/tmp/ptt-live.pid" + +# Fonction cleanup +cleanup() { + echo "" + echo -e "${YELLOW}⏹ Arrêt PTT Live...${NC}" + + if [ -f "$PID_FILE" ]; then + while read -r pid; do + if ps -p "$pid" > /dev/null 2>&1; then + kill "$pid" 2>/dev/null || true + fi + done < "$PID_FILE" + rm -f "$PID_FILE" + fi + + echo -e "${GREEN}✓ Arrêté${NC}" + exit 0 +} + +trap cleanup SIGINT SIGTERM EXIT + +# Démarrer le serveur en arrière-plan +echo -e "${BLUE}🔧 Démarrage serveur...${NC}" +cd server +npm start > ../server.log 2>&1 & +SERVER_PID=$! +echo "$SERVER_PID" > "$PID_FILE" +cd .. + +echo -e "${GREEN}✓ Serveur démarré (PID: $SERVER_PID)${NC}" + +# Attendre que le serveur soit prêt +echo -e "${YELLOW}⏳ Attente démarrage serveur...${NC}" +for i in {1..30}; do + if curl -sf http://localhost:3000/health > /dev/null 2>&1; then + echo -e "${GREEN}✓ Serveur prêt${NC}" + break + fi + + if [ $i -eq 30 ]; then + echo -e "${RED}❌ Timeout : le serveur n'a pas démarré${NC}" + echo " Consultez server.log pour plus de détails" + exit 1 + fi + + sleep 1 +done + +echo "" + +# Build client si pas déjà fait ou mode dev +if [ "$1" == "--dev" ]; then + echo -e "${BLUE}🎨 Démarrage client (dev)...${NC}" + cd client + npm run dev & + CLIENT_PID=$! + echo "$CLIENT_PID" >> "$PID_FILE" + cd .. + + echo -e "${GREEN}✓ Client dev démarré${NC}" + echo "" + echo -e "${GREEN}==================================" + echo "✅ PTT Live démarré (mode dev)" + echo "==================================${NC}" + echo "" + echo "🌐 Accès :" + echo " • Local : https://localhost:5173" + echo " • Réseau : https://${NETWORK_IP}:5173" + echo "" + echo "📊 API serveur : http://${NETWORK_IP}:3000" + echo "🎛️ Interface admin : http://${NETWORK_IP}:3000/admin" + echo "" + echo -e "${YELLOW}Appuyez sur Ctrl+C pour arrêter${NC}" + echo "" + + # Attendre indéfiniment + wait + +else + # Mode production : build et serve + echo -e "${BLUE}🎨 Build client production...${NC}" + cd client + + if [ ! -d "dist" ] || [ "$1" == "--rebuild" ]; then + npm run build + echo -e "${GREEN}✓ Client buildé${NC}" + else + echo -e "${YELLOW}⚠️ Build existant utilisé (--rebuild pour forcer)${NC}" + fi + + cd .. + + echo "" + echo -e "${GREEN}==================================" + echo "✅ PTT Live démarré (production)" + echo "==================================${NC}" + echo "" + echo "🌐 Accès :" + echo " • Local : http://localhost:3000" + echo " • Réseau : http://${NETWORK_IP}:3000" + echo "" + echo "🎛️ Interface admin : http://${NETWORK_IP}:3000/admin" + echo "" + echo "📝 Logs serveur : tail -f server.log" + echo "" + echo -e "${YELLOW}Appuyez sur Ctrl+C pour arrêter${NC}" + echo "" + + # Attendre indéfiniment + wait +fi