feat: scripts portables et API détection devices audio

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 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 13:27:53 +02:00
parent b35f80fc7c
commit 324ff11be9
4 changed files with 399 additions and 5 deletions
Executable
+47
View File
@@ -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
+69 -5
View File
@@ -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
}
+120
View File
@@ -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;
Executable
+163
View File
@@ -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