327 lines
11 KiB
Python
327 lines
11 KiB
Python
#!/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() |