#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ OPTIMIERTES KIOSK-STARTUP SCRIPT ============================== Startet die Flask-App mit optimierter Konfiguration für Kiosk-Betrieb. Behebt die Probleme mit "unreachable" und langen Ladezeiten. Verwendung: python start_kiosk_optimized.py [--port PORT] [--debug] Autor: AutoFix-System Datum: $(date) """ import os import sys import time import signal import socket import subprocess import multiprocessing from pathlib import Path # ===== KONFIGURATION ===== DEFAULT_PORT = 5000 DEFAULT_HOST = "127.0.0.1" # IPv4 only für bessere Kompatibilität WORKERS = min(4, multiprocessing.cpu_count()) # Optimal für Kiosk TIMEOUT = 120 # 2 Minuten Timeout KEEPALIVE = 2 # 2 Sekunden Keep-Alive MAX_REQUESTS = 1000 # Worker-Recycling class KioskOptimizer: """Optimiert die App für Kiosk-Betrieb""" def __init__(self): self.base_dir = Path(__file__).parent self.app_file = self.base_dir / "app.py" self.log_file = self.base_dir / "logs" / "kiosk.log" self.pid_file = self.base_dir / "kiosk.pid" # Stelle sicher, dass Log-Verzeichnis existiert self.log_file.parent.mkdir(exist_ok=True) def check_dependencies(self): """Prüft ob alle Abhängigkeiten verfügbar sind""" try: import gunicorn print("✅ Gunicorn verfügbar") except ImportError: print("❌ Gunicorn nicht installiert!") print("Installiere mit: pip install gunicorn") sys.exit(1) if not self.app_file.exists(): print(f"❌ App-Datei nicht gefunden: {self.app_file}") sys.exit(1) print("✅ Alle Abhängigkeiten verfügbar") def fix_ipv6_issues(self): """Behebt IPv6-Auflösungsprobleme""" print("🔧 Behebe IPv6-Auflösungsprobleme...") # Windows: Prüfe hosts-Datei hosts_file = Path("C:/Windows/System32/drivers/etc/hosts") try: if hosts_file.exists(): content = hosts_file.read_text(encoding='utf-8') # Prüfe auf IPv6 localhost-Eintrag if "::1" in content and "localhost" in content: print("⚠️ IPv6 localhost-Eintrag in hosts-Datei gefunden") print("💡 Empfehlung: Verwende 127.0.0.1 statt localhost im Kiosk") except Exception as e: print(f"⚠️ Hosts-Datei-Check fehlgeschlagen: {e}") print("✅ IPv6-Issues-Check abgeschlossen") def kill_hanging_processes(self): """Beendet hängende Flask/Python-Prozesse""" print("🔄 Beende hängende Prozesse...") # Finde Python-Prozesse die auf Port 5000 hören try: result = subprocess.run( ["netstat", "-ano"], capture_output=True, text=True, shell=True ) lines = result.stdout.split('\n') hanging_pids = set() for line in lines: if ":5000" in line and ("WARTEND" in line or "ESTABLISHED" in line): parts = line.split() if len(parts) >= 5: pid = parts[-1] if pid.isdigit(): hanging_pids.add(int(pid)) # Töte hängende Prozesse for pid in hanging_pids: try: if pid != 0: # 0 ist System-PID subprocess.run(["taskkill", "/F", "/PID", str(pid)], capture_output=True, shell=True) print(f"💀 Prozess {pid} beendet") except: pass if hanging_pids: print(f"✅ {len(hanging_pids)} hängende Prozesse beendet") time.sleep(2) # Kurz warten else: print("✅ Keine hängenden Prozesse gefunden") except Exception as e: print(f"⚠️ Prozess-Cleanup fehlgeschlagen: {e}") def check_port_availability(self, host, port): """Prüft ob Port verfügbar ist""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.settimeout(1) result = sock.connect_ex((host, port)) return result != 0 # Port ist frei wenn connect fehlschlägt finally: sock.close() def start_with_gunicorn(self, host=DEFAULT_HOST, port=DEFAULT_PORT, debug=False): """Startet die App mit Gunicorn""" print(f"🚀 Starte Kiosk-App mit Gunicorn auf {host}:{port}") # Gunicorn-Konfiguration für optimale Kiosk-Performance gunicorn_config = [ "gunicorn", "--bind", f"{host}:{port}", "--workers", str(WORKERS), "--worker-class", "sync", # Sync für einfache Requests, funktioniert am stabilsten "--timeout", str(TIMEOUT), "--keepalive", str(KEEPALIVE), "--max-requests", str(MAX_REQUESTS), "--max-requests-jitter", "50", "--preload", # App einmal laden, dann forken "--worker-tmp-dir", "/dev/shm" if os.name != 'nt' else ".", "--log-level", "info" if not debug else "debug", "--access-logfile", str(self.log_file), "--error-logfile", str(self.log_file), "--capture-output", "--enable-stdio-inheritance" ] # Windows-spezifische Optimierungen if os.name == 'nt': gunicorn_config.extend([ "--worker-connections", "100", # Weniger Connections auf Windows "--threads", "2" # Threading für Windows ]) # Debug-Modus Anpassungen if debug: gunicorn_config.extend([ "--reload", "--log-level", "debug" ]) # App-Modul gunicorn_config.append("app:app") print(f"🔧 Gunicorn-Kommando: {' '.join(gunicorn_config)}") # Umgebungsvariablen für optimierte App setzen env = os.environ.copy() env.update({ "FLASK_ENV": "production" if not debug else "development", "USE_OPTIMIZED_CONFIG": "true", "FORCE_OPTIMIZED_MODE": "true", "PYTHONUNBUFFERED": "1", "PYTHONIOENCODING": "utf-8" }) # Starte Gunicorn try: # PID speichern process = subprocess.Popen( gunicorn_config, cwd=str(self.base_dir), env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True ) # PID in Datei schreiben with open(self.pid_file, 'w') as f: f.write(str(process.pid)) print(f"✅ Gunicorn gestartet (PID: {process.pid})") print(f"📋 Logs: {self.log_file}") print(f"🌐 URL: http://{host}:{port}") print(f"🔄 Für Kiosk verwende: http://127.0.0.1:{port}") # Warte kurz und teste Verbindung time.sleep(3) if self.test_connection(host, port): print("🎉 APP ERFOLGREICH GESTARTET!") print(f"💡 Kiosk-Browser auf http://127.0.0.1:{port} zeigen lassen") return process else: print("❌ App startet nicht korrekt!") process.terminate() return None except Exception as e: print(f"❌ Fehler beim Starten: {e}") return None def test_connection(self, host, port, retries=5): """Testet die Verbindung zur App""" print(f"🔍 Teste Verbindung zu {host}:{port}...") for i in range(retries): try: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(2) result = sock.connect_ex((host, port)) sock.close() if result == 0: print(f"✅ Verbindung erfolgreich (Versuch {i+1})") return True else: print(f"⏳ Versuch {i+1} fehlgeschlagen, warte...") time.sleep(1) except Exception as e: print(f"⚠️ Verbindungstest Versuch {i+1} fehlgeschlagen: {e}") time.sleep(1) print("❌ Verbindung fehlgeschlagen!") return False def stop(self): """Stoppt die App""" if self.pid_file.exists(): try: with open(self.pid_file, 'r') as f: pid = int(f.read().strip()) print(f"🛑 Stoppe App (PID: {pid})") # Windows: taskkill verwenden if os.name == 'nt': subprocess.run(["taskkill", "/F", "/PID", str(pid)], capture_output=True, shell=True) else: os.kill(pid, signal.SIGTERM) self.pid_file.unlink() print("✅ App gestoppt") except Exception as e: print(f"⚠️ Fehler beim Stoppen: {e}") else: print("ℹ️ Keine PID-Datei gefunden") def main(): """Hauptfunktion""" import argparse parser = argparse.ArgumentParser(description="Optimiertes Kiosk-Startup für Flask-App") parser.add_argument("--port", type=int, default=DEFAULT_PORT, help="Port (Standard: 5000)") parser.add_argument("--host", default=DEFAULT_HOST, help="Host (Standard: 127.0.0.1)") parser.add_argument("--debug", action="store_true", help="Debug-Modus") parser.add_argument("--stop", action="store_true", help="App stoppen") parser.add_argument("--force-kill", action="store_true", help="Alle hängenden Prozesse beenden") args = parser.parse_args() optimizer = KioskOptimizer() print("🚀 KIOSK-OPTIMIZER GESTARTET") print("=" * 50) if args.stop: optimizer.stop() return if args.force_kill: optimizer.kill_hanging_processes() return # Systemchecks optimizer.check_dependencies() optimizer.fix_ipv6_issues() # Port-Check if not optimizer.check_port_availability(args.host, args.port): print(f"❌ Port {args.port} ist bereits belegt!") print("💡 Versuche --force-kill um hängende Prozesse zu beenden") sys.exit(1) # Cleanup alter Prozesse optimizer.kill_hanging_processes() # App starten process = optimizer.start_with_gunicorn(args.host, args.port, args.debug) if process: try: # Warte auf CTRL+C print("\n⌨️ Drücke CTRL+C zum Beenden...") process.wait() except KeyboardInterrupt: print("\n🛑 CTRL+C erkannt, stoppe App...") optimizer.stop() else: print("❌ App-Start fehlgeschlagen!") sys.exit(1) if __name__ == "__main__": main()