🎉 Improved backend functionality & documentation, optimized database files, and introduced shutdown management 🧹
This commit is contained in:
parent
ea12a470c9
commit
486647fade
204
backend/app.py
204
backend/app.py
@ -6538,85 +6538,49 @@ if __name__ == "__main__":
|
|||||||
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
os.environ['PYTHONIOENCODING'] = 'utf-8'
|
||||||
os.environ['PYTHONUTF8'] = '1'
|
os.environ['PYTHONUTF8'] = '1'
|
||||||
|
|
||||||
# Windows-spezifisches Signal-Handling für ordnungsgemäßes Shutdown
|
# ===== INITIALISIERE ZENTRALEN SHUTDOWN-MANAGER =====
|
||||||
def signal_handler(sig, frame):
|
try:
|
||||||
"""Signal-Handler für ordnungsgemäßes Shutdown."""
|
from utils.shutdown_manager import get_shutdown_manager
|
||||||
app_logger.warning(f"🛑 Signal {sig} empfangen - fahre System herunter...")
|
shutdown_manager = get_shutdown_manager(timeout=45) # 45 Sekunden Gesamt-Timeout
|
||||||
try:
|
app_logger.info("✅ Zentraler Shutdown-Manager initialisiert")
|
||||||
# Queue Manager stoppen
|
except ImportError as e:
|
||||||
app_logger.info("🔄 Beende Queue Manager...")
|
app_logger.error(f"❌ Shutdown-Manager konnte nicht geladen werden: {e}")
|
||||||
stop_queue_manager()
|
# Fallback auf die alte Methode
|
||||||
|
shutdown_manager = None
|
||||||
|
|
||||||
# Scheduler stoppen falls aktiviert
|
# Windows-spezifisches Signal-Handling als Fallback
|
||||||
if SCHEDULER_ENABLED and scheduler:
|
def fallback_signal_handler(sig, frame):
|
||||||
try:
|
"""Fallback Signal-Handler für ordnungsgemäßes Shutdown."""
|
||||||
scheduler.stop()
|
app_logger.warning(f"🛑 Signal {sig} empfangen - fahre System herunter (Fallback)...")
|
||||||
app_logger.info("Job-Scheduler gestoppt")
|
|
||||||
except Exception as e:
|
|
||||||
app_logger.error(f"Fehler beim Stoppen des Schedulers: {str(e)}")
|
|
||||||
|
|
||||||
# ===== ROBUSTES DATENBANK-CLEANUP MIT NEUER LOGIC =====
|
|
||||||
app_logger.info("💾 Führe robustes Datenbank-Cleanup durch...")
|
|
||||||
try:
|
try:
|
||||||
# Importiere und verwende den neuen DatabaseCleanupManager
|
# Queue Manager stoppen
|
||||||
from utils.database_cleanup import safe_database_cleanup
|
stop_queue_manager()
|
||||||
|
|
||||||
# Führe umfassendes, sicheres Cleanup durch
|
# Scheduler stoppen falls aktiviert
|
||||||
cleanup_result = safe_database_cleanup(force_mode_switch=True)
|
if SCHEDULER_ENABLED and scheduler:
|
||||||
|
try:
|
||||||
|
if hasattr(scheduler, 'shutdown'):
|
||||||
|
scheduler.shutdown(wait=True)
|
||||||
|
else:
|
||||||
|
scheduler.stop()
|
||||||
|
except Exception as e:
|
||||||
|
app_logger.error(f"Fehler beim Stoppen des Schedulers: {str(e)}")
|
||||||
|
|
||||||
if cleanup_result["success"]:
|
app_logger.info("✅ Fallback-Shutdown abgeschlossen")
|
||||||
app_logger.info(f"✅ Datenbank-Cleanup erfolgreich: {', '.join(cleanup_result['operations'])}")
|
sys.exit(0)
|
||||||
if cleanup_result.get("wal_files_removed", False):
|
except Exception as e:
|
||||||
app_logger.info("✅ WAL- und SHM-Dateien erfolgreich entfernt")
|
app_logger.error(f"❌ Fehler beim Fallback-Shutdown: {str(e)}")
|
||||||
else:
|
sys.exit(1)
|
||||||
app_logger.warning(f"⚠️ Datenbank-Cleanup mit Problemen: {', '.join(cleanup_result['errors'])}")
|
|
||||||
# Trotzdem weiter - wenigstens WAL-Checkpoint versucht
|
|
||||||
|
|
||||||
except ImportError:
|
# Signal-Handler registrieren (Windows-kompatibel)
|
||||||
# Fallback auf die alte Methode falls Cleanup-Manager nicht verfügbar
|
if os.name == 'nt': # Windows
|
||||||
app_logger.warning("Fallback: Verwende Legacy-Datenbank-Cleanup...")
|
signal.signal(signal.SIGINT, fallback_signal_handler)
|
||||||
try:
|
signal.signal(signal.SIGTERM, fallback_signal_handler)
|
||||||
from models import create_optimized_engine
|
signal.signal(signal.SIGBREAK, fallback_signal_handler)
|
||||||
from sqlalchemy import text
|
else: # Unix/Linux
|
||||||
|
signal.signal(signal.SIGINT, fallback_signal_handler)
|
||||||
engine = create_optimized_engine()
|
signal.signal(signal.SIGTERM, fallback_signal_handler)
|
||||||
|
signal.signal(signal.SIGHUP, fallback_signal_handler)
|
||||||
with engine.connect() as conn:
|
|
||||||
# Nur WAL-Checkpoint - kein risikoreicher Mode-Switch
|
|
||||||
app_logger.info("📝 Führe WAL-Checkpoint durch...")
|
|
||||||
result = conn.execute(text("PRAGMA wal_checkpoint(TRUNCATE)")).fetchone()
|
|
||||||
|
|
||||||
if result:
|
|
||||||
app_logger.info(f"WAL-Checkpoint abgeschlossen: {result[1]} Seiten übertragen, {result[2]} Seiten zurückgesetzt")
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# Engine-Connection-Pool schließen
|
|
||||||
engine.dispose()
|
|
||||||
app_logger.info("✅ Legacy-Datenbank-Cleanup abgeschlossen")
|
|
||||||
|
|
||||||
except Exception as db_error:
|
|
||||||
app_logger.error(f"❌ Fehler beim Legacy-Datenbank-Cleanup: {str(db_error)}")
|
|
||||||
|
|
||||||
except Exception as cleanup_error:
|
|
||||||
app_logger.error(f"❌ Fehler beim robusten Datenbank-Cleanup: {str(cleanup_error)}")
|
|
||||||
|
|
||||||
app_logger.info("✅ Shutdown abgeschlossen")
|
|
||||||
sys.exit(0)
|
|
||||||
except Exception as e:
|
|
||||||
app_logger.error(f"❌ Fehler beim Shutdown: {str(e)}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Signal-Handler registrieren (Windows-kompatibel)
|
|
||||||
if os.name == 'nt': # Windows
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
|
||||||
# Zusätzlich für Flask-Development-Server
|
|
||||||
signal.signal(signal.SIGBREAK, signal_handler)
|
|
||||||
else: # Unix/Linux
|
|
||||||
signal.signal(signal.SIGINT, signal_handler)
|
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
|
||||||
signal.signal(signal.SIGHUP, signal_handler)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Datenbank initialisieren und Migrationen durchführen
|
# Datenbank initialisieren und Migrationen durchführen
|
||||||
@ -6643,6 +6607,25 @@ if __name__ == "__main__":
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
app_logger.error(f"❌ Fehler bei automatischer Steckdosen-Initialisierung: {str(e)}")
|
app_logger.error(f"❌ Fehler bei automatischer Steckdosen-Initialisierung: {str(e)}")
|
||||||
|
|
||||||
|
# ===== SHUTDOWN-MANAGER KONFIGURATION =====
|
||||||
|
if shutdown_manager:
|
||||||
|
# Queue Manager beim Shutdown-Manager registrieren
|
||||||
|
try:
|
||||||
|
import utils.queue_manager as queue_module
|
||||||
|
shutdown_manager.register_queue_manager(queue_module)
|
||||||
|
app_logger.debug("✅ Queue Manager beim Shutdown-Manager registriert")
|
||||||
|
except Exception as e:
|
||||||
|
app_logger.warning(f"⚠️ Queue Manager Registrierung fehlgeschlagen: {e}")
|
||||||
|
|
||||||
|
# Scheduler beim Shutdown-Manager registrieren
|
||||||
|
shutdown_manager.register_scheduler(scheduler, SCHEDULER_ENABLED)
|
||||||
|
|
||||||
|
# Datenbank-Cleanup beim Shutdown-Manager registrieren
|
||||||
|
shutdown_manager.register_database_cleanup()
|
||||||
|
|
||||||
|
# Windows Thread Manager beim Shutdown-Manager registrieren
|
||||||
|
shutdown_manager.register_windows_thread_manager()
|
||||||
|
|
||||||
# Queue-Manager für automatische Drucker-Überwachung starten
|
# Queue-Manager für automatische Drucker-Überwachung starten
|
||||||
# Nur im Produktionsmodus starten (nicht im Debug-Modus)
|
# Nur im Produktionsmodus starten (nicht im Debug-Modus)
|
||||||
if not debug_mode:
|
if not debug_mode:
|
||||||
@ -6650,61 +6633,6 @@ if __name__ == "__main__":
|
|||||||
queue_manager = start_queue_manager()
|
queue_manager = start_queue_manager()
|
||||||
app_logger.info("✅ Printer Queue Manager erfolgreich gestartet")
|
app_logger.info("✅ Printer Queue Manager erfolgreich gestartet")
|
||||||
|
|
||||||
# Verbesserte Shutdown-Handler registrieren
|
|
||||||
def cleanup_queue_manager():
|
|
||||||
try:
|
|
||||||
app_logger.info("🔄 Beende Queue Manager...")
|
|
||||||
stop_queue_manager()
|
|
||||||
except Exception as e:
|
|
||||||
app_logger.error(f"❌ Fehler beim Queue Manager Cleanup: {str(e)}")
|
|
||||||
|
|
||||||
atexit.register(cleanup_queue_manager)
|
|
||||||
|
|
||||||
# ===== ROBUSTES DATENBANK-CLEANUP BEIM PROGRAMMENDE =====
|
|
||||||
def cleanup_database():
|
|
||||||
"""Führt robustes Datenbank-Cleanup beim normalen Programmende aus."""
|
|
||||||
try:
|
|
||||||
app_logger.info("💾 Führe finales robustes Datenbank-Cleanup durch...")
|
|
||||||
|
|
||||||
# Verwende den neuen DatabaseCleanupManager
|
|
||||||
try:
|
|
||||||
from utils.database_cleanup import safe_database_cleanup
|
|
||||||
|
|
||||||
# Führe umfassendes, sicheres Cleanup durch
|
|
||||||
cleanup_result = safe_database_cleanup(force_mode_switch=True)
|
|
||||||
|
|
||||||
if cleanup_result["success"]:
|
|
||||||
app_logger.info(f"✅ Finales Datenbank-Cleanup erfolgreich: {', '.join(cleanup_result['operations'])}")
|
|
||||||
if cleanup_result.get("wal_files_removed", False):
|
|
||||||
app_logger.info("✅ WAL- und SHM-Dateien erfolgreich entfernt")
|
|
||||||
else:
|
|
||||||
app_logger.warning(f"⚠️ Finales Datenbank-Cleanup mit Problemen: {', '.join(cleanup_result['errors'])}")
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
# Fallback auf die alte Methode falls Cleanup-Manager nicht verfügbar
|
|
||||||
app_logger.warning("Fallback: Verwende Legacy-finales-Datenbank-Cleanup...")
|
|
||||||
from models import create_optimized_engine
|
|
||||||
from sqlalchemy import text
|
|
||||||
|
|
||||||
engine = create_optimized_engine()
|
|
||||||
|
|
||||||
with engine.connect() as conn:
|
|
||||||
# Nur WAL-Checkpoint - kein risikoreicher Mode-Switch
|
|
||||||
result = conn.execute(text("PRAGMA wal_checkpoint(TRUNCATE)")).fetchone()
|
|
||||||
if result and result[1] > 0:
|
|
||||||
app_logger.info(f"Final WAL-Checkpoint: {result[1]} Seiten übertragen")
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# Connection-Pool ordnungsgemäß schließen
|
|
||||||
engine.dispose()
|
|
||||||
app_logger.info("✅ Legacy-finales Datenbank-Cleanup abgeschlossen")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
app_logger.error(f"❌ Fehler beim finalen robusten Datenbank-Cleanup: {str(e)}")
|
|
||||||
|
|
||||||
atexit.register(cleanup_database)
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}")
|
app_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}")
|
||||||
else:
|
else:
|
||||||
@ -6760,12 +6688,18 @@ if __name__ == "__main__":
|
|||||||
)
|
)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
app_logger.info("🔄 Tastatur-Unterbrechung empfangen - beende Anwendung...")
|
app_logger.info("🔄 Tastatur-Unterbrechung empfangen - beende Anwendung...")
|
||||||
signal_handler(signal.SIGINT, None)
|
if shutdown_manager:
|
||||||
|
shutdown_manager.shutdown()
|
||||||
|
else:
|
||||||
|
fallback_signal_handler(signal.SIGINT, None)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}")
|
app_logger.error(f"Fehler beim Starten der Anwendung: {str(e)}")
|
||||||
# Cleanup bei Fehler
|
# Cleanup bei Fehler
|
||||||
try:
|
if shutdown_manager:
|
||||||
stop_queue_manager()
|
shutdown_manager.force_shutdown(1)
|
||||||
except:
|
else:
|
||||||
pass
|
try:
|
||||||
sys.exit(1)
|
stop_queue_manager()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
sys.exit(1)
|
||||||
|
Binary file not shown.
Binary file not shown.
1
backend/docs/SHUTDOWN_VERBESSERUNGEN.md
Normal file
1
backend/docs/SHUTDOWN_VERBESSERUNGEN.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -4,7 +4,7 @@
|
|||||||
# MYP Druckerverwaltung - VOLLSTÄNDIGER KIOSK-INSTALLER für Raspbian
|
# MYP Druckerverwaltung - VOLLSTÄNDIGER KIOSK-INSTALLER für Raspbian
|
||||||
# Entwickelt auf Windows, ausführbar auf Raspberry Pi / Debian
|
# Entwickelt auf Windows, ausführbar auf Raspberry Pi / Debian
|
||||||
# OHNE virtualenv - verwendet System-Python mit --break-system-packages
|
# OHNE virtualenv - verwendet System-Python mit --break-system-packages
|
||||||
# ECHTER KIOSK-MODUS: Entfernt Desktop, 3 Backend-Instanzen, Autologin
|
# ECHTER KIOSK-MODUS: Entfernt Desktop, 1 Backend-Instanz HTTPS, Autologin
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@ -12,9 +12,7 @@ set -euo pipefail
|
|||||||
# =========================== KONFIGURATION ===========================
|
# =========================== KONFIGURATION ===========================
|
||||||
APP_NAME="MYP Druckerverwaltung"
|
APP_NAME="MYP Druckerverwaltung"
|
||||||
APP_DIR="/opt/myp"
|
APP_DIR="/opt/myp"
|
||||||
SERVICE_NAME_KIOSK="myp-kiosk"
|
SERVICE_NAME="myp-https"
|
||||||
SERVICE_NAME_HTTPS="myp-https"
|
|
||||||
SERVICE_NAME_HTTP="myp-http"
|
|
||||||
KIOSK_USER="kiosk"
|
KIOSK_USER="kiosk"
|
||||||
CURRENT_DIR="$(pwd)"
|
CURRENT_DIR="$(pwd)"
|
||||||
INSTALL_LOG="/var/log/myp-install.log"
|
INSTALL_LOG="/var/log/myp-install.log"
|
||||||
@ -579,14 +577,85 @@ install_system_dependencies() {
|
|||||||
log "✅ Alle Abhängigkeiten (Python + Node.js + npm) erfolgreich installiert"
|
log "✅ Alle Abhängigkeiten (Python + Node.js + npm) erfolgreich installiert"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ========================== 3 BACKEND SERVICES ERSTELLEN ==========================
|
# ========================== SSL-ZERTIFIKATE FÜR LOCALHOST ERSTELLEN ==========================
|
||||||
create_backend_services() {
|
create_localhost_ssl_certificates() {
|
||||||
log "=== ERSTELLE 3 BACKEND-SERVICES ==="
|
log "=== ERSTELLE SSL-ZERTIFIKATE FÜR LOCALHOST ==="
|
||||||
|
|
||||||
# Python-Startskripte für die verschiedenen Ports erstellen
|
SSL_DIR="$APP_DIR/certs/localhost"
|
||||||
progress "Erstelle Python-Startskripte..."
|
mkdir -p "$SSL_DIR"
|
||||||
|
|
||||||
|
progress "Erstelle SSL-Zertifikate für localhost..."
|
||||||
|
|
||||||
|
# Private Key erstellen
|
||||||
|
openssl genrsa -out "$SSL_DIR/localhost.key" 2048 || error "Private Key Erstellung fehlgeschlagen"
|
||||||
|
|
||||||
|
# Certificate Signing Request (CSR) erstellen
|
||||||
|
openssl req -new -key "$SSL_DIR/localhost.key" -out "$SSL_DIR/localhost.csr" -subj "/C=DE/ST=Baden-Wuerttemberg/L=Stuttgart/O=Mercedes-Benz/OU=MYP/CN=localhost" || error "CSR Erstellung fehlgeschlagen"
|
||||||
|
|
||||||
|
# Self-signed Zertifikat erstellen (gültig für 365 Tage)
|
||||||
|
openssl x509 -req -days 365 -in "$SSL_DIR/localhost.csr" -signkey "$SSL_DIR/localhost.key" -out "$SSL_DIR/localhost.crt" -extensions v3_req -extfile <(
|
||||||
|
cat <<EOF
|
||||||
|
[req]
|
||||||
|
distinguished_name = req_distinguished_name
|
||||||
|
req_extensions = v3_req
|
||||||
|
prompt = no
|
||||||
|
|
||||||
|
[req_distinguished_name]
|
||||||
|
C = DE
|
||||||
|
ST = Baden-Wuerttemberg
|
||||||
|
L = Stuttgart
|
||||||
|
O = Mercedes-Benz
|
||||||
|
OU = MYP
|
||||||
|
CN = localhost
|
||||||
|
|
||||||
|
[v3_req]
|
||||||
|
keyUsage = keyEncipherment, dataEncipherment
|
||||||
|
extendedKeyUsage = serverAuth
|
||||||
|
subjectAltName = @alt_names
|
||||||
|
|
||||||
|
[alt_names]
|
||||||
|
DNS.1 = localhost
|
||||||
|
DNS.2 = *.localhost
|
||||||
|
IP.1 = 127.0.0.1
|
||||||
|
IP.2 = 0.0.0.0
|
||||||
|
EOF
|
||||||
|
) || error "SSL-Zertifikat Erstellung fehlgeschlagen"
|
||||||
|
|
||||||
|
# CSR-Datei aufräumen
|
||||||
|
rm -f "$SSL_DIR/localhost.csr"
|
||||||
|
|
||||||
|
# Berechtigungen setzen
|
||||||
|
chmod 600 "$SSL_DIR/localhost.key"
|
||||||
|
chmod 644 "$SSL_DIR/localhost.crt"
|
||||||
|
chown -R root:root "$SSL_DIR"
|
||||||
|
|
||||||
|
# Zertifikat zu System CA-Store hinzufügen
|
||||||
|
progress "Füge localhost-Zertifikat zu System CA-Store hinzu..."
|
||||||
|
cp "$SSL_DIR/localhost.crt" "/usr/local/share/ca-certificates/localhost.crt"
|
||||||
|
update-ca-certificates || warning "CA-Zertifikate Update fehlgeschlagen"
|
||||||
|
|
||||||
|
# Zertifikat-Info anzeigen
|
||||||
|
log "✅ SSL-Zertifikate für localhost erstellt:"
|
||||||
|
log " 📜 Private Key: $SSL_DIR/localhost.key"
|
||||||
|
log " 📜 Zertifikat: $SSL_DIR/localhost.crt"
|
||||||
|
log " 📜 System CA-Store: /usr/local/share/ca-certificates/localhost.crt"
|
||||||
|
|
||||||
|
# Zertifikat validieren
|
||||||
|
if openssl x509 -in "$SSL_DIR/localhost.crt" -text -noout | grep -q "localhost"; then
|
||||||
|
log "✅ SSL-Zertifikat validiert - localhost Subject gefunden"
|
||||||
|
else
|
||||||
|
warning "⚠️ SSL-Zertifikat Validierung fehlgeschlagen"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "✅ SSL-Zertifikate für localhost erfolgreich installiert"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========================== EINZELNER HTTPS BACKEND SERVICE ==========================
|
||||||
|
create_backend_service() {
|
||||||
|
log "=== ERSTELLE HTTPS BACKEND SERVICE ==="
|
||||||
|
|
||||||
# HTTPS-Startskript (Port 443)
|
# HTTPS-Startskript (Port 443)
|
||||||
|
progress "Erstelle HTTPS-Startskript..."
|
||||||
cat > "$APP_DIR/start_https.py" << 'EOF'
|
cat > "$APP_DIR/start_https.py" << 'EOF'
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import sys
|
import sys
|
||||||
@ -608,88 +677,21 @@ try:
|
|||||||
print("Starte HTTPS-Server auf Port 443...")
|
print("Starte HTTPS-Server auf Port 443...")
|
||||||
app.run(host='0.0.0.0', port=443, debug=False, ssl_context=ssl_context, threaded=True)
|
app.run(host='0.0.0.0', port=443, debug=False, ssl_context=ssl_context, threaded=True)
|
||||||
else:
|
else:
|
||||||
print('SSL-Kontext nicht verfügbar')
|
print('SSL-Kontext nicht verfügbar - verwende localhost Zertifikate')
|
||||||
sys.exit(1)
|
# Fallback auf localhost-Zertifikate
|
||||||
|
ssl_context = ('/opt/myp/certs/localhost/localhost.crt', '/opt/myp/certs/localhost/localhost.key')
|
||||||
|
app.run(host='0.0.0.0', port=443, debug=False, ssl_context=ssl_context, threaded=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Fehler beim Starten des HTTPS-Servers: {e}")
|
print(f"Fehler beim Starten des HTTPS-Servers: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# HTTP-Startskript (Port 80)
|
# Skript ausführbar machen
|
||||||
cat > "$APP_DIR/start_http.py" << 'EOF'
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
# Füge App-Verzeichnis zum Python-Pfad hinzu
|
|
||||||
sys.path.insert(0, '/opt/myp')
|
|
||||||
|
|
||||||
# Setze Umgebungsvariablen
|
|
||||||
os.environ['FLASK_PORT'] = '80'
|
|
||||||
os.environ['FLASK_HOST'] = '0.0.0.0'
|
|
||||||
os.environ['FLASK_ENV'] = 'production'
|
|
||||||
|
|
||||||
try:
|
|
||||||
from app import app
|
|
||||||
|
|
||||||
print("Starte HTTP-Server auf Port 80...")
|
|
||||||
app.run(host='0.0.0.0', port=80, debug=False, threaded=True)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Fehler beim Starten des HTTP-Servers: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Skripte ausführbar machen
|
|
||||||
chmod +x "$APP_DIR/start_https.py"
|
chmod +x "$APP_DIR/start_https.py"
|
||||||
chmod +x "$APP_DIR/start_http.py"
|
|
||||||
|
|
||||||
# Service 1: Kiosk-Backend (Port 5000)
|
# HTTPS-Service (Port 443)
|
||||||
progress "Erstelle myp-kiosk.service (Port 5000)..."
|
|
||||||
cat > "/etc/systemd/system/${SERVICE_NAME_KIOSK}.service" << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=MYP Kiosk Backend (Port 5000)
|
|
||||||
After=network.target network-online.target
|
|
||||||
Wants=network-online.target
|
|
||||||
Requires=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=root
|
|
||||||
Group=root
|
|
||||||
WorkingDirectory=$APP_DIR
|
|
||||||
ExecStart=/usr/bin/python3 $APP_DIR/app.py --debug
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
StartLimitBurst=5
|
|
||||||
StartLimitInterval=60
|
|
||||||
|
|
||||||
# Umgebungsvariablen
|
|
||||||
Environment=PYTHONUNBUFFERED=1
|
|
||||||
Environment=FLASK_ENV=production
|
|
||||||
Environment=FLASK_HOST=0.0.0.0
|
|
||||||
Environment=FLASK_PORT=5000
|
|
||||||
Environment=PYTHONPATH=$APP_DIR
|
|
||||||
Environment=LC_ALL=C.UTF-8
|
|
||||||
Environment=LANG=C.UTF-8
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
StandardOutput=journal
|
|
||||||
StandardError=journal
|
|
||||||
SyslogIdentifier=myp-kiosk
|
|
||||||
|
|
||||||
# Security-Einstellungen
|
|
||||||
NoNewPrivileges=true
|
|
||||||
PrivateTmp=false
|
|
||||||
ProtectSystem=strict
|
|
||||||
ReadWritePaths=$APP_DIR
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Service 2: HTTPS-Backend (Port 443)
|
|
||||||
progress "Erstelle myp-https.service (Port 443)..."
|
progress "Erstelle myp-https.service (Port 443)..."
|
||||||
cat > "/etc/systemd/system/${SERVICE_NAME_HTTPS}.service" << EOF
|
cat > "/etc/systemd/system/${SERVICE_NAME}.service" << EOF
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=MYP HTTPS Backend (Port 443)
|
Description=MYP HTTPS Backend (Port 443)
|
||||||
After=network.target network-online.target
|
After=network.target network-online.target
|
||||||
@ -715,6 +717,8 @@ Environment=FLASK_PORT=443
|
|||||||
Environment=PYTHONPATH=$APP_DIR
|
Environment=PYTHONPATH=$APP_DIR
|
||||||
Environment=LC_ALL=C.UTF-8
|
Environment=LC_ALL=C.UTF-8
|
||||||
Environment=LANG=C.UTF-8
|
Environment=LANG=C.UTF-8
|
||||||
|
Environment=SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||||
|
Environment=REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
StandardOutput=journal
|
StandardOutput=journal
|
||||||
@ -731,76 +735,12 @@ ReadWritePaths=$APP_DIR
|
|||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Service 3: HTTP-Backend (Port 80)
|
log "✅ HTTPS Backend-Service erstellt"
|
||||||
progress "Erstelle myp-http.service (Port 80)..."
|
|
||||||
cat > "/etc/systemd/system/${SERVICE_NAME_HTTP}.service" << EOF
|
|
||||||
[Unit]
|
|
||||||
Description=MYP HTTP Backend (Port 80)
|
|
||||||
After=network.target network-online.target
|
|
||||||
Wants=network-online.target
|
|
||||||
Requires=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=root
|
|
||||||
Group=root
|
|
||||||
WorkingDirectory=$APP_DIR
|
|
||||||
ExecStart=/usr/bin/python3 $APP_DIR/start_http.py
|
|
||||||
Restart=always
|
|
||||||
RestartSec=5
|
|
||||||
StartLimitBurst=5
|
|
||||||
StartLimitInterval=60
|
|
||||||
|
|
||||||
# Umgebungsvariablen
|
|
||||||
Environment=PYTHONUNBUFFERED=1
|
|
||||||
Environment=FLASK_ENV=production
|
|
||||||
Environment=FLASK_HOST=0.0.0.0
|
|
||||||
Environment=FLASK_PORT=80
|
|
||||||
Environment=PYTHONPATH=$APP_DIR
|
|
||||||
Environment=LC_ALL=C.UTF-8
|
|
||||||
Environment=LANG=C.UTF-8
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
StandardOutput=journal
|
|
||||||
StandardError=journal
|
|
||||||
SyslogIdentifier=myp-http
|
|
||||||
|
|
||||||
# Security-Einstellungen
|
|
||||||
NoNewPrivileges=true
|
|
||||||
PrivateTmp=false
|
|
||||||
ProtectSystem=strict
|
|
||||||
ReadWritePaths=$APP_DIR
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
EOF
|
|
||||||
|
|
||||||
log "✅ 3 Backend-Services mit separaten Python-Skripten erstellt"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ========================== AUTOLOGIN KONFIGURIEREN ==========================
|
|
||||||
configure_autologin() {
|
|
||||||
log "=== KONFIGURIERE AUTOLOGIN ==="
|
|
||||||
|
|
||||||
progress "Erstelle Autologin-Service..."
|
|
||||||
|
|
||||||
# Getty-Service für automatischen Login überschreiben
|
|
||||||
mkdir -p /etc/systemd/system/getty@tty1.service.d/
|
|
||||||
cat > /etc/systemd/system/getty@tty1.service.d/autologin.conf << EOF
|
|
||||||
[Service]
|
|
||||||
ExecStart=
|
|
||||||
ExecStart=-/sbin/agetty --autologin $KIOSK_USER --noclear %I linux
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Systemd Target auf Multi-User setzen (ohne grafisches Login)
|
|
||||||
systemctl set-default multi-user.target
|
|
||||||
|
|
||||||
log "✅ Autologin konfiguriert für Benutzer: $KIOSK_USER"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# ========================== KIOSK BROWSER KONFIGURATION ==========================
|
# ========================== KIOSK BROWSER KONFIGURATION ==========================
|
||||||
configure_kiosk_browser() {
|
configure_kiosk_browser() {
|
||||||
log "=== KONFIGURIERE KIOSK-BROWSER ==="
|
log "=== KONFIGURIERE KIOSK-BROWSER FÜR HTTPS ==="
|
||||||
|
|
||||||
KIOSK_HOME="/home/$KIOSK_USER"
|
KIOSK_HOME="/home/$KIOSK_USER"
|
||||||
|
|
||||||
@ -918,8 +858,8 @@ if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then
|
|||||||
fi
|
fi
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# .xinitrc für Kiosk-Session erstellen
|
# .xinitrc für HTTPS Kiosk-Session erstellen
|
||||||
progress "Erstelle optimierte Kiosk X-Session..."
|
progress "Erstelle optimierte HTTPS Kiosk X-Session..."
|
||||||
cat > "$KIOSK_HOME/.xinitrc" << 'EOF'
|
cat > "$KIOSK_HOME/.xinitrc" << 'EOF'
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
@ -946,31 +886,30 @@ unclutter -idle 0.1 -root -noevents &
|
|||||||
# Openbox im Hintergrund starten
|
# Openbox im Hintergrund starten
|
||||||
openbox &
|
openbox &
|
||||||
|
|
||||||
# Warte auf Backend-Services
|
# Warte auf HTTPS Backend-Service
|
||||||
echo "Warte auf MYP Backend-Services..."
|
echo "Warte auf MYP HTTPS Backend-Service..."
|
||||||
WAIT_COUNT=0
|
WAIT_COUNT=0
|
||||||
while ! curl -s http://localhost:5000 > /dev/null; do
|
while ! curl -k -s https://localhost:443 > /dev/null; do
|
||||||
echo "Warte auf Backend (Port 5000)... ($WAIT_COUNT/60)"
|
echo "Warte auf HTTPS Backend (Port 443)... ($WAIT_COUNT/60)"
|
||||||
sleep 2
|
sleep 2
|
||||||
WAIT_COUNT=$((WAIT_COUNT + 1))
|
WAIT_COUNT=$((WAIT_COUNT + 1))
|
||||||
if [ $WAIT_COUNT -gt 60 ]; then
|
if [ $WAIT_COUNT -gt 60 ]; then
|
||||||
echo "FEHLER: Backend nach 120s nicht erreichbar!"
|
echo "FEHLER: HTTPS Backend nach 120s nicht erreichbar!"
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Backend erreichbar - starte Kiosk-Browser..."
|
echo "HTTPS Backend erreichbar - starte Kiosk-Browser..."
|
||||||
|
|
||||||
# Browser-Erkennung und -Start mit optimierten Vollbild-Flags
|
# Browser-Erkennung und -Start mit HTTPS-optimierten Vollbild-Flags
|
||||||
if command -v chromium >/dev/null 2>&1; then
|
if command -v chromium >/dev/null 2>&1; then
|
||||||
echo "Starte Chromium Browser mit Auflösung ${WIDTH}x${HEIGHT}..."
|
echo "Starte Chromium Browser mit HTTPS und Auflösung ${WIDTH}x${HEIGHT}..."
|
||||||
exec chromium \
|
exec chromium \
|
||||||
--kiosk \
|
--kiosk \
|
||||||
--no-sandbox \
|
--no-sandbox \
|
||||||
--disable-infobars \
|
--disable-infobars \
|
||||||
--disable-session-crashed-bubble \
|
--disable-session-crashed-bubble \
|
||||||
--disable-restore-session-state \
|
--disable-restore-session-state \
|
||||||
--disable-web-security \
|
|
||||||
--disable-features=TranslateUI \
|
--disable-features=TranslateUI \
|
||||||
--disable-extensions \
|
--disable-extensions \
|
||||||
--disable-plugins \
|
--disable-plugins \
|
||||||
@ -1000,16 +939,21 @@ if command -v chromium >/dev/null 2>&1; then
|
|||||||
--disable-features=VizDisplayCompositor \
|
--disable-features=VizDisplayCompositor \
|
||||||
--enable-features=OverlayScrollbar \
|
--enable-features=OverlayScrollbar \
|
||||||
--hide-scrollbars \
|
--hide-scrollbars \
|
||||||
http://localhost:5000
|
--ignore-certificate-errors \
|
||||||
|
--ignore-ssl-errors \
|
||||||
|
--ignore-certificate-errors-spki-list \
|
||||||
|
--disable-web-security \
|
||||||
|
--allow-running-insecure-content \
|
||||||
|
--unsafely-treat-insecure-origin-as-secure=https://localhost:443 \
|
||||||
|
https://localhost:443
|
||||||
elif command -v chromium-browser >/dev/null 2>&1; then
|
elif command -v chromium-browser >/dev/null 2>&1; then
|
||||||
echo "Starte Chromium-Browser mit Auflösung ${WIDTH}x${HEIGHT}..."
|
echo "Starte Chromium-Browser mit HTTPS und Auflösung ${WIDTH}x${HEIGHT}..."
|
||||||
exec chromium-browser \
|
exec chromium-browser \
|
||||||
--kiosk \
|
--kiosk \
|
||||||
--no-sandbox \
|
--no-sandbox \
|
||||||
--disable-infobars \
|
--disable-infobars \
|
||||||
--disable-session-crashed-bubble \
|
--disable-session-crashed-bubble \
|
||||||
--disable-restore-session-state \
|
--disable-restore-session-state \
|
||||||
--disable-web-security \
|
|
||||||
--disable-features=TranslateUI \
|
--disable-features=TranslateUI \
|
||||||
--disable-extensions \
|
--disable-extensions \
|
||||||
--disable-plugins \
|
--disable-plugins \
|
||||||
@ -1039,18 +983,26 @@ elif command -v chromium-browser >/dev/null 2>&1; then
|
|||||||
--disable-features=VizDisplayCompositor \
|
--disable-features=VizDisplayCompositor \
|
||||||
--enable-features=OverlayScrollbar \
|
--enable-features=OverlayScrollbar \
|
||||||
--hide-scrollbars \
|
--hide-scrollbars \
|
||||||
http://localhost:5000
|
--ignore-certificate-errors \
|
||||||
|
--ignore-ssl-errors \
|
||||||
|
--ignore-certificate-errors-spki-list \
|
||||||
|
--disable-web-security \
|
||||||
|
--allow-running-insecure-content \
|
||||||
|
--unsafely-treat-insecure-origin-as-secure=https://localhost:443 \
|
||||||
|
https://localhost:443
|
||||||
elif command -v firefox-esr >/dev/null 2>&1; then
|
elif command -v firefox-esr >/dev/null 2>&1; then
|
||||||
echo "Starte Firefox ESR mit Auflösung ${WIDTH}x${HEIGHT}..."
|
echo "Starte Firefox ESR mit HTTPS und Auflösung ${WIDTH}x${HEIGHT}..."
|
||||||
# Firefox-Profil für Kiosk erstellen
|
# Firefox-Profil für HTTPS Kiosk erstellen
|
||||||
mkdir -p /home/kiosk/.mozilla/firefox/kiosk.default
|
mkdir -p /home/kiosk/.mozilla/firefox/kiosk.default
|
||||||
cat > /home/kiosk/.mozilla/firefox/kiosk.default/user.js << FIREFOXEOF
|
cat > /home/kiosk/.mozilla/firefox/kiosk.default/user.js << FIREFOXEOF
|
||||||
user_pref("browser.shell.checkDefaultBrowser", false);
|
user_pref("browser.shell.checkDefaultBrowser", false);
|
||||||
user_pref("browser.startup.homepage", "http://localhost:5000");
|
user_pref("browser.startup.homepage", "https://localhost:443");
|
||||||
user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true);
|
user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true);
|
||||||
user_pref("browser.tabs.warnOnClose", false);
|
user_pref("browser.tabs.warnOnClose", false);
|
||||||
user_pref("browser.sessionstore.resume_from_crash", false);
|
user_pref("browser.sessionstore.resume_from_crash", false);
|
||||||
user_pref("security.tls.insecure_fallback_hosts", "localhost");
|
user_pref("security.tls.insecure_fallback_hosts", "localhost");
|
||||||
|
user_pref("security.mixed_content.block_active_content", false);
|
||||||
|
user_pref("security.mixed_content.block_display_content", false);
|
||||||
user_pref("browser.cache.disk.enable", false);
|
user_pref("browser.cache.disk.enable", false);
|
||||||
user_pref("browser.cache.memory.enable", true);
|
user_pref("browser.cache.memory.enable", true);
|
||||||
user_pref("browser.cache.offline.enable", false);
|
user_pref("browser.cache.offline.enable", false);
|
||||||
@ -1059,6 +1011,8 @@ user_pref("browser.fullscreen.autohide", true);
|
|||||||
user_pref("dom.disable_open_during_load", false);
|
user_pref("dom.disable_open_during_load", false);
|
||||||
user_pref("privacy.popups.disable_from_plugins", 0);
|
user_pref("privacy.popups.disable_from_plugins", 0);
|
||||||
user_pref("dom.popup_maximum", 0);
|
user_pref("dom.popup_maximum", 0);
|
||||||
|
user_pref("security.cert_pinning.enforcement_level", 0);
|
||||||
|
user_pref("security.tls.unrestricted_rc4_fallback", true);
|
||||||
FIREFOXEOF
|
FIREFOXEOF
|
||||||
|
|
||||||
# Firefox CSS für randlosen Vollbildmodus
|
# Firefox CSS für randlosen Vollbildmodus
|
||||||
@ -1082,7 +1036,7 @@ FIREFOXCSS
|
|||||||
--width=${WIDTH} \
|
--width=${WIDTH} \
|
||||||
--height=${HEIGHT} \
|
--height=${HEIGHT} \
|
||||||
--profile /home/kiosk/.mozilla/firefox/kiosk.default \
|
--profile /home/kiosk/.mozilla/firefox/kiosk.default \
|
||||||
http://localhost:5000
|
https://localhost:443
|
||||||
else
|
else
|
||||||
echo "FEHLER: Kein Browser verfügbar!"
|
echo "FEHLER: Kein Browser verfügbar!"
|
||||||
exit 1
|
exit 1
|
||||||
@ -1093,12 +1047,73 @@ EOF
|
|||||||
chmod +x "$KIOSK_HOME/.xinitrc"
|
chmod +x "$KIOSK_HOME/.xinitrc"
|
||||||
chown -R "$KIOSK_USER:$KIOSK_USER" "$KIOSK_HOME"
|
chown -R "$KIOSK_USER:$KIOSK_USER" "$KIOSK_HOME"
|
||||||
|
|
||||||
log "✅ Kiosk-Browser mit optimiertem Vollbildmodus konfiguriert"
|
log "✅ Kiosk-Browser mit HTTPS-Vollbildmodus konfiguriert"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ========================== APP.PY SSL-UNTERSTÜTZUNG PRÜFEN ==========================
|
||||||
|
verify_app_ssl_support() {
|
||||||
|
log "=== PRÜFE APP.PY SSL-UNTERSTÜTZUNG ==="
|
||||||
|
|
||||||
|
if [ ! -f "$APP_DIR/app.py" ]; then
|
||||||
|
error "app.py nicht gefunden in $APP_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prüfe ob get_ssl_context Funktion existiert
|
||||||
|
if grep -q "def get_ssl_context" "$APP_DIR/app.py"; then
|
||||||
|
log "✅ get_ssl_context Funktion bereits vorhanden in app.py"
|
||||||
|
else
|
||||||
|
progress "Füge SSL-Unterstützung zu app.py hinzu..."
|
||||||
|
|
||||||
|
# Backup der originalen app.py
|
||||||
|
cp "$APP_DIR/app.py" "$APP_DIR/app.py.backup.$(date +%s)"
|
||||||
|
|
||||||
|
# SSL-Funktion am Ende der Datei hinzufügen (vor dem if __name__ == '__main__' Block)
|
||||||
|
cat >> "$APP_DIR/app.py" << 'EOF'
|
||||||
|
|
||||||
|
def get_ssl_context():
|
||||||
|
"""
|
||||||
|
SSL-Kontext für HTTPS-Server erstellen
|
||||||
|
Verwendet localhost-Zertifikate falls verfügbar
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
ssl_cert_path = '/opt/myp/certs/localhost/localhost.crt'
|
||||||
|
ssl_key_path = '/opt/myp/certs/localhost/localhost.key'
|
||||||
|
|
||||||
|
if os.path.exists(ssl_cert_path) and os.path.exists(ssl_key_path):
|
||||||
|
try:
|
||||||
|
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||||
|
context.load_cert_chain(ssl_cert_path, ssl_key_path)
|
||||||
|
context.check_hostname = False
|
||||||
|
context.verify_mode = ssl.CERT_NONE
|
||||||
|
print(f"✅ SSL-Kontext erstellt mit: {ssl_cert_path}")
|
||||||
|
return context
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Fehler beim Erstellen des SSL-Kontexts: {e}")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
print(f"❌ SSL-Zertifikate nicht gefunden:")
|
||||||
|
print(f" Cert: {ssl_cert_path}")
|
||||||
|
print(f" Key: {ssl_key_path}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
log "✅ SSL-Unterstützung zu app.py hinzugefügt"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prüfe ob SSL-Imports vorhanden sind
|
||||||
|
if ! grep -q "import ssl" "$APP_DIR/app.py"; then
|
||||||
|
progress "Füge SSL-Import zu app.py hinzu..."
|
||||||
|
sed -i '1i import ssl' "$APP_DIR/app.py"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "✅ app.py SSL-Unterstützung verifiziert"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ========================== PRODUKTIONS-KIOSK SETUP ==========================
|
# ========================== PRODUKTIONS-KIOSK SETUP ==========================
|
||||||
setup_production_kiosk() {
|
setup_production_kiosk() {
|
||||||
log "=== RICHTE PRODUKTIONS-KIOSK-MODUS EIN ==="
|
log "=== RICHTE PRODUKTIONS-KIOSK-MODUS MIT HTTPS EIN ==="
|
||||||
|
|
||||||
# 1. System-Abhängigkeiten installieren
|
# 1. System-Abhängigkeiten installieren
|
||||||
install_system_dependencies
|
install_system_dependencies
|
||||||
@ -1161,6 +1176,7 @@ setup_production_kiosk() {
|
|||||||
mkdir -p "$APP_DIR/logs/auth"
|
mkdir -p "$APP_DIR/logs/auth"
|
||||||
mkdir -p "$APP_DIR/logs/errors"
|
mkdir -p "$APP_DIR/logs/errors"
|
||||||
mkdir -p "$APP_DIR/uploads/temp"
|
mkdir -p "$APP_DIR/uploads/temp"
|
||||||
|
mkdir -p "$APP_DIR/certs/localhost"
|
||||||
|
|
||||||
# Berechtigungen setzen
|
# Berechtigungen setzen
|
||||||
chown -R root:root "$APP_DIR"
|
chown -R root:root "$APP_DIR"
|
||||||
@ -1168,65 +1184,70 @@ setup_production_kiosk() {
|
|||||||
chmod 750 "$APP_DIR/database"
|
chmod 750 "$APP_DIR/database"
|
||||||
chmod 750 "$APP_DIR/logs"
|
chmod 750 "$APP_DIR/logs"
|
||||||
chmod 755 "$APP_DIR/uploads"
|
chmod 755 "$APP_DIR/uploads"
|
||||||
|
chmod 750 "$APP_DIR/certs"
|
||||||
|
|
||||||
# 6. Backend-Services erstellen
|
# 6. App.py SSL-Unterstützung prüfen und hinzufügen
|
||||||
create_backend_services
|
verify_app_ssl_support
|
||||||
|
|
||||||
# 7. Autologin konfigurieren
|
# 7. SSL-Zertifikate für localhost erstellen
|
||||||
|
create_localhost_ssl_certificates
|
||||||
|
|
||||||
|
# 8. Backend-Service erstellen
|
||||||
|
create_backend_service
|
||||||
|
|
||||||
|
# 9. Autologin konfigurieren
|
||||||
configure_autologin
|
configure_autologin
|
||||||
|
|
||||||
# 8. Kiosk-Browser konfigurieren
|
# 10. Kiosk-Browser konfigurieren
|
||||||
configure_kiosk_browser
|
configure_kiosk_browser
|
||||||
|
|
||||||
# 9. Services aktivieren und starten
|
# 11. Service aktivieren und starten
|
||||||
progress "Lade Systemd-Konfiguration neu..."
|
progress "Lade Systemd-Konfiguration neu..."
|
||||||
systemctl daemon-reload || error "Systemd Reload fehlgeschlagen"
|
systemctl daemon-reload || error "Systemd Reload fehlgeschlagen"
|
||||||
|
|
||||||
progress "Aktiviere alle Backend-Services..."
|
progress "Aktiviere HTTPS Backend-Service..."
|
||||||
systemctl enable "$SERVICE_NAME_KIOSK.service" || error "Kiosk-Service Enable fehlgeschlagen"
|
systemctl enable "$SERVICE_NAME.service" || error "HTTPS-Service Enable fehlgeschlagen"
|
||||||
systemctl enable "$SERVICE_NAME_HTTPS.service" || error "HTTPS-Service Enable fehlgeschlagen"
|
|
||||||
systemctl enable "$SERVICE_NAME_HTTP.service" || error "HTTP-Service Enable fehlgeschlagen"
|
|
||||||
|
|
||||||
progress "Starte alle Backend-Services..."
|
progress "Starte HTTPS Backend-Service..."
|
||||||
systemctl start "$SERVICE_NAME_KIOSK.service" || error "Kiosk-Service Start fehlgeschlagen"
|
systemctl start "$SERVICE_NAME.service" || error "HTTPS-Service Start fehlgeschlagen"
|
||||||
systemctl start "$SERVICE_NAME_HTTPS.service" || warning "HTTPS-Service Start fehlgeschlagen (SSL möglicherweise nicht konfiguriert)"
|
|
||||||
systemctl start "$SERVICE_NAME_HTTP.service" || error "HTTP-Service Start fehlgeschlagen"
|
|
||||||
|
|
||||||
# Service-Status prüfen
|
# Service-Status prüfen
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
info "=== SERVICE-STATUS ==="
|
info "=== SERVICE-STATUS ==="
|
||||||
for service in "$SERVICE_NAME_KIOSK" "$SERVICE_NAME_HTTPS" "$SERVICE_NAME_HTTP"; do
|
if systemctl is-active --quiet "$SERVICE_NAME.service"; then
|
||||||
if systemctl is-active --quiet "$service.service"; then
|
log "✅ $SERVICE_NAME Service läuft erfolgreich"
|
||||||
log "✅ $service Service läuft erfolgreich"
|
else
|
||||||
else
|
warning "⚠️ $SERVICE_NAME Service läuft nicht - prüfen Sie die Logs: journalctl -u $SERVICE_NAME -f"
|
||||||
warning "⚠️ $service Service läuft nicht - prüfen Sie die Logs: journalctl -u $service -f"
|
fi
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Backend-Tests
|
# Backend-Tests
|
||||||
progress "Teste Backend-Erreichbarkeit..."
|
progress "Teste HTTPS Backend-Erreichbarkeit..."
|
||||||
sleep 3
|
sleep 3
|
||||||
|
|
||||||
if curl -s http://localhost:5000 > /dev/null 2>&1; then
|
|
||||||
log "✅ Port 5000 (Kiosk) erreichbar"
|
|
||||||
else
|
|
||||||
warning "⚠️ Port 5000 (Kiosk) nicht erreichbar"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if curl -s http://localhost:80 > /dev/null 2>&1; then
|
|
||||||
log "✅ Port 80 (HTTP) erreichbar"
|
|
||||||
else
|
|
||||||
warning "⚠️ Port 80 (HTTP) nicht erreichbar"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if curl -k -s https://localhost:443 > /dev/null 2>&1; then
|
if curl -k -s https://localhost:443 > /dev/null 2>&1; then
|
||||||
log "✅ Port 443 (HTTPS) erreichbar"
|
log "✅ Port 443 (HTTPS) erreichbar"
|
||||||
else
|
else
|
||||||
warning "⚠️ Port 443 (HTTPS) nicht erreichbar (SSL möglicherweise nicht konfiguriert)"
|
warning "⚠️ Port 443 (HTTPS) nicht erreichbar"
|
||||||
|
# Zusätzliche Debug-Information
|
||||||
|
progress "Versuche SSL-Zertifikat zu testen..."
|
||||||
|
if openssl s_client -connect localhost:443 -servername localhost < /dev/null 2>/dev/null | grep -q "CONNECTED"; then
|
||||||
|
log "✅ SSL-Verbindung funktioniert"
|
||||||
|
else
|
||||||
|
warning "⚠️ SSL-Verbindung fehlgeschlagen"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log "✅ PRODUKTIONS-KIOSK-MODUS ERFOLGREICH EINGERICHTET"
|
# SSL-Zertifikat Status
|
||||||
|
if [ -f "$APP_DIR/certs/localhost/localhost.crt" ]; then
|
||||||
|
log "✅ SSL-Zertifikat vorhanden: $APP_DIR/certs/localhost/localhost.crt"
|
||||||
|
CERT_EXPIRY=$(openssl x509 -in "$APP_DIR/certs/localhost/localhost.crt" -noout -enddate | cut -d= -f2)
|
||||||
|
log "📅 Zertifikat läuft ab: $CERT_EXPIRY"
|
||||||
|
else
|
||||||
|
warning "⚠️ SSL-Zertifikat nicht gefunden"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "✅ PRODUKTIONS-KIOSK-MODUS MIT HTTPS ERFOLGREICH EINGERICHTET"
|
||||||
log ""
|
log ""
|
||||||
log "🚀 WICHTIG: NEUSTART ERFORDERLICH!"
|
log "🚀 WICHTIG: NEUSTART ERFORDERLICH!"
|
||||||
log " sudo reboot"
|
log " sudo reboot"
|
||||||
@ -1234,30 +1255,36 @@ setup_production_kiosk() {
|
|||||||
log "📊 NACH DEM NEUSTART:"
|
log "📊 NACH DEM NEUSTART:"
|
||||||
log " • Automatischer Login als Benutzer: $KIOSK_USER"
|
log " • Automatischer Login als Benutzer: $KIOSK_USER"
|
||||||
log " • Automatischer X-Start und Chromium-Kiosk"
|
log " • Automatischer X-Start und Chromium-Kiosk"
|
||||||
log " • Backend läuft auf 3 Ports:"
|
log " • Backend läuft auf HTTPS:"
|
||||||
log " - http://localhost:5000 (Kiosk-Anzeige)"
|
log " - https://localhost:443 (Kiosk-Anzeige mit SSL)"
|
||||||
log " - http://localhost:80 (HTTP-API)"
|
log " - https://0.0.0.0:443 (Netzwerk-Zugriff)"
|
||||||
log " - https://localhost:443 (HTTPS-API)"
|
log ""
|
||||||
|
log "🔐 SSL-ZERTIFIKATE:"
|
||||||
|
log " • Self-Signed Zertifikat für localhost erstellt"
|
||||||
|
log " • Chromium akzeptiert Zertifikat automatisch"
|
||||||
|
log " • Zertifikat im System CA-Store installiert"
|
||||||
log ""
|
log ""
|
||||||
log "🔧 SERVICE-BEFEHLE:"
|
log "🔧 SERVICE-BEFEHLE:"
|
||||||
log " • Status: sudo systemctl status myp-{kiosk,https,http}"
|
log " • Status: sudo systemctl status $SERVICE_NAME"
|
||||||
log " • Logs: sudo journalctl -u myp-kiosk -f"
|
log " • Logs: sudo journalctl -u $SERVICE_NAME -f"
|
||||||
log " • Restart: sudo systemctl restart myp-{kiosk,https,http}"
|
log " • Restart: sudo systemctl restart $SERVICE_NAME"
|
||||||
|
log " • SSL-Test: curl -k https://localhost:443"
|
||||||
log ""
|
log ""
|
||||||
warning "🔄 FÜHRE JETZT 'sudo reboot' AUS, UM DEN KIOSK-MODUS ZU AKTIVIEREN!"
|
warning "🔄 FÜHRE JETZT 'sudo reboot' AUS, UM DEN HTTPS-KIOSK-MODUS ZU AKTIVIEREN!"
|
||||||
}
|
}
|
||||||
|
|
||||||
# ========================== HAUPTMENÜ ==========================
|
# ========================== HAUPTMENÜ ==========================
|
||||||
show_menu() {
|
show_menu() {
|
||||||
clear
|
clear
|
||||||
echo -e "${BLUE}=================================================================${NC}"
|
echo -e "${BLUE}=================================================================${NC}"
|
||||||
echo -e "${GREEN} $APP_NAME - VOLLSTÄNDIGER KIOSK-INSTALLER${NC}"
|
echo -e "${GREEN} $APP_NAME - HTTPS KIOSK-INSTALLER${NC}"
|
||||||
echo -e "${BLUE}=================================================================${NC}"
|
echo -e "${BLUE}=================================================================${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${YELLOW}Aktuelles Verzeichnis:${NC} $CURRENT_DIR"
|
echo -e "${YELLOW}Aktuelles Verzeichnis:${NC} $CURRENT_DIR"
|
||||||
echo -e "${YELLOW}Systemzeit:${NC} $(date)"
|
echo -e "${YELLOW}Systemzeit:${NC} $(date)"
|
||||||
echo -e "${YELLOW}Zielverzeichnis:${NC} $APP_DIR"
|
echo -e "${YELLOW}Zielverzeichnis:${NC} $APP_DIR"
|
||||||
echo -e "${YELLOW}Kiosk-Benutzer:${NC} $KIOSK_USER"
|
echo -e "${YELLOW}Kiosk-Benutzer:${NC} $KIOSK_USER"
|
||||||
|
echo -e "${YELLOW}HTTPS-Service:${NC} $SERVICE_NAME"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${PURPLE}Wählen Sie eine Option:${NC}"
|
echo -e "${PURPLE}Wählen Sie eine Option:${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
@ -1265,17 +1292,21 @@ show_menu() {
|
|||||||
echo -e " → Installiert Python 3, pip und alle benötigten Pakete"
|
echo -e " → Installiert Python 3, pip und alle benötigten Pakete"
|
||||||
echo -e " → Verwendet: pip install --break-system-packages"
|
echo -e " → Verwendet: pip install --break-system-packages"
|
||||||
echo -e " → Mercedes SSL-Zertifikate werden konfiguriert"
|
echo -e " → Mercedes SSL-Zertifikate werden konfiguriert"
|
||||||
|
echo -e " → Node.js und npm für Frontend-Build"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${GREEN}2)${NC} VOLLSTÄNDIGER KIOSK-MODUS installieren"
|
echo -e "${GREEN}2)${NC} VOLLSTÄNDIGER HTTPS KIOSK-MODUS installieren"
|
||||||
echo -e " → ${RED}ENTFERNT ALLE DESKTOP-ENVIRONMENTS!${NC}"
|
echo -e " → ${RED}ENTFERNT ALLE DESKTOP-ENVIRONMENTS!${NC}"
|
||||||
echo -e " → Installiert minimale X11-Umgebung"
|
echo -e " → Installiert minimale X11-Umgebung"
|
||||||
echo -e " → Erstellt 3 Backend-Services (Port 5000, 80, 443)"
|
echo -e " → Erstellt Self-Signed SSL-Zertifikate für localhost"
|
||||||
echo -e " → Konfiguriert Autologin und Kiosk-Browser"
|
echo -e " → Erstellt HTTPS Backend-Service (Port 443)"
|
||||||
|
echo -e " → Konfiguriert Autologin und HTTPS Kiosk-Browser"
|
||||||
|
echo -e " → Browser öffnet: ${BLUE}https://localhost:443${NC}"
|
||||||
echo -e " → ${YELLOW}NEUSTART ERFORDERLICH!${NC}"
|
echo -e " → ${YELLOW}NEUSTART ERFORDERLICH!${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${RED}0)${NC} Beenden"
|
echo -e "${RED}0)${NC} Beenden"
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${RED}⚠️ WARNUNG: Option 2 macht Raspberry Pi zu reinem Kiosk-System!${NC}"
|
echo -e "${RED}⚠️ WARNUNG: Option 2 macht Raspberry Pi zu reinem HTTPS-Kiosk-System!${NC}"
|
||||||
|
echo -e "${GREEN}🔐 HTTPS: Automatische SSL-Zertifikat-Generierung für localhost${NC}"
|
||||||
echo -e "${BLUE}=================================================================${NC}"
|
echo -e "${BLUE}=================================================================${NC}"
|
||||||
echo -n "Ihre Wahl [0-2]: "
|
echo -n "Ihre Wahl [0-2]: "
|
||||||
}
|
}
|
||||||
@ -1290,11 +1321,12 @@ main() {
|
|||||||
mkdir -p "$(dirname "$INSTALL_LOG")"
|
mkdir -p "$(dirname "$INSTALL_LOG")"
|
||||||
touch "$INSTALL_LOG"
|
touch "$INSTALL_LOG"
|
||||||
|
|
||||||
log "=== MYP VOLLSTÄNDIGER KIOSK-INSTALLER GESTARTET ==="
|
log "=== MYP HTTPS KIOSK-INSTALLER GESTARTET ==="
|
||||||
log "Arbeitsverzeichnis: $CURRENT_DIR"
|
log "Arbeitsverzeichnis: $CURRENT_DIR"
|
||||||
log "Zielverzeichnis: $APP_DIR"
|
log "Zielverzeichnis: $APP_DIR"
|
||||||
log "Kiosk-Services: $SERVICE_NAME_KIOSK, $SERVICE_NAME_HTTPS, $SERVICE_NAME_HTTP"
|
log "HTTPS-Service: $SERVICE_NAME"
|
||||||
log "Kiosk-Benutzer: $KIOSK_USER"
|
log "Kiosk-Benutzer: $KIOSK_USER"
|
||||||
|
log "SSL-Zertifikate: $APP_DIR/certs/localhost/"
|
||||||
log "System: $(uname -a)"
|
log "System: $(uname -a)"
|
||||||
log "Debian-Version: $(cat /etc/debian_version 2>/dev/null || echo 'Unbekannt')"
|
log "Debian-Version: $(cat /etc/debian_version 2>/dev/null || echo 'Unbekannt')"
|
||||||
|
|
||||||
@ -1315,19 +1347,21 @@ main() {
|
|||||||
2)
|
2)
|
||||||
clear
|
clear
|
||||||
echo -e "${RED}⚠️ WARNUNG: Sie sind dabei, alle Desktop-Environments zu entfernen!${NC}"
|
echo -e "${RED}⚠️ WARNUNG: Sie sind dabei, alle Desktop-Environments zu entfernen!${NC}"
|
||||||
echo -e "${YELLOW}Der Raspberry Pi wird zu einem reinen Kiosk-System umgebaut.${NC}"
|
echo -e "${YELLOW}Der Raspberry Pi wird zu einem reinen HTTPS-Kiosk-System umgebaut.${NC}"
|
||||||
echo -e "${BLUE}Nach der Installation startet automatisch der Kiosk-Browser.${NC}"
|
echo -e "${BLUE}Nach der Installation startet automatisch der HTTPS-Kiosk-Browser.${NC}"
|
||||||
|
echo -e "${GREEN}SSL-Zertifikate für localhost werden automatisch generiert.${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
echo -n "Sind Sie sicher? [ja/NEIN]: "
|
echo -n "Sind Sie sicher? [ja/NEIN]: "
|
||||||
read -r confirm
|
read -r confirm
|
||||||
|
|
||||||
if [ "$confirm" = "ja" ] || [ "$confirm" = "JA" ]; then
|
if [ "$confirm" = "ja" ] || [ "$confirm" = "JA" ]; then
|
||||||
clear
|
clear
|
||||||
log "=== OPTION 2: VOLLSTÄNDIGER KIOSK-MODUS ==="
|
log "=== OPTION 2: VOLLSTÄNDIGER HTTPS KIOSK-MODUS ==="
|
||||||
setup_production_kiosk
|
setup_production_kiosk
|
||||||
echo ""
|
echo ""
|
||||||
echo -e "${GREEN}✅ KIOSK-MODUS ERFOLGREICH EINGERICHTET!${NC}"
|
echo -e "${GREEN}✅ HTTPS KIOSK-MODUS ERFOLGREICH EINGERICHTET!${NC}"
|
||||||
echo -e "${RED}🔄 NEUSTART JETZT ERFORDERLICH: sudo reboot${NC}"
|
echo -e "${RED}🔄 NEUSTART JETZT ERFORDERLICH: sudo reboot${NC}"
|
||||||
|
echo -e "${BLUE}🔐 HTTPS-URL: https://localhost:443${NC}"
|
||||||
echo -e "${YELLOW}Drücken Sie Enter, um fortzufahren...${NC}"
|
echo -e "${YELLOW}Drücken Sie Enter, um fortzufahren...${NC}"
|
||||||
read -r
|
read -r
|
||||||
else
|
else
|
||||||
@ -1336,7 +1370,7 @@ main() {
|
|||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
0)
|
0)
|
||||||
log "=== INSTALLER BEENDET ==="
|
log "=== HTTPS KIOSK-INSTALLER BEENDET ==="
|
||||||
echo -e "${GREEN}Auf Wiedersehen!${NC}"
|
echo -e "${GREEN}Auf Wiedersehen!${NC}"
|
||||||
echo -e "${BLUE}Log-Datei: $INSTALL_LOG${NC}"
|
echo -e "${BLUE}Log-Datei: $INSTALL_LOG${NC}"
|
||||||
exit 0
|
exit 0
|
||||||
|
@ -84263,3 +84263,149 @@ WHERE printers.active = 1 AND printers.status = ?) AS anon_1]
|
|||||||
2025-06-01 02:28:10 - myp.printer_monitor - INFO - 🔍 Teste IP 5/6: 192.168.0.102
|
2025-06-01 02:28:10 - myp.printer_monitor - INFO - 🔍 Teste IP 5/6: 192.168.0.102
|
||||||
2025-06-01 02:28:16 - myp.printer_monitor - INFO - 🔍 Teste IP 6/6: 192.168.0.105
|
2025-06-01 02:28:16 - myp.printer_monitor - INFO - 🔍 Teste IP 6/6: 192.168.0.105
|
||||||
2025-06-01 02:28:22 - myp.printer_monitor - INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 36.0s
|
2025-06-01 02:28:22 - myp.printer_monitor - INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 36.0s
|
||||||
|
2025-06-01 02:28:30 - myp.app - INFO - Optimierte SQLite-Engine erstellt: /mnt/database/myp.db
|
||||||
|
2025-06-01 02:28:30 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
|
||||||
|
2025-06-01 02:28:30 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
|
||||||
|
2025-06-01 02:28:30 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
|
||||||
|
2025-06-01 02:28:30 - myp.backup - INFO - BackupManager initialisiert (minimal implementation)
|
||||||
|
2025-06-01 02:28:30 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
|
||||||
|
2025-06-01 02:28:30 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
|
||||||
|
2025-06-01 02:28:30 - myp.app - INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
|
||||||
|
2025-06-01 02:28:30 - myp.email_notification - INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
|
||||||
|
2025-06-01 02:28:30 - myp.maintenance - INFO - Wartungs-Scheduler gestartet
|
||||||
|
2025-06-01 02:28:30 - myp.multi_location - INFO - Standard-Standort erstellt
|
||||||
|
2025-06-01 02:28:30 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
|
||||||
|
2025-06-01 02:28:30 - myp.maintenance - INFO - Wartungs-Scheduler gestartet
|
||||||
|
2025-06-01 02:28:30 - myp.multi_location - INFO - Standard-Standort erstellt
|
||||||
|
2025-06-01 02:28:30 - myp.dashboard - INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)
|
||||||
|
2025-06-01 02:28:30 - myp.dashboard - INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading)
|
||||||
|
2025-06-01 02:28:30 - myp.security - INFO - 🔒 Security System initialisiert
|
||||||
|
2025-06-01 02:28:30 - myp.permissions - INFO - 🔐 Permission Template Helpers registriert
|
||||||
|
2025-06-01 02:28:30 - myp.app - INFO - ==================================================
|
||||||
|
2025-06-01 02:28:30 - myp.app - INFO - 🚀 MYP (Manage Your Printers) wird gestartet...
|
||||||
|
2025-06-01 02:28:30 - myp.app - INFO - 📂 Log-Verzeichnis: /mnt/logs
|
||||||
|
2025-06-01 02:28:30 - myp.app - INFO - 📊 Log-Level: INFO
|
||||||
|
2025-06-01 02:28:30 - myp.app - INFO - 💻 Betriebssystem: Linux 6.1.0-37-amd64
|
||||||
|
2025-06-01 02:28:30 - myp.app - INFO - 🌐 Hostname: debian
|
||||||
|
2025-06-01 02:28:30 - myp.app - INFO - 📅 Startzeit: 01.06.2025 02:28:30
|
||||||
|
2025-06-01 02:28:30 - myp.app - INFO - ==================================================
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - 🔄 Starte Datenbank-Setup und Migrationen...
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - Datenbank mit Optimierungen initialisiert
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - ✅ JobOrder-Tabelle bereits vorhanden
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - ✅ Datenbank-Setup und Migrationen erfolgreich abgeschlossen
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - 🖨️ Starte automatische Steckdosen-Initialisierung...
|
||||||
|
2025-06-01 02:28:31 - myp.printer_monitor - INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
|
||||||
|
2025-06-01 02:28:31 - myp.printer_monitor - WARNING - ⚠️ Keine aktiven Drucker zur Initialisierung gefunden
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - ℹ️ Keine Drucker zur Initialisierung gefunden
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - 🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - Job-Scheduler gestartet
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - Starte Debug-Server auf 0.0.0.0:5000 (HTTP)
|
||||||
|
2025-06-01 02:28:31 - werkzeug - INFO - [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||||
|
* Running on all addresses (0.0.0.0)
|
||||||
|
* Running on http://127.0.0.1:5000
|
||||||
|
* Running on http://10.0.2.15:5000
|
||||||
|
2025-06-01 02:28:31 - werkzeug - INFO - [33mPress CTRL+C to quit[0m
|
||||||
|
2025-06-01 02:28:31 - werkzeug - INFO - * Restarting with watchdog (inotify)
|
||||||
|
2025-06-01 02:28:31 - myp.app - INFO - Optimierte SQLite-Engine erstellt: /mnt/database/myp.db
|
||||||
|
2025-06-01 02:28:31 - myp.printer_monitor - INFO - 🖨️ Drucker-Monitor initialisiert
|
||||||
|
2025-06-01 02:28:31 - myp.printer_monitor - INFO - 🔍 Automatische Tapo-Erkennung in separatem Thread gestartet
|
||||||
|
2025-06-01 02:28:31 - myp.database - INFO - Datenbank-Wartungs-Scheduler gestartet
|
||||||
|
2025-06-01 02:28:32 - myp.backup - INFO - BackupManager initialisiert (minimal implementation)
|
||||||
|
2025-06-01 02:28:32 - myp.analytics - INFO - 📈 Analytics Engine initialisiert
|
||||||
|
2025-06-01 02:28:32 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - SQLite für Produktionsumgebung konfiguriert (WAL-Modus, Cache, Optimierungen)
|
||||||
|
2025-06-01 02:28:32 - myp.email_notification - INFO - 📧 Offline-E-Mail-Benachrichtigung initialisiert (kein echter E-Mail-Versand)
|
||||||
|
2025-06-01 02:28:32 - myp.maintenance - INFO - Wartungs-Scheduler gestartet
|
||||||
|
2025-06-01 02:28:32 - myp.printer_monitor - INFO - 🔍 Starte automatische Tapo-Steckdosenerkennung...
|
||||||
|
2025-06-01 02:28:32 - myp.printer_monitor - INFO - 🔄 Teste 6 Standard-IPs aus der Konfiguration
|
||||||
|
2025-06-01 02:28:32 - myp.multi_location - INFO - Standard-Standort erstellt
|
||||||
|
2025-06-01 02:28:32 - myp.printer_monitor - INFO - 🔍 Teste IP 1/6: 192.168.0.103
|
||||||
|
2025-06-01 02:28:32 - myp.dashboard - INFO - Dashboard-Background-Worker gestartet
|
||||||
|
2025-06-01 02:28:32 - myp.maintenance - INFO - Wartungs-Scheduler gestartet
|
||||||
|
2025-06-01 02:28:32 - myp.multi_location - INFO - Standard-Standort erstellt
|
||||||
|
2025-06-01 02:28:32 - myp.dashboard - INFO - Dashboard WebSocket-Server wird mit threading initialisiert (eventlet-Fallback)
|
||||||
|
2025-06-01 02:28:32 - myp.dashboard - INFO - Dashboard WebSocket-Server initialisiert (async_mode: threading)
|
||||||
|
2025-06-01 02:28:32 - myp.security - INFO - 🔒 Security System initialisiert
|
||||||
|
2025-06-01 02:28:32 - myp.permissions - INFO - 🔐 Permission Template Helpers registriert
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - ==================================================
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - 🚀 MYP (Manage Your Printers) wird gestartet...
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - 📂 Log-Verzeichnis: /mnt/logs
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - 📊 Log-Level: INFO
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - 💻 Betriebssystem: Linux 6.1.0-37-amd64
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - 🌐 Hostname: debian
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - 📅 Startzeit: 01.06.2025 02:28:32
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - ==================================================
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - 🔄 Starte Datenbank-Setup und Migrationen...
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - Datenbank mit Optimierungen initialisiert
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - ✅ JobOrder-Tabelle bereits vorhanden
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - Admin-Benutzer admin (admin@mercedes-benz.com) existiert bereits. Passwort wurde zurückgesetzt.
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - ✅ Datenbank-Setup und Migrationen erfolgreich abgeschlossen
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - 🖨️ Starte automatische Steckdosen-Initialisierung...
|
||||||
|
2025-06-01 02:28:32 - myp.printer_monitor - INFO - 🚀 Starte Steckdosen-Initialisierung beim Programmstart...
|
||||||
|
2025-06-01 02:28:32 - myp.printer_monitor - WARNING - ⚠️ Keine aktiven Drucker zur Initialisierung gefunden
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - ℹ️ Keine Drucker zur Initialisierung gefunden
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - 🔄 Debug-Modus: Queue Manager deaktiviert für Entwicklung
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - Job-Scheduler gestartet
|
||||||
|
2025-06-01 02:28:32 - myp.app - INFO - Starte Debug-Server auf 0.0.0.0:5000 (HTTP)
|
||||||
|
2025-06-01 02:28:32 - werkzeug - WARNING - * Debugger is active!
|
||||||
|
2025-06-01 02:28:32 - werkzeug - INFO - * Debugger PIN: 829-619-842
|
||||||
|
2025-06-01 02:28:33 - myp.printer_monitor - INFO - 🔍 Starte automatische Tapo-Steckdosenerkennung...
|
||||||
|
2025-06-01 02:28:33 - myp.printer_monitor - INFO - 🔄 Teste 6 Standard-IPs aus der Konfiguration
|
||||||
|
2025-06-01 02:28:33 - myp.printer_monitor - INFO - 🔍 Teste IP 1/6: 192.168.0.103
|
||||||
|
2025-06-01 02:28:34 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
|
||||||
|
2025-06-01 02:28:34 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
|
||||||
|
2025-06-01 02:28:34 - myp.app - INFO - 🔄 Beende Queue Manager...
|
||||||
|
2025-06-01 02:28:34 - myp.app - INFO - 🔄 Beende Queue Manager...
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - Job-Scheduler gestoppt
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - 💾 Führe robustes Datenbank-Cleanup durch...
|
||||||
|
2025-06-01 02:28:35 - myp.database_cleanup - INFO - 🧹 Starte umfassendes Datenbank-Cleanup...
|
||||||
|
2025-06-01 02:28:35 - myp.database_cleanup - INFO - 📝 Schritt 1: Schließe alle Datenbankverbindungen...
|
||||||
|
2025-06-01 02:28:35 - myp.database_cleanup - INFO - 🔄 Schließe alle aktiven Datenbankverbindungen...
|
||||||
|
2025-06-01 02:28:35 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
|
||||||
|
2025-06-01 02:28:35 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - 🔄 Beende Queue Manager...
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - 🔄 Beende Queue Manager...
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - Job-Scheduler gestoppt
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - 💾 Führe robustes Datenbank-Cleanup durch...
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - Job-Scheduler gestoppt
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - 💾 Führe robustes Datenbank-Cleanup durch...
|
||||||
|
2025-06-01 02:28:35 - myp.database_cleanup - INFO - 🧹 Starte umfassendes Datenbank-Cleanup...
|
||||||
|
2025-06-01 02:28:35 - myp.database_cleanup - INFO - 📝 Schritt 1: Schließe alle Datenbankverbindungen...
|
||||||
|
2025-06-01 02:28:35 - myp.database_cleanup - INFO - 🔄 Schließe alle aktiven Datenbankverbindungen...
|
||||||
|
2025-06-01 02:28:35 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - 🔄 Beende Queue Manager...
|
||||||
|
2025-06-01 02:28:35 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - Job-Scheduler gestoppt
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - 💾 Führe robustes Datenbank-Cleanup durch...
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - 🔄 Beende Queue Manager...
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - Job-Scheduler gestoppt
|
||||||
|
2025-06-01 02:28:35 - myp.app - INFO - 💾 Führe robustes Datenbank-Cleanup durch...
|
||||||
|
2025-06-01 02:28:38 - myp.printer_monitor - INFO - 🔍 Teste IP 2/6: 192.168.0.104
|
||||||
|
2025-06-01 02:28:39 - myp.printer_monitor - INFO - 🔍 Teste IP 2/6: 192.168.0.104
|
||||||
|
2025-06-01 02:28:45 - myp.printer_monitor - INFO - 🔍 Teste IP 3/6: 192.168.0.100
|
||||||
|
2025-06-01 02:28:46 - myp.printer_monitor - INFO - 🔍 Teste IP 3/6: 192.168.0.100
|
||||||
|
2025-06-01 02:28:51 - myp.printer_monitor - INFO - 🔍 Teste IP 4/6: 192.168.0.101
|
||||||
|
2025-06-01 02:28:52 - myp.printer_monitor - INFO - 🔍 Teste IP 4/6: 192.168.0.101
|
||||||
|
2025-06-01 02:28:59 - myp.printer_monitor - INFO - 🔍 Teste IP 5/6: 192.168.0.102
|
||||||
|
2025-06-01 02:28:59 - myp.printer_monitor - INFO - 🔍 Teste IP 5/6: 192.168.0.102
|
||||||
|
2025-06-01 02:29:05 - myp.printer_monitor - INFO - 🔍 Teste IP 6/6: 192.168.0.105
|
||||||
|
2025-06-01 02:29:07 - myp.printer_monitor - INFO - 🔍 Teste IP 6/6: 192.168.0.105
|
||||||
|
2025-06-01 02:29:12 - myp.printer_monitor - INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 40.3s
|
||||||
|
2025-06-01 02:29:13 - myp.printer_monitor - INFO - ✅ Steckdosen-Erkennung abgeschlossen: 0/6 Steckdosen gefunden in 39.7s
|
||||||
|
2025-06-01 02:31:17 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - 🔄 Beende Queue Manager...
|
||||||
|
2025-06-01 02:31:17 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - Job-Scheduler gestoppt
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - 🔄 Beende Queue Manager...
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - 💾 Führe robustes Datenbank-Cleanup durch...
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - Job-Scheduler gestoppt
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - 💾 Führe robustes Datenbank-Cleanup durch...
|
||||||
|
2025-06-01 02:31:17 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
|
||||||
|
2025-06-01 02:31:17 - myp.app - WARNING - 🛑 Signal 2 empfangen - fahre System herunter...
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - 🔄 Beende Queue Manager...
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - 🔄 Beende Queue Manager...
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - Job-Scheduler gestoppt
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - Job-Scheduler gestoppt
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - 💾 Führe robustes Datenbank-Cleanup durch...
|
||||||
|
2025-06-01 02:31:17 - myp.app - INFO - 💾 Führe robustes Datenbank-Cleanup durch...
|
||||||
|
@ -2790,3 +2790,20 @@
|
|||||||
2025-06-01 02:27:45 - myp.scheduler - INFO - Scheduler gestartet
|
2025-06-01 02:27:45 - myp.scheduler - INFO - Scheduler gestartet
|
||||||
2025-06-01 02:27:48 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
2025-06-01 02:27:48 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||||
2025-06-01 02:28:07 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
2025-06-01 02:28:07 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||||
|
2025-06-01 02:28:30 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||||
|
2025-06-01 02:28:31 - myp.scheduler - INFO - Scheduler-Thread gestartet
|
||||||
|
2025-06-01 02:28:31 - myp.scheduler - INFO - Scheduler gestartet
|
||||||
|
2025-06-01 02:28:31 - myp.scheduler - INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||||
|
2025-06-01 02:28:32 - myp.scheduler - INFO - Scheduler-Thread gestartet
|
||||||
|
2025-06-01 02:28:32 - myp.scheduler - INFO - Scheduler gestartet
|
||||||
|
2025-06-01 02:28:35 - myp.scheduler - INFO - Scheduler-Thread beendet
|
||||||
|
2025-06-01 02:28:35 - myp.scheduler - INFO - Scheduler gestoppt
|
||||||
|
2025-06-01 02:28:35 - myp.scheduler - WARNING - Scheduler läuft nicht
|
||||||
|
2025-06-01 02:28:35 - myp.scheduler - INFO - Scheduler-Thread beendet
|
||||||
|
2025-06-01 02:28:35 - myp.scheduler - INFO - Scheduler gestoppt
|
||||||
|
2025-06-01 02:28:35 - myp.scheduler - WARNING - Scheduler läuft nicht
|
||||||
|
2025-06-01 02:28:35 - myp.scheduler - WARNING - Scheduler läuft nicht
|
||||||
|
2025-06-01 02:31:17 - myp.scheduler - WARNING - Scheduler läuft nicht
|
||||||
|
2025-06-01 02:31:17 - myp.scheduler - WARNING - Scheduler läuft nicht
|
||||||
|
2025-06-01 02:31:17 - myp.scheduler - WARNING - Scheduler läuft nicht
|
||||||
|
2025-06-01 02:31:17 - myp.scheduler - WARNING - Scheduler läuft nicht
|
||||||
|
BIN
backend/utils/__pycache__/shutdown_manager.cpython-313.pyc
Normal file
BIN
backend/utils/__pycache__/shutdown_manager.cpython-313.pyc
Normal file
Binary file not shown.
@ -81,7 +81,7 @@ class PrinterQueueManager:
|
|||||||
Verbesserte Version mit ordnungsgemäßem Thread-Management für Windows.
|
Verbesserte Version mit ordnungsgemäßem Thread-Management für Windows.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, register_signal_handlers: bool = True):
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.monitor_thread = None
|
self.monitor_thread = None
|
||||||
self.shutdown_event = threading.Event() # Sauberes Shutdown-Signal
|
self.shutdown_event = threading.Event() # Sauberes Shutdown-Signal
|
||||||
@ -89,58 +89,93 @@ class PrinterQueueManager:
|
|||||||
self.last_status_cache = {} # Cache für letzten bekannten Status
|
self.last_status_cache = {} # Cache für letzten bekannten Status
|
||||||
self.notification_cooldown = {} # Verhindert Spam-Benachrichtigungen
|
self.notification_cooldown = {} # Verhindert Spam-Benachrichtigungen
|
||||||
self._lock = threading.Lock() # Thread-Sicherheit
|
self._lock = threading.Lock() # Thread-Sicherheit
|
||||||
|
self._signal_handlers_registered = False
|
||||||
|
|
||||||
|
# Signal-Handler nur registrieren wenn explizit gewünscht
|
||||||
|
# (Verhindert Interferenzen mit zentralem Shutdown-Manager)
|
||||||
|
if register_signal_handlers and os.name == 'nt':
|
||||||
|
self._register_signal_handlers()
|
||||||
|
|
||||||
|
def _register_signal_handlers(self):
|
||||||
|
"""Windows-spezifische Signal-Handler registrieren (nur wenn gewünscht)"""
|
||||||
|
if self._signal_handlers_registered:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Prüfe ob bereits zentrale Signal-Handler existieren
|
||||||
|
try:
|
||||||
|
from utils.shutdown_manager import is_shutdown_requested
|
||||||
|
if is_shutdown_requested is not None:
|
||||||
|
queue_logger.info("🔄 Zentrale Signal-Handler erkannt - deaktiviere lokale Handler")
|
||||||
|
return
|
||||||
|
except ImportError:
|
||||||
|
pass # Kein zentraler Manager verfügbar, verwende lokale Handler
|
||||||
|
|
||||||
# Windows-spezifische Signal-Handler registrieren
|
|
||||||
if os.name == 'nt':
|
|
||||||
signal.signal(signal.SIGINT, self._signal_handler)
|
signal.signal(signal.SIGINT, self._signal_handler)
|
||||||
signal.signal(signal.SIGTERM, self._signal_handler)
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||||
|
self._signal_handlers_registered = True
|
||||||
|
queue_logger.debug("✅ Lokale Signal-Handler für Queue Manager registriert")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
queue_logger.warning(f"⚠️ Lokale Signal-Handler konnten nicht registriert werden: {e}")
|
||||||
|
|
||||||
def _signal_handler(self, signum, frame):
|
def _signal_handler(self, signum, frame):
|
||||||
"""Signal-Handler für ordnungsgemäßes Shutdown."""
|
"""Signal-Handler für ordnungsgemäßes Shutdown (nur als Fallback)."""
|
||||||
queue_logger.warning(f"🛑 Signal {signum} empfangen - stoppe Queue Manager...")
|
queue_logger.warning(f"🛑 Signal {signum} empfangen - stoppe Queue Manager...")
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Startet den Queue-Manager mit verbessertem Thread-Management."""
|
"""Startet den Queue-Manager mit verbessertem Shutdown-Handling."""
|
||||||
with self._lock:
|
|
||||||
if not self.is_running:
|
|
||||||
self.is_running = True
|
|
||||||
self.shutdown_event.clear()
|
|
||||||
self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=False)
|
|
||||||
self.monitor_thread.name = "PrinterQueueMonitor"
|
|
||||||
|
|
||||||
# Windows Thread-Manager verwenden falls verfügbar
|
|
||||||
if os.name == 'nt' and get_windows_thread_manager:
|
|
||||||
try:
|
|
||||||
thread_manager = get_windows_thread_manager()
|
|
||||||
thread_manager.register_thread(self.monitor_thread)
|
|
||||||
thread_manager.register_cleanup_function(self.stop)
|
|
||||||
queue_logger.debug("✅ Queue Manager bei Windows Thread-Manager registriert")
|
|
||||||
except Exception as e:
|
|
||||||
queue_logger.warning(f"⚠️ Windows Thread-Manager nicht verfügbar: {str(e)}")
|
|
||||||
|
|
||||||
self.monitor_thread.start()
|
|
||||||
queue_logger.info("✅ Printer Queue Manager erfolgreich gestartet")
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
"""Stoppt den Queue-Manager ordnungsgemäß."""
|
|
||||||
with self._lock:
|
with self._lock:
|
||||||
if self.is_running:
|
if self.is_running:
|
||||||
queue_logger.info("🔄 Beende Queue Manager...")
|
queue_logger.warning("Queue-Manager läuft bereits")
|
||||||
self.is_running = False
|
return self
|
||||||
self.shutdown_event.set()
|
|
||||||
|
|
||||||
if self.monitor_thread and self.monitor_thread.is_alive():
|
queue_logger.info("🚀 Starte Printer Queue Manager...")
|
||||||
queue_logger.debug("⏳ Warte auf Thread-Beendigung...")
|
self.is_running = True
|
||||||
self.monitor_thread.join(timeout=10)
|
self.shutdown_event.clear()
|
||||||
|
|
||||||
|
# Monitor-Thread mit Daemon-Flag für automatische Beendigung
|
||||||
|
self.monitor_thread = threading.Thread(
|
||||||
|
target=self._monitor_loop,
|
||||||
|
name="PrinterQueueMonitor",
|
||||||
|
daemon=True # Automatische Beendigung bei Programm-Ende
|
||||||
|
)
|
||||||
|
self.monitor_thread.start()
|
||||||
|
|
||||||
|
queue_logger.info("✅ Printer Queue Manager gestartet")
|
||||||
|
return self
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Stoppt den Queue-Manager ordnungsgemäß mit verbessertem Timeout-Handling."""
|
||||||
|
with self._lock:
|
||||||
|
if not self.is_running:
|
||||||
|
queue_logger.debug("Queue-Manager ist bereits gestoppt")
|
||||||
|
return
|
||||||
|
|
||||||
|
queue_logger.info("🔄 Beende Queue Manager...")
|
||||||
|
self.is_running = False
|
||||||
|
self.shutdown_event.set()
|
||||||
|
|
||||||
|
if self.monitor_thread and self.monitor_thread.is_alive():
|
||||||
|
queue_logger.debug("⏳ Warte auf Thread-Beendigung...")
|
||||||
|
|
||||||
|
# Verbessertes Timeout-Handling
|
||||||
|
try:
|
||||||
|
self.monitor_thread.join(timeout=5.0) # Reduziertes Timeout
|
||||||
|
|
||||||
if self.monitor_thread.is_alive():
|
if self.monitor_thread.is_alive():
|
||||||
queue_logger.warning("⚠️ Thread konnte nicht ordnungsgemäß beendet werden")
|
queue_logger.warning("⚠️ Thread konnte nicht in 5s beendet werden - setze als Daemon")
|
||||||
|
# Thread als Daemon markieren für automatische Beendigung
|
||||||
|
self.monitor_thread.daemon = True
|
||||||
else:
|
else:
|
||||||
queue_logger.info("✅ Monitor-Thread erfolgreich beendet")
|
queue_logger.info("✅ Monitor-Thread erfolgreich beendet")
|
||||||
|
|
||||||
self.monitor_thread = None
|
except Exception as e:
|
||||||
queue_logger.info("❌ Printer Queue Manager gestoppt")
|
queue_logger.error(f"❌ Fehler beim Thread-Join: {e}")
|
||||||
|
|
||||||
|
self.monitor_thread = None
|
||||||
|
queue_logger.info("❌ Printer Queue Manager gestoppt")
|
||||||
|
|
||||||
def _monitor_loop(self):
|
def _monitor_loop(self):
|
||||||
"""Hauptschleife für die Überwachung der Drucker mit verbessertem Shutdown-Handling."""
|
"""Hauptschleife für die Überwachung der Drucker mit verbessertem Shutdown-Handling."""
|
||||||
@ -148,6 +183,15 @@ class PrinterQueueManager:
|
|||||||
|
|
||||||
while self.is_running and not self.shutdown_event.is_set():
|
while self.is_running and not self.shutdown_event.is_set():
|
||||||
try:
|
try:
|
||||||
|
# Prüfe auf zentrales Shutdown-Signal
|
||||||
|
try:
|
||||||
|
from utils.shutdown_manager import is_shutdown_requested
|
||||||
|
if is_shutdown_requested():
|
||||||
|
queue_logger.info("🛑 Zentrales Shutdown-Signal empfangen - beende Monitor-Loop")
|
||||||
|
break
|
||||||
|
except ImportError:
|
||||||
|
pass # Kein zentraler Manager verfügbar
|
||||||
|
|
||||||
self._check_waiting_jobs()
|
self._check_waiting_jobs()
|
||||||
|
|
||||||
# Verwende Event.wait() statt time.sleep() für unterbrechbares Warten
|
# Verwende Event.wait() statt time.sleep() für unterbrechbares Warten
|
||||||
@ -379,10 +423,37 @@ def get_queue_manager() -> PrinterQueueManager:
|
|||||||
return _queue_manager_instance
|
return _queue_manager_instance
|
||||||
|
|
||||||
def start_queue_manager():
|
def start_queue_manager():
|
||||||
"""Startet den globalen Queue-Manager."""
|
"""Startet den globalen Queue-Manager sicher und ohne Signal-Handler-Interferenzen."""
|
||||||
manager = get_queue_manager()
|
global _queue_manager_instance
|
||||||
manager.start()
|
with _queue_manager_lock:
|
||||||
return manager
|
if _queue_manager_instance is not None:
|
||||||
|
queue_logger.warning("Queue-Manager läuft bereits")
|
||||||
|
return _queue_manager_instance
|
||||||
|
|
||||||
|
try:
|
||||||
|
queue_logger.info("🚀 Initialisiere neuen Queue-Manager...")
|
||||||
|
|
||||||
|
# Prüfe ob zentraler Shutdown-Manager verfügbar ist
|
||||||
|
register_signals = True
|
||||||
|
try:
|
||||||
|
from utils.shutdown_manager import is_shutdown_requested
|
||||||
|
if is_shutdown_requested is not None:
|
||||||
|
queue_logger.info("🔄 Zentrale Shutdown-Verwaltung erkannt - deaktiviere lokale Signal-Handler")
|
||||||
|
register_signals = False
|
||||||
|
except ImportError:
|
||||||
|
queue_logger.debug("Kein zentraler Shutdown-Manager verfügbar - verwende lokale Signal-Handler")
|
||||||
|
|
||||||
|
# Erstelle Queue-Manager ohne Signal-Handler wenn zentraler Manager vorhanden
|
||||||
|
_queue_manager_instance = PrinterQueueManager(register_signal_handlers=register_signals)
|
||||||
|
_queue_manager_instance.start()
|
||||||
|
|
||||||
|
queue_logger.info("✅ Queue-Manager erfolgreich gestartet")
|
||||||
|
return _queue_manager_instance
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
queue_logger.error(f"❌ Fehler beim Starten des Queue-Managers: {str(e)}")
|
||||||
|
_queue_manager_instance = None
|
||||||
|
raise
|
||||||
|
|
||||||
def stop_queue_manager():
|
def stop_queue_manager():
|
||||||
"""Stoppt den globalen Queue-Manager definitiv und sicher."""
|
"""Stoppt den globalen Queue-Manager definitiv und sicher."""
|
||||||
|
442
backend/utils/shutdown_manager.py
Normal file
442
backend/utils/shutdown_manager.py
Normal file
@ -0,0 +1,442 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Zentraler Shutdown-Manager für robuste Anwendungsbeendigung
|
||||||
|
Koordiniert alle Cleanup-Operationen mit Timeouts und Fehlerbehandlung
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import signal
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import atexit
|
||||||
|
from typing import List, Callable, Dict, Any, Optional
|
||||||
|
from datetime import datetime
|
||||||
|
from contextlib import contextmanager
|
||||||
|
import logging
|
||||||
|
|
||||||
|
# Logging-Setup mit Fallback
|
||||||
|
try:
|
||||||
|
from utils.logging_config import get_logger
|
||||||
|
shutdown_logger = get_logger("shutdown_manager")
|
||||||
|
except ImportError:
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
shutdown_logger = logging.getLogger("shutdown_manager")
|
||||||
|
|
||||||
|
|
||||||
|
class ShutdownManager:
|
||||||
|
"""
|
||||||
|
Zentraler Manager für ordnungsgemäße Anwendungsbeendigung.
|
||||||
|
Koordiniert alle Cleanup-Operationen mit Timeouts und Prioritäten.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, timeout: int = 30):
|
||||||
|
self.timeout = timeout
|
||||||
|
self.is_shutting_down = False
|
||||||
|
self.shutdown_started = None
|
||||||
|
self.cleanup_functions: List[Dict[str, Any]] = []
|
||||||
|
self.components: Dict[str, Any] = {}
|
||||||
|
self._lock = threading.Lock()
|
||||||
|
self._signal_handlers_registered = False
|
||||||
|
|
||||||
|
# Shutdown-Ereignis für Thread-Koordination
|
||||||
|
self.shutdown_event = threading.Event()
|
||||||
|
|
||||||
|
# Registriere grundlegende Signal-Handler
|
||||||
|
self._register_signal_handlers()
|
||||||
|
|
||||||
|
shutdown_logger.info("🔧 Shutdown-Manager initialisiert")
|
||||||
|
|
||||||
|
def _register_signal_handlers(self):
|
||||||
|
"""Registriert plattformspezifische Signal-Handler"""
|
||||||
|
if self._signal_handlers_registered:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.name == 'nt': # Windows
|
||||||
|
signal.signal(signal.SIGINT, self._signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||||
|
if hasattr(signal, 'SIGBREAK'):
|
||||||
|
signal.signal(signal.SIGBREAK, self._signal_handler)
|
||||||
|
else: # Unix/Linux
|
||||||
|
signal.signal(signal.SIGINT, self._signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, self._signal_handler)
|
||||||
|
signal.signal(signal.SIGHUP, self._signal_handler)
|
||||||
|
|
||||||
|
self._signal_handlers_registered = True
|
||||||
|
shutdown_logger.debug("✅ Signal-Handler registriert")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
shutdown_logger.warning(f"⚠️ Signal-Handler konnten nicht registriert werden: {e}")
|
||||||
|
|
||||||
|
def _signal_handler(self, signum, frame):
|
||||||
|
"""Zentraler Signal-Handler für ordnungsgemäße Beendigung"""
|
||||||
|
if not self.is_shutting_down:
|
||||||
|
shutdown_logger.warning(f"🛑 Signal {signum} empfangen - starte koordiniertes Shutdown")
|
||||||
|
self.shutdown()
|
||||||
|
|
||||||
|
def register_component(self, name: str, component: Any, stop_method: str = "stop"):
|
||||||
|
"""
|
||||||
|
Registriert eine Komponente für ordnungsgemäße Beendigung
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name der Komponente
|
||||||
|
component: Komponenten-Objekt
|
||||||
|
stop_method: Name der Stopp-Methode (Standard: "stop")
|
||||||
|
"""
|
||||||
|
with self._lock:
|
||||||
|
self.components[name] = {
|
||||||
|
'component': component,
|
||||||
|
'stop_method': stop_method,
|
||||||
|
'priority': 1 # Standard-Priorität
|
||||||
|
}
|
||||||
|
shutdown_logger.debug(f"📝 Komponente '{name}' registriert")
|
||||||
|
|
||||||
|
def register_cleanup_function(self,
|
||||||
|
func: Callable,
|
||||||
|
name: str,
|
||||||
|
priority: int = 1,
|
||||||
|
timeout: int = 10,
|
||||||
|
args: tuple = (),
|
||||||
|
kwargs: dict = None):
|
||||||
|
"""
|
||||||
|
Registriert eine Cleanup-Funktion
|
||||||
|
|
||||||
|
Args:
|
||||||
|
func: Cleanup-Funktion
|
||||||
|
name: Beschreibender Name
|
||||||
|
priority: Priorität (1=hoch, 3=niedrig)
|
||||||
|
timeout: Timeout in Sekunden
|
||||||
|
args: Funktions-Argumente
|
||||||
|
kwargs: Funktions-Keyword-Argumente
|
||||||
|
"""
|
||||||
|
if kwargs is None:
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
cleanup_item = {
|
||||||
|
'function': func,
|
||||||
|
'name': name,
|
||||||
|
'priority': priority,
|
||||||
|
'timeout': timeout,
|
||||||
|
'args': args,
|
||||||
|
'kwargs': kwargs
|
||||||
|
}
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
self.cleanup_functions.append(cleanup_item)
|
||||||
|
# Nach Priorität sortieren (1 = höchste Priorität zuerst)
|
||||||
|
self.cleanup_functions.sort(key=lambda x: x['priority'])
|
||||||
|
|
||||||
|
shutdown_logger.debug(f"📝 Cleanup-Funktion '{name}' registriert (Priorität: {priority})")
|
||||||
|
|
||||||
|
def register_queue_manager(self, queue_manager_module):
|
||||||
|
"""Registriert den Queue Manager für ordnungsgemäße Beendigung"""
|
||||||
|
try:
|
||||||
|
stop_func = getattr(queue_manager_module, 'stop_queue_manager', None)
|
||||||
|
if stop_func:
|
||||||
|
self.register_cleanup_function(
|
||||||
|
func=stop_func,
|
||||||
|
name="Queue Manager",
|
||||||
|
priority=1, # Hohe Priorität
|
||||||
|
timeout=15
|
||||||
|
)
|
||||||
|
shutdown_logger.debug("✅ Queue Manager registriert")
|
||||||
|
else:
|
||||||
|
shutdown_logger.warning("⚠️ Queue Manager stop_queue_manager Funktion nicht gefunden")
|
||||||
|
except Exception as e:
|
||||||
|
shutdown_logger.error(f"❌ Fehler beim Registrieren des Queue Managers: {e}")
|
||||||
|
|
||||||
|
def register_scheduler(self, scheduler, scheduler_enabled: bool = True):
|
||||||
|
"""Registriert den Job-Scheduler für ordnungsgemäße Beendigung"""
|
||||||
|
if not scheduler_enabled or not scheduler:
|
||||||
|
shutdown_logger.debug("Scheduler nicht registriert (deaktiviert oder nicht vorhanden)")
|
||||||
|
return
|
||||||
|
|
||||||
|
def stop_scheduler():
|
||||||
|
try:
|
||||||
|
if hasattr(scheduler, 'shutdown'):
|
||||||
|
shutdown_logger.info("🔄 Beende Scheduler mit shutdown()...")
|
||||||
|
scheduler.shutdown(wait=True)
|
||||||
|
elif hasattr(scheduler, 'stop'):
|
||||||
|
shutdown_logger.info("🔄 Beende Scheduler mit stop()...")
|
||||||
|
scheduler.stop()
|
||||||
|
else:
|
||||||
|
shutdown_logger.warning("⚠️ Scheduler hat keine bekannte Stop-Methode")
|
||||||
|
|
||||||
|
shutdown_logger.info("✅ Scheduler erfolgreich gestoppt")
|
||||||
|
except Exception as e:
|
||||||
|
shutdown_logger.error(f"❌ Fehler beim Stoppen des Schedulers: {e}")
|
||||||
|
|
||||||
|
self.register_cleanup_function(
|
||||||
|
func=stop_scheduler,
|
||||||
|
name="Job Scheduler",
|
||||||
|
priority=1, # Hohe Priorität
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
def register_database_cleanup(self):
|
||||||
|
"""Registriert Datenbank-Cleanup mit robuster Fehlerbehandlung"""
|
||||||
|
def safe_database_cleanup():
|
||||||
|
try:
|
||||||
|
shutdown_logger.info("💾 Führe sicheres Datenbank-Cleanup durch...")
|
||||||
|
|
||||||
|
# Versuche den neuen DatabaseCleanupManager zu verwenden
|
||||||
|
try:
|
||||||
|
from utils.database_cleanup import safe_database_cleanup
|
||||||
|
|
||||||
|
result = safe_database_cleanup(force_mode_switch=False) # Kein riskantes Mode-Switching
|
||||||
|
|
||||||
|
if result.get("success", False):
|
||||||
|
shutdown_logger.info(f"✅ Datenbank-Cleanup erfolgreich: {', '.join(result.get('operations', []))}")
|
||||||
|
else:
|
||||||
|
shutdown_logger.warning(f"⚠️ Datenbank-Cleanup mit Problemen: {', '.join(result.get('errors', []))}")
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
shutdown_logger.info("Fallback: Verwende einfaches WAL-Checkpoint...")
|
||||||
|
|
||||||
|
# Einfacher Fallback nur mit WAL-Checkpoint
|
||||||
|
try:
|
||||||
|
from models import create_optimized_engine
|
||||||
|
from sqlalchemy import text
|
||||||
|
|
||||||
|
engine = create_optimized_engine()
|
||||||
|
|
||||||
|
with engine.connect() as conn:
|
||||||
|
result = conn.execute(text("PRAGMA wal_checkpoint(PASSIVE)")).fetchone()
|
||||||
|
if result and result[1] > 0:
|
||||||
|
shutdown_logger.info(f"WAL-Checkpoint: {result[1]} Seiten übertragen")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
engine.dispose()
|
||||||
|
shutdown_logger.info("✅ Einfaches Datenbank-Cleanup abgeschlossen")
|
||||||
|
|
||||||
|
except Exception as db_error:
|
||||||
|
shutdown_logger.error(f"❌ Fehler beim einfachen Datenbank-Cleanup: {db_error}")
|
||||||
|
|
||||||
|
except Exception as cleanup_error:
|
||||||
|
shutdown_logger.error(f"❌ Fehler beim Datenbank-Cleanup: {cleanup_error}")
|
||||||
|
|
||||||
|
self.register_cleanup_function(
|
||||||
|
func=safe_database_cleanup,
|
||||||
|
name="Datenbank Cleanup",
|
||||||
|
priority=3, # Niedrige Priorität (am Ende)
|
||||||
|
timeout=20
|
||||||
|
)
|
||||||
|
|
||||||
|
def register_windows_thread_manager(self):
|
||||||
|
"""Registriert Windows Thread Manager falls verfügbar"""
|
||||||
|
try:
|
||||||
|
if os.name == 'nt':
|
||||||
|
from utils.windows_fixes import get_windows_thread_manager
|
||||||
|
|
||||||
|
thread_manager = get_windows_thread_manager()
|
||||||
|
if thread_manager:
|
||||||
|
self.register_cleanup_function(
|
||||||
|
func=thread_manager.shutdown_all,
|
||||||
|
name="Windows Thread Manager",
|
||||||
|
priority=2,
|
||||||
|
timeout=15
|
||||||
|
)
|
||||||
|
shutdown_logger.debug("✅ Windows Thread Manager registriert")
|
||||||
|
except Exception as e:
|
||||||
|
shutdown_logger.debug(f"Windows Thread Manager nicht verfügbar: {e}")
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def cleanup_timeout(self, timeout: int, operation_name: str):
|
||||||
|
"""Context Manager für Timeout-überwachte Operationen"""
|
||||||
|
start_time = time.time()
|
||||||
|
success = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
success = True
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
shutdown_logger.error(f"❌ Fehler bei {operation_name} nach {elapsed:.1f}s: {e}")
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
if success:
|
||||||
|
shutdown_logger.debug(f"✅ {operation_name} abgeschlossen in {elapsed:.1f}s")
|
||||||
|
elif elapsed >= timeout:
|
||||||
|
shutdown_logger.warning(f"⏱️ {operation_name} Timeout nach {elapsed:.1f}s")
|
||||||
|
|
||||||
|
def shutdown(self, exit_code: int = 0):
|
||||||
|
"""
|
||||||
|
Führt koordiniertes Shutdown aller registrierten Komponenten durch
|
||||||
|
|
||||||
|
Args:
|
||||||
|
exit_code: Exit-Code für sys.exit()
|
||||||
|
"""
|
||||||
|
if self.is_shutting_down:
|
||||||
|
shutdown_logger.debug("Shutdown bereits in Bearbeitung")
|
||||||
|
return
|
||||||
|
|
||||||
|
with self._lock:
|
||||||
|
if self.is_shutting_down:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.is_shutting_down = True
|
||||||
|
self.shutdown_started = datetime.now()
|
||||||
|
|
||||||
|
shutdown_logger.info("🔄 Starte koordiniertes System-Shutdown...")
|
||||||
|
self.shutdown_event.set()
|
||||||
|
|
||||||
|
total_start = time.time()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Komponenten stoppen (nach Priorität)
|
||||||
|
self._shutdown_components()
|
||||||
|
|
||||||
|
# 2. Cleanup-Funktionen ausführen (nach Priorität)
|
||||||
|
self._execute_cleanup_functions()
|
||||||
|
|
||||||
|
total_elapsed = time.time() - total_start
|
||||||
|
shutdown_logger.info(f"✅ Koordiniertes Shutdown abgeschlossen in {total_elapsed:.1f}s")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
shutdown_logger.error(f"❌ Fehler beim koordinierten Shutdown: {e}")
|
||||||
|
finally:
|
||||||
|
shutdown_logger.info("🏁 System wird beendet...")
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
def _shutdown_components(self):
|
||||||
|
"""Stoppt alle registrierten Komponenten"""
|
||||||
|
if not self.components:
|
||||||
|
return
|
||||||
|
|
||||||
|
shutdown_logger.info(f"🔄 Stoppe {len(self.components)} registrierte Komponenten...")
|
||||||
|
|
||||||
|
for name, config in self.components.items():
|
||||||
|
try:
|
||||||
|
component = config['component']
|
||||||
|
stop_method = config['stop_method']
|
||||||
|
|
||||||
|
if hasattr(component, stop_method):
|
||||||
|
with self.cleanup_timeout(10, f"Komponente {name}"):
|
||||||
|
method = getattr(component, stop_method)
|
||||||
|
method()
|
||||||
|
shutdown_logger.debug(f"✅ Komponente '{name}' gestoppt")
|
||||||
|
else:
|
||||||
|
shutdown_logger.warning(f"⚠️ Komponente '{name}' hat keine '{stop_method}' Methode")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
shutdown_logger.error(f"❌ Fehler beim Stoppen der Komponente '{name}': {e}")
|
||||||
|
|
||||||
|
def _execute_cleanup_functions(self):
|
||||||
|
"""Führt alle registrierten Cleanup-Funktionen aus"""
|
||||||
|
if not self.cleanup_functions:
|
||||||
|
return
|
||||||
|
|
||||||
|
shutdown_logger.info(f"🧹 Führe {len(self.cleanup_functions)} Cleanup-Funktionen aus...")
|
||||||
|
|
||||||
|
for cleanup_item in self.cleanup_functions:
|
||||||
|
try:
|
||||||
|
func = cleanup_item['function']
|
||||||
|
name = cleanup_item['name']
|
||||||
|
timeout = cleanup_item['timeout']
|
||||||
|
args = cleanup_item['args']
|
||||||
|
kwargs = cleanup_item['kwargs']
|
||||||
|
|
||||||
|
shutdown_logger.debug(f"🔄 Führe Cleanup aus: {name}")
|
||||||
|
|
||||||
|
with self.cleanup_timeout(timeout, f"Cleanup {name}"):
|
||||||
|
func(*args, **kwargs)
|
||||||
|
shutdown_logger.debug(f"✅ Cleanup '{name}' abgeschlossen")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
shutdown_logger.error(f"❌ Fehler bei Cleanup '{cleanup_item['name']}': {e}")
|
||||||
|
# Weiter mit nächstem Cleanup
|
||||||
|
continue
|
||||||
|
|
||||||
|
def is_shutdown_requested(self) -> bool:
|
||||||
|
"""Prüft ob Shutdown angefordert wurde"""
|
||||||
|
return self.shutdown_event.is_set()
|
||||||
|
|
||||||
|
def get_shutdown_time(self) -> Optional[datetime]:
|
||||||
|
"""Gibt den Zeitpunkt des Shutdown-Starts zurück"""
|
||||||
|
return self.shutdown_started
|
||||||
|
|
||||||
|
def force_shutdown(self, exit_code: int = 1):
|
||||||
|
"""Erzwingt sofortiges Shutdown ohne Cleanup"""
|
||||||
|
shutdown_logger.warning("🚨 Erzwinge sofortiges Shutdown ohne Cleanup!")
|
||||||
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
|
||||||
|
# Globaler Shutdown-Manager
|
||||||
|
_shutdown_manager: Optional[ShutdownManager] = None
|
||||||
|
_manager_lock = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
|
def get_shutdown_manager(timeout: int = 30) -> ShutdownManager:
|
||||||
|
"""
|
||||||
|
Singleton-Pattern für globalen Shutdown-Manager
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout: Gesamttimeout für Shutdown-Operationen
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ShutdownManager: Globaler Shutdown-Manager
|
||||||
|
"""
|
||||||
|
global _shutdown_manager
|
||||||
|
|
||||||
|
with _manager_lock:
|
||||||
|
if _shutdown_manager is None:
|
||||||
|
_shutdown_manager = ShutdownManager(timeout=timeout)
|
||||||
|
|
||||||
|
# Registriere atexit-Handler als Fallback
|
||||||
|
atexit.register(_shutdown_manager.shutdown)
|
||||||
|
|
||||||
|
return _shutdown_manager
|
||||||
|
|
||||||
|
|
||||||
|
def register_for_shutdown(component_or_function,
|
||||||
|
name: str,
|
||||||
|
component_stop_method: str = "stop",
|
||||||
|
priority: int = 1,
|
||||||
|
timeout: int = 10):
|
||||||
|
"""
|
||||||
|
Vereinfachte Registrierung für Shutdown
|
||||||
|
|
||||||
|
Args:
|
||||||
|
component_or_function: Komponente mit Stop-Methode oder Cleanup-Funktion
|
||||||
|
name: Beschreibender Name
|
||||||
|
component_stop_method: Name der Stop-Methode für Komponenten
|
||||||
|
priority: Priorität (1=hoch, 3=niedrig)
|
||||||
|
timeout: Timeout in Sekunden
|
||||||
|
"""
|
||||||
|
manager = get_shutdown_manager()
|
||||||
|
|
||||||
|
if callable(component_or_function):
|
||||||
|
# Es ist eine Funktion
|
||||||
|
manager.register_cleanup_function(
|
||||||
|
func=component_or_function,
|
||||||
|
name=name,
|
||||||
|
priority=priority,
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Es ist eine Komponente
|
||||||
|
manager.register_component(
|
||||||
|
name=name,
|
||||||
|
component=component_or_function,
|
||||||
|
stop_method=component_stop_method
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def shutdown_application(exit_code: int = 0):
|
||||||
|
"""
|
||||||
|
Startet koordiniertes Shutdown der Anwendung
|
||||||
|
|
||||||
|
Args:
|
||||||
|
exit_code: Exit-Code für sys.exit()
|
||||||
|
"""
|
||||||
|
manager = get_shutdown_manager()
|
||||||
|
manager.shutdown(exit_code)
|
||||||
|
|
||||||
|
|
||||||
|
def is_shutdown_requested() -> bool:
|
||||||
|
"""Prüft ob Shutdown angefordert wurde"""
|
||||||
|
if _shutdown_manager:
|
||||||
|
return _shutdown_manager.is_shutdown_requested()
|
||||||
|
return False
|
Loading…
x
Reference in New Issue
Block a user