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:
Executable
+47
@@ -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
@@ -222,6 +222,61 @@ install_node_deps() {
|
|||||||
echo "Dépendances Node.js installées !"
|
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
|
# Configuration audio
|
||||||
configure_audio() {
|
configure_audio() {
|
||||||
echo ""
|
echo ""
|
||||||
@@ -259,10 +314,10 @@ configure_audio() {
|
|||||||
print_summary() {
|
print_summary() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo " Installation terminée !"
|
echo " ✅ Installation terminée !"
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Prochaines étapes :"
|
echo "📝 Prochaines étapes :"
|
||||||
echo ""
|
echo ""
|
||||||
echo "1. Démarrer le serveur :"
|
echo "1. Démarrer le serveur :"
|
||||||
echo " cd $PROJECT_ROOT/server"
|
echo " cd $PROJECT_ROOT/server"
|
||||||
@@ -272,10 +327,18 @@ print_summary() {
|
|||||||
echo " cd $PROJECT_ROOT/client"
|
echo " cd $PROJECT_ROOT/client"
|
||||||
echo " npm run dev"
|
echo " npm run dev"
|
||||||
echo ""
|
echo ""
|
||||||
echo "3. Accéder à l'interface :"
|
echo "3. Accéder à l'application :"
|
||||||
echo " http://localhost:5173"
|
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 ""
|
||||||
echo "Documentation : $PROJECT_ROOT/README.md"
|
|
||||||
echo "========================================"
|
echo "========================================"
|
||||||
echo ""
|
echo ""
|
||||||
}
|
}
|
||||||
@@ -286,6 +349,7 @@ main() {
|
|||||||
install_system_deps
|
install_system_deps
|
||||||
install_livekit_server
|
install_livekit_server
|
||||||
install_node_deps
|
install_node_deps
|
||||||
|
configure_network
|
||||||
configure_audio
|
configure_audio
|
||||||
print_summary
|
print_summary
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
export default router;
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user