🎉 Improved backend functionality & documentation, optimized database files, and introduced shutdown management 🧹

This commit is contained in:
Till Tomczak 2025-06-01 02:42:15 +02:00
parent ea12a470c9
commit 486647fade
10 changed files with 1052 additions and 407 deletions

View File

@ -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
app_logger.info("✅ Zentraler Shutdown-Manager initialisiert")
except ImportError as e:
app_logger.error(f"❌ Shutdown-Manager konnte nicht geladen werden: {e}")
# Fallback auf die alte Methode
shutdown_manager = None
# Windows-spezifisches Signal-Handling als Fallback
def fallback_signal_handler(sig, frame):
"""Fallback Signal-Handler für ordnungsgemäßes Shutdown."""
app_logger.warning(f"🛑 Signal {sig} empfangen - fahre System herunter (Fallback)...")
try: try:
# Queue Manager stoppen # Queue Manager stoppen
app_logger.info("🔄 Beende Queue Manager...")
stop_queue_manager() stop_queue_manager()
# Scheduler stoppen falls aktiviert # Scheduler stoppen falls aktiviert
if SCHEDULER_ENABLED and scheduler: if SCHEDULER_ENABLED and scheduler:
try: try:
if hasattr(scheduler, 'shutdown'):
scheduler.shutdown(wait=True)
else:
scheduler.stop() scheduler.stop()
app_logger.info("Job-Scheduler gestoppt")
except Exception as e: except Exception as e:
app_logger.error(f"Fehler beim Stoppen des Schedulers: {str(e)}") app_logger.error(f"Fehler beim Stoppen des Schedulers: {str(e)}")
# ===== ROBUSTES DATENBANK-CLEANUP MIT NEUER LOGIC ===== app_logger.info("✅ Fallback-Shutdown abgeschlossen")
app_logger.info("💾 Führe robustes Datenbank-Cleanup durch...")
try:
# Importiere und verwende den neuen DatabaseCleanupManager
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"✅ 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"⚠️ Datenbank-Cleanup mit Problemen: {', '.join(cleanup_result['errors'])}")
# Trotzdem weiter - wenigstens WAL-Checkpoint versucht
except ImportError:
# Fallback auf die alte Methode falls Cleanup-Manager nicht verfügbar
app_logger.warning("Fallback: Verwende Legacy-Datenbank-Cleanup...")
try:
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
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) sys.exit(0)
except Exception as e: except Exception as e:
app_logger.error(f"❌ Fehler beim Shutdown: {str(e)}") app_logger.error(f"❌ Fehler beim Fallback-Shutdown: {str(e)}")
sys.exit(1) sys.exit(1)
# Signal-Handler registrieren (Windows-kompatibel) # Signal-Handler registrieren (Windows-kompatibel)
if os.name == 'nt': # Windows if os.name == 'nt': # Windows
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, fallback_signal_handler)
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, fallback_signal_handler)
# Zusätzlich für Flask-Development-Server signal.signal(signal.SIGBREAK, fallback_signal_handler)
signal.signal(signal.SIGBREAK, signal_handler)
else: # Unix/Linux else: # Unix/Linux
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, fallback_signal_handler)
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, fallback_signal_handler)
signal.signal(signal.SIGHUP, signal_handler) signal.signal(signal.SIGHUP, fallback_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,10 +6688,16 @@ 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
if shutdown_manager:
shutdown_manager.force_shutdown(1)
else:
try: try:
stop_queue_manager() stop_queue_manager()
except: except:

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@

View File

@ -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 Service läuft nicht - prüfen Sie die Logs: journalctl -u $service -f" warning "⚠️ $SERVICE_NAME Service läuft nicht - prüfen Sie die Logs: journalctl -u $SERVICE_NAME -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

View File

@ -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 - WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* 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 - Press CTRL+C to quit
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...

View File

@ -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

View File

@ -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,56 +89,91 @@ 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.warning("Queue-Manager läuft bereits")
return self
queue_logger.info("🚀 Starte Printer Queue Manager...")
self.is_running = True
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...") queue_logger.info("🔄 Beende Queue Manager...")
self.is_running = False self.is_running = False
self.shutdown_event.set() self.shutdown_event.set()
if self.monitor_thread and self.monitor_thread.is_alive(): if self.monitor_thread and self.monitor_thread.is_alive():
queue_logger.debug("⏳ Warte auf Thread-Beendigung...") queue_logger.debug("⏳ Warte auf Thread-Beendigung...")
self.monitor_thread.join(timeout=10)
# 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")
except Exception as e:
queue_logger.error(f"❌ Fehler beim Thread-Join: {e}")
self.monitor_thread = None self.monitor_thread = None
queue_logger.info("❌ Printer Queue Manager gestoppt") queue_logger.info("❌ Printer Queue Manager gestoppt")
@ -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."""

View 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