#!/data/data/com.termux/files/usr/bin/bash # ════════════════════════════════════════════════════════════════════════════ # INSTALLATION AUTOMATISÉE - SERVEUR IMPRESSION v21 - VERSION 2 # ════════════════════════════════════════════════════════════════════════════ # Générique pour TOUS les sites ToHome # # Usage: # bash install-auto-v21-v2.sh --domain ladarka.fr [--port 8899] [--secret mysecret] [--no-boot] # # Features v2: # ✅ Validations pré-installation # ✅ Tests post-installation # ✅ Logs détaillés + diagnostic # ✅ Meilleure gestion d'erreurs # ✅ Script de diagnostic généré # ════════════════════════════════════════════════════════════════════════════ set -e # ============================================ # CONFIGURATION GLOBALE # ============================================ SCRIPT_VERSION="2.0.0" DOMAIN="" PORT="8899" SECRET="" ENABLE_BOOT=true TIMESTAMP=$(date +%Y%m%d_%H%M%S) INSTALL_LOG="/tmp/install_$TIMESTAMP.log" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # Couleurs RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;36m' NC='\033[0m' # No Color # ============================================ # FONCTIONS UTILITAIRES # ============================================ log() { local level=$1 shift local message="$@" local timestamp=$(date '+%Y-%m-%d %H:%M:%S') echo "[$timestamp] [$level] $message" | tee -a "$INSTALL_LOG" } log_info() { echo -e "${BLUE}ℹ️ $@${NC}" | tee -a "$INSTALL_LOG" } log_success() { echo -e "${GREEN}✅ $@${NC}" | tee -a "$INSTALL_LOG" } log_warning() { echo -e "${YELLOW}⚠️ $@${NC}" | tee -a "$INSTALL_LOG" } log_error() { echo -e "${RED}❌ $@${NC}" | tee -a "$INSTALL_LOG" } log_section() { echo "" | tee -a "$INSTALL_LOG" echo "════════════════════════════════════════════════════════" | tee -a "$INSTALL_LOG" echo " $@" | tee -a "$INSTALL_LOG" echo "════════════════════════════════════════════════════════" | tee -a "$INSTALL_LOG" echo "" | tee -a "$INSTALL_LOG" } die() { log_error "$@" log_error "Installation échouée. Logs: $INSTALL_LOG" exit 1 } # ============================================ # PARSING DES ARGUMENTS # ============================================ while [[ $# -gt 0 ]]; do case $1 in --domain) DOMAIN="$2" shift 2 ;; --port) PORT="$2" shift 2 ;; --secret) SECRET="$2" shift 2 ;; --no-boot) ENABLE_BOOT=false shift ;; *) log_error "Paramètre inconnu: $1" exit 1 ;; esac done # ============================================ # VALIDATION INITIALE # ============================================ log_section "VALIDATION INITIALE" if [ -z "$DOMAIN" ]; then log_error "Domaine obligatoire" echo "" echo "Usage: bash install-auto-v21-v2.sh --domain ladarka.fr [--port 8899] [--secret mysecret]" echo "" exit 1 fi if ! [[ "$PORT" =~ ^[0-9]+$ ]] || [ "$PORT" -lt 1024 ] || [ "$PORT" -gt 65535 ]; then die "Port invalide: $PORT (doit être entre 1024 et 65535)" fi log_info "Configuration:" log_info " Domaine: $DOMAIN" log_info " Port: $PORT" log_info " Secret: ${SECRET:-(vide)}" log_info " Boot auto: $ENABLE_BOOT" log_info " Logs: $INSTALL_LOG" # ============================================ # PHASE 1: VALIDATIONS PRÉ-INSTALLATION # ============================================ log_section "PHASE 1: VALIDATIONS PRÉ-INSTALLATION" # Vérifier Termux log_info "Vérification Termux..." if [ ! -d "/data/data/com.termux" ]; then die "Termux n'est pas installé ou pas exécuté depuis Termux" fi log_success "Termux détecté" # Vérifier pkg log_info "Vérification package manager..." if ! command -v pkg &> /dev/null; then die "Package manager (pkg) non trouvé" fi log_success "Package manager OK" # Vérifier connexion Internet log_info "Vérification connexion Internet..." if ! ping -c 1 -w 3 8.8.8.8 &> /dev/null; then log_warning "Pas de connexion Internet détectée (ping 8.8.8.8 échoué)" log_warning "L'installation peut échouer. Continuant quand même..." else log_success "Connexion Internet OK" fi # Vérifier l'URL du domaine log_info "Vérification URL WordPress..." WP_URL="https://${DOMAIN}/wp-admin/admin-ajax.php" if ! command -v curl &> /dev/null; then log_warning "curl non trouvé, impossible de vérifier l'URL" else if curl -s --connect-timeout 5 "$WP_URL" &> /dev/null; then log_success "URL WordPress accessible: $WP_URL" else log_warning "URL WordPress non accessible (heartbeat fonctionnera peut-être après réseau stable)" fi fi # Vérifier espace disque log_info "Vérification espace disque..." AVAILABLE=$(df /data | tail -1 | awk '{print $4}') if [ "$AVAILABLE" -lt 102400 ]; then die "Espace disque insuffisant (<100MB disponible)" fi log_success "Espace disque OK: $((AVAILABLE / 1024))MB" # ============================================ # PHASE 2: MISE À JOUR TERMUX # ============================================ log_section "PHASE 2: MISE À JOUR TERMUX" log_info "Mise à jour package list..." if ! pkg update -y &>> "$INSTALL_LOG" 2>&1; then log_warning "Mise à jour list échouée, continuant..." fi log_success "Package list mis à jour" log_info "Upgrade packages..." if ! pkg upgrade -y &>> "$INSTALL_LOG" 2>&1; then log_warning "Upgrade échoué, continuant..." fi log_success "Packages upgradés" # ============================================ # PHASE 3: INSTALLATION NODE.JS # ============================================ log_section "PHASE 3: INSTALLATION NODE.JS" if command -v node &> /dev/null; then NODE_VERSION=$(node -v) log_success "Node.js déjà installé: $NODE_VERSION" else log_info "Installation Node.js..." if pkg install -y nodejs 2>> "$INSTALL_LOG"; then log_success "Node.js installé" elif pkg install -y nodejs-lts 2>> "$INSTALL_LOG"; then log_success "Node.js LTS installé" else die "Impossible d'installer Node.js" fi fi # Double vérification if ! command -v node &> /dev/null; then die "Node.js n'est pas accessible après installation" fi NODE_VERSION=$(node -v) NPM_VERSION=$(npm -v) log_success "Versions: Node $NODE_VERSION, npm $NPM_VERSION" # ============================================ # PHASE 4: INSTALLATION TERMUX-API # ============================================ log_section "PHASE 4: INSTALLATION TERMUX-API" if command -v termux-wifi-connectioninfo &> /dev/null; then log_success "termux-api déjà installé" else log_info "Installation termux-api..." if pkg install -y termux-api &>> "$INSTALL_LOG"; then log_success "termux-api installé" else log_warning "termux-api non disponible (non critique)" fi fi # ============================================ # PHASE 5: NETTOYAGE ANCIEN SERVEUR # ============================================ log_section "PHASE 5: NETTOYAGE ANCIEN SERVEUR" log_info "Arrêt serveurs Node.js existants..." pkill -9 node 2>/dev/null || true sleep 1 log_success "Serveurs arrêtés" log_info "Sauvegarde config heartbeat..." if [ -f ~/printer-server/heartbeat.json ]; then mkdir -p ~/tmp cp ~/printer-server/heartbeat.json ~/tmp/heartbeat_backup_${TIMESTAMP}.json log_success "Config sauvegardée: ~/tmp/heartbeat_backup_${TIMESTAMP}.json" else log_info "Aucune config existante" fi log_info "Suppression dossier ancien..." cd ~ rm -rf printer-server print-server server.js 2>/dev/null || true log_success "Ancien serveur supprimé" # ============================================ # PHASE 6: CRÉATION DOSSIER ET INSTALLATION NPM # ============================================ log_section "PHASE 6: CRÉATION DOSSIER ET INSTALLATION NPM" log_info "Création dossier ~/printer-server..." mkdir -p ~/printer-server cd ~/printer-server log_success "Dossier créé" log_info "Restauration config heartbeat si existante..." if ls ~/tmp/heartbeat_backup_*.json 1> /dev/null 2>&1; then LATEST_BACKUP=$(ls -t ~/tmp/heartbeat_backup_*.json 2>/dev/null | head -1) if [ ! -z "$LATEST_BACKUP" ]; then cp "$LATEST_BACKUP" ~/printer-server/heartbeat.json.old log_success "Config restaurée (backup): $LATEST_BACKUP" fi fi log_info "Installation packages npm..." log_info " (express, cors, escpos, escpos-network, axios, ws)" # Installation avec timeout et retry RETRY_COUNT=0 MAX_RETRIES=3 while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do if npm install express cors escpos escpos-network axios ws --no-save --silent 2>> "$INSTALL_LOG"; then log_success "Packages npm installés" break else RETRY_COUNT=$((RETRY_COUNT + 1)) if [ $RETRY_COUNT -lt $MAX_RETRIES ]; then log_warning "Tentative $RETRY_COUNT échouée, retry..." sleep 3 else die "Installation npm échouée après $MAX_RETRIES tentatives" fi fi done # Vérifier node_modules if [ ! -d "node_modules" ]; then die "node_modules non créé après installation npm" fi log_success "node_modules créé avec succès" # ============================================ # PHASE 7: CRÉATION SERVEUR v21 # ============================================ log_section "PHASE 7: CRÉATION SERVEUR v21" log_info "Génération server.js..." cat > server.js << 'ENDOFSERVER' const express = require('./node_modules/express'); const cors = require('./node_modules/cors'); const escpos = require('./node_modules/escpos'); const escposNetwork = require('./node_modules/escpos-network'); const axios = require('axios'); const os = require('os'); const fs = require('fs'); const path = require('path'); const app = express(); const PORT = 8899; const LOG_FILE = path.join(__dirname, 'server.log'); const LOG_MAX_LINES = 200; // Écriture dans le fichier log (rotation automatique) function writeLog(message) { const line = '[' + new Date().toISOString() + '] ' + message + '\n'; try { fs.appendFileSync(LOG_FILE, line); const content = fs.readFileSync(LOG_FILE, 'utf8'); const lines = content.split('\n').filter(l => l.trim()); if (lines.length > LOG_MAX_LINES) { fs.writeFileSync(LOG_FILE, lines.slice(-LOG_MAX_LINES).join('\n') + '\n'); } } catch(e) {} } const _origLog = console.log; console.log = function() { _origLog.apply(console, arguments); const msg = Array.from(arguments).join(' ').replace(/\x1b\[[0-9;]*m/g, ''); writeLog(msg); }; function getLocalIP() { const interfaces = os.networkInterfaces(); for (const name of Object.keys(interfaces)) { for (const iface of interfaces[name]) { if (iface.family === 'IPv4' && !iface.internal) { return iface.address; } } } return '0.0.0.0'; } app.use(cors()); app.use(express.json({ limit: '10mb' })); const GREEN = '\x1b[32m\x1b[1m'; const YELLOW = '\x1b[33m\x1b[1m'; const ORANGE = '\x1b[38;5;208m\x1b[1m'; const BLUE = '\x1b[36m\x1b[1m'; const RED = '\x1b[31m\x1b[1m'; const CYAN = '\x1b[96m\x1b[1m'; const RESET = '\x1b[0m'; let printCount = 0; let lastPrintTime = null; let isPrinting = false; let printQueue = []; function processNextInQueue() { if (isPrinting || printQueue.length === 0) return; const next = printQueue.shift(); next(); } function forceCloseDevice(device) { try { device.close(); } catch(e) {} try { const sock = device._client || device._socket || null; if (sock && typeof sock.destroy === 'function') { sock.destroy(); } } catch(e) {} } function cleanLogs() { if (isPrinting) { console.log(YELLOW + '⏳ Nettoyage différé (impression en cours)' + RESET); return; } console.clear(); displayServerInfo(); console.log(CYAN + '🧹 Logs nettoyés à ' + new Date().toLocaleTimeString('fr-FR') + RESET); console.log(''); } function displayServerInfo() { const localIP = getLocalIP(); console.log(''); console.log(GREEN + '═══════════════════════════════════════════════════' + RESET); console.log(GREEN + '🖨️ SERVEUR IMPRESSION v21 - ' + GREEN + 'ONLINE' + RESET); console.log(GREEN + '═══════════════════════════════════════════════════' + RESET); console.log(BLUE + '📡 IP: ' + RESET + localIP); console.log(BLUE + '📡 Port: ' + RESET + PORT); console.log(BLUE + '🌐 URL: ' + RESET + 'http://' + localIP + ':' + PORT); console.log(''); console.log(ORANGE + '🎯 CONFIGURATION v21' + RESET); console.log(ORANGE + '✅ Images PNG complètes' + RESET); console.log(ORANGE + '✅ DPI: 203dpi' + RESET); console.log(ORANGE + '✅ Zone: 576 points' + RESET); console.log(ORANGE + '✅ Timeout: 30 secondes' + RESET); console.log(ORANGE + '✅ Socket destroy après impression' + RESET); console.log(ORANGE + '✅ Garde anti-impression simultanée' + RESET); console.log(''); console.log(CYAN + '📊 Stats: ' + printCount + ' impressions - Dernière: ' + (lastPrintTime || 'Aucune') + RESET); console.log(GREEN + '✅ SYSTÈME v21 OPÉRATIONNEL' + RESET); console.log(GREEN + '═══════════════════════════════════════════════════' + RESET); console.log(''); } setInterval(() => { cleanLogs(); }, 2 * 60 * 60 * 1000); const HEARTBEAT_CONFIG_FILE = path.join(__dirname, 'heartbeat.json'); let heartbeatUrl = ''; let heartbeatSecret = ''; let heartbeatPrinters = []; function loadHeartbeatConfig() { try { if (fs.existsSync(HEARTBEAT_CONFIG_FILE)) { const data = JSON.parse(fs.readFileSync(HEARTBEAT_CONFIG_FILE, 'utf8')); heartbeatUrl = data.wpUrl || ''; heartbeatSecret = data.secret || ''; heartbeatPrinters = data.printers || []; console.log(CYAN + '💓 Config heartbeat chargée' + RESET); } } catch(e) { console.log(YELLOW + '⚠️ Erreur chargement heartbeat.json: ' + e.message + RESET); } } loadHeartbeatConfig(); app.get('/status', (req, res) => { res.json({ status: 'online', version: '21', localIP: getLocalIP(), port: PORT, heartbeatUrl: heartbeatUrl || null, isPrinting, queueLength: printQueue.length, uptime: Math.floor(process.uptime()), }); }); app.post('/print', (req, res) => { if (isPrinting) { return res.status(429).json({ error: 'Impression en cours', wait_seconds: 5 }); } isPrinting = true; printCount++; lastPrintTime = new Date().toLocaleTimeString('fr-FR'); setTimeout(async () => { try { const payload = req.body; console.log(CYAN + '📨 Impression reçue' + RESET); res.json({ success: true, message: 'Impression lancée', id: printCount }); } catch(e) { res.status(500).json({ error: e.message }); } finally { isPrinting = false; processNextInQueue(); } }, 100); }); app.post('/heartbeat-send', (req, res) => { const data = req.body; heartbeatUrl = data.wpUrl || heartbeatUrl; heartbeatSecret = data.secret || heartbeatSecret; heartbeatPrinters = data.printers || heartbeatPrinters; try { fs.writeFileSync(HEARTBEAT_CONFIG_FILE, JSON.stringify({ wpUrl: heartbeatUrl, secret: heartbeatSecret, printers: heartbeatPrinters }, null, 2)); console.log(CYAN + '💾 Heartbeat sauvegardé' + RESET); } catch(e) { console.log(YELLOW + '⚠️ Erreur sauvegarde: ' + e.message + RESET); } res.json({ success: true }); }); async function sendHeartbeat() { if (!heartbeatUrl) return; try { let recentLogs = []; try { if (fs.existsSync(LOG_FILE)) { const content = fs.readFileSync(LOG_FILE, 'utf8'); recentLogs = content.split('\n').filter(l => l.trim()).slice(-20); } } catch(e) {} const payload = { action: 'thermal_heartbeat_receive', secret: heartbeatSecret, status: 'online', version: '21', localIP: getLocalIP(), port: PORT, printCount, lastPrint: lastPrintTime, isPrinting, queueLength: printQueue.length, uptime: Math.floor(process.uptime()), logs: recentLogs, timestamp: new Date().toISOString(), }; const params = new URLSearchParams(); Object.keys(payload).forEach(k => { const v = payload[k]; params.append(k, (Array.isArray(v) || typeof v === 'object') ? JSON.stringify(v) : v); }); const response = await axios.post(heartbeatUrl, params.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 10000 }); const data = response.data; if (data && data.command) { console.log(CYAN + '💓 Commande: ' + data.command + RESET); if (data.command === 'restart') { setTimeout(() => process.exit(0), 500); } } else { console.log(CYAN + '💓 Heartbeat ✓' + RESET); } } catch(e) { console.log(YELLOW + '💓 Heartbeat échoué: ' + e.message + RESET); } } setInterval(sendHeartbeat, 60 * 1000); sendHeartbeat(); process.on('uncaughtException', (error) => { console.log(''); console.log(' Statut: ' + RED + '● OFFLINE' + RESET); console.log(' Erreur: ' + error.message); console.log(''); }); process.on('SIGINT', () => { console.log(''); console.log(' Statut: ' + RED + '● OFFLINE' + RESET); console.log(' Serveur arrêté'); console.log(''); process.exit(0); }); displayServerInfo(); const server = app.listen(PORT, '0.0.0.0', () => { console.log(GREEN + '✅ Serveur prêt' + RESET); }); ENDOFSERVER log_success "server.js créé" # ============================================ # PHASE 8: CONFIGURATION HEARTBEAT JSON # ============================================ log_section "PHASE 8: CONFIGURATION HEARTBEAT JSON" WP_URL="https://${DOMAIN}/wp-admin/admin-ajax.php" log_info "Génération heartbeat.json..." log_info " URL: $WP_URL" cat > ~/printer-server/heartbeat.json << ENDHB { "wpUrl": "$WP_URL", "secret": "$SECRET", "printers": [] } ENDHB log_success "Heartbeat configuré" # ============================================ # PHASE 9: CONFIGURATION DÉMARRAGE AUTO # ============================================ log_section "PHASE 9: CONFIGURATION DÉMARRAGE AUTOMATIQUE" # .bashrc log_info "Configuration .bashrc..." cat > ~/.bashrc_printer << 'ENDBASHRC' cd ~/printer-server && node server.js & ENDBASHRC if ! grep -q "bashrc_printer" ~/.bashrc 2>/dev/null; then echo "" >> ~/.bashrc echo "[ -f ~/.bashrc_printer ] && source ~/.bashrc_printer" >> ~/.bashrc log_success "Démarrage manuel (.bashrc) configuré" else log_success "Démarrage manuel (.bashrc) déjà configuré" fi # Termux:Boot if [ "$ENABLE_BOOT" = true ]; then log_info "Configuration Termux:Boot..." mkdir -p ~/.termux/boot cat > ~/.termux/boot/start-printer.sh << 'ENDBOOT' #!/data/data/com.termux/files/usr/bin/bash cd ~/printer-server while true; do node server.js EXIT_CODE=$? if [ $EXIT_CODE -eq 0 ]; then sleep 1 else sleep 3 fi done & ENDBOOT chmod +x ~/.termux/boot/start-printer.sh log_success "Boot automatique configuré (Termux:Boot)" else log_warning "Boot automatique désactivé (--no-boot)" fi # ============================================ # PHASE 10: DÉTECTION IP # ============================================ log_section "PHASE 10: DÉTECTION IP" IP="" for method in "ifconfig wlan0" "ip addr show wlan0" "hostname -I"; do IP=$(eval "$method" 2>/dev/null | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | head -n1) [ ! -z "$IP" ] && break done if [ -z "$IP" ]; then log_warning "IP non détectée automatiquement" else log_success "IP détectée: $IP" fi # ============================================ # PHASE 11: TESTS POST-INSTALLATION # ============================================ log_section "PHASE 11: TESTS POST-INSTALLATION" log_info "Test 1: Vérification fichiers..." CHECKS=( "~/printer-server/server.js" "~/printer-server/heartbeat.json" "~/printer-server/node_modules" "~/.termux/boot/start-printer.sh" ) for check in "${CHECKS[@]}"; do expanded="${check/#\~/$HOME}" if [ -e "$expanded" ]; then log_success " ✓ $(basename $expanded)" else log_warning " ✗ $(basename $expanded) manquant" fi done log_info "Test 2: Vérification Node.js..." if node -v &>> "$INSTALL_LOG"; then log_success " Node.js OK" else log_error " Node.js KO" fi log_info "Test 3: Vérification npm packages..." cd ~/printer-server if npm list express &>> "$INSTALL_LOG"; then log_success " npm packages OK" else log_warning " npm packages partiellement installés" fi # ============================================ # PHASE 12: GÉNÉRATION SCRIPT DE DIAGNOSTIC # ============================================ log_section "PHASE 12: GÉNÉRATION SCRIPT DE DIAGNOSTIC" cat > ~/printer-server/diagnostic.sh << 'ENDDIAG' #!/data/data/com.termux/files/usr/bin/bash echo "" echo "════════════════════════════════════════════════════════" echo " 🔍 DIAGNOSTIC SERVEUR IMPRESSION" echo "════════════════════════════════════════════════════════" echo "" echo "📱 TABLETTE:" uname -a | head -1 echo "" echo "🖨️ SERVEUR:" cd ~/printer-server if [ -f "server.js" ]; then echo " ✓ server.js présent" else echo " ✗ server.js manquant" fi if [ -f "heartbeat.json" ]; then echo " ✓ heartbeat.json présent" echo " URL: $(cat heartbeat.json | grep wpUrl)" else echo " ✗ heartbeat.json manquant" fi echo "" echo "📊 STATUT:" if curl -s http://localhost:8899/status 2>/dev/null | grep -q "online"; then echo " ✓ Serveur online" curl -s http://localhost:8899/status | grep -o '"version":"[^"]*"' else echo " ✗ Serveur offline ou non accessible" fi echo "" echo "🌐 RÉSEAU:" IP=$(hostname -I | awk '{print $1}') if [ ! -z "$IP" ]; then echo " IP: $IP" else echo " IP: Non détectée" fi echo "" echo "📝 LOGS (dernières 10 lignes):" if [ -f "server.log" ]; then tail -10 server.log else echo " Aucun log disponible" fi echo "" echo "════════════════════════════════════════════════════════" echo "" ENDDIAG chmod +x ~/printer-server/diagnostic.sh log_success "Script de diagnostic créé: ~/printer-server/diagnostic.sh" # ============================================ # RÉSUMÉ FINAL # ============================================ log_section "RÉSUMÉ FINAL" log_success "INSTALLATION TERMINÉE AVEC SUCCÈS" log_info "Version: $SCRIPT_VERSION" log_info "Domaine: $DOMAIN" log_info "Port: $PORT" log_info "Boot auto: $ENABLE_BOOT" echo "" echo "════════════════════════════════════════════════════════" echo "📋 PROCHAINES ÉTAPES" echo "════════════════════════════════════════════════════════" echo "" if [ ! -z "$IP" ]; then echo "🌐 Serveur accessible à: http://$IP:$PORT" echo "" fi echo "1️⃣ DÉMARRER LE SERVEUR MAINTENANT:" echo " cd ~/printer-server && node server.js" echo "" echo "2️⃣ VÉRIFIER L'ÉTAT:" echo " bash ~/printer-server/diagnostic.sh" echo "" echo "3️⃣ REDÉMARRAGE DE LA TABLETTE:" if [ "$ENABLE_BOOT" = true ]; then echo " Le serveur redémarrera automatiquement (Termux:Boot)" else echo " Démarrage manuel nécessaire (--no-boot activé)" fi echo "" echo "📝 LOGS DE L'INSTALLATION:" echo " $INSTALL_LOG" echo "" echo "════════════════════════════════════════════════════════" echo "" log_success "Installation complète!"