603 lines
20 KiB
Python
603 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
MYP Druckerverwaltung - UNIFIED VERSION
|
|
======================================
|
|
|
|
Einheitliche Flask App für Entwicklung UND Produktion.
|
|
Diese App ersetzt sowohl app.py als auch app_production.py.
|
|
|
|
Verwendung:
|
|
- Development: python app_unified.py
|
|
- Production: sudo python app_unified.py --production
|
|
- SSL-Force: python app_unified.py --ssl
|
|
|
|
Version: 6.0.0 Unified
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import ssl
|
|
import logging
|
|
import platform
|
|
import argparse
|
|
from datetime import datetime, timedelta
|
|
|
|
# Füge App-Verzeichnis zum Python-Pfad hinzu
|
|
sys.path.insert(0, '/opt/myp')
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
# Import der Haupt-App-Logik
|
|
from app import app, app_logger, init_database, create_initial_admin, main as app_main
|
|
from app import start_queue_manager, stop_queue_manager, get_job_scheduler, cleanup_rate_limiter
|
|
|
|
# Flask-Imports für Request-Handling
|
|
from flask import request, redirect
|
|
|
|
# =========================== UMGEBUNGS-ERKENNUNG ===========================
|
|
|
|
def detect_environment():
|
|
"""Erkennt automatisch die Laufzeitumgebung"""
|
|
|
|
# Kommandozeilen-Argumente prüfen
|
|
if '--production' in sys.argv or '--prod' in sys.argv:
|
|
return 'production'
|
|
|
|
if '--development' in sys.argv or '--dev' in sys.argv:
|
|
return 'development'
|
|
|
|
# Umgebungsvariablen prüfen
|
|
env_mode = os.getenv('MYP_MODE', '').lower()
|
|
if env_mode in ['production', 'prod']:
|
|
return 'production'
|
|
elif env_mode in ['development', 'dev']:
|
|
return 'development'
|
|
|
|
# Automatische Erkennung basierend auf System
|
|
if detect_raspberry_pi():
|
|
return 'production'
|
|
|
|
if platform.system() == 'Windows':
|
|
return 'development'
|
|
|
|
# Standard: Development für unbekannte Systeme
|
|
return 'development'
|
|
|
|
def detect_raspberry_pi():
|
|
"""Erkennt ob das System auf einem Raspberry Pi läuft"""
|
|
try:
|
|
with open('/proc/cpuinfo', 'r') as f:
|
|
cpuinfo = f.read()
|
|
if 'Raspberry Pi' in cpuinfo or 'BCM' in cpuinfo:
|
|
return True
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
machine = platform.machine().lower()
|
|
if 'arm' in machine or 'aarch64' in machine:
|
|
return True
|
|
except:
|
|
pass
|
|
|
|
return os.getenv('FORCE_RASPBERRY_PI', '').lower() in ['true', '1', 'yes']
|
|
|
|
def should_use_ssl():
|
|
"""Bestimmt ob SSL verwendet werden soll"""
|
|
if '--ssl' in sys.argv or '--https' in sys.argv:
|
|
return True
|
|
|
|
if '--no-ssl' in sys.argv or '--http' in sys.argv:
|
|
return False
|
|
|
|
env_ssl = os.getenv('MYP_SSL', '').lower()
|
|
if env_ssl in ['true', '1', 'yes', 'force']:
|
|
return True
|
|
elif env_ssl in ['false', '0', 'no', 'disable']:
|
|
return False
|
|
|
|
# Automatisch: SSL für Production, HTTP für Development
|
|
return detect_environment() == 'production'
|
|
|
|
# =========================== KONFIGURATIONSKLASSEN ===========================
|
|
|
|
class DevelopmentConfig:
|
|
"""Konfiguration für Entwicklungsumgebung"""
|
|
|
|
# Debug-Einstellungen
|
|
DEBUG = True
|
|
TESTING = False
|
|
|
|
# HTTP-Konfiguration
|
|
FORCE_HTTPS = False
|
|
SSL_REQUIRED = False
|
|
HTTP_PORT = 5000
|
|
|
|
# Performance (weniger optimiert für bessere Debug-Möglichkeiten)
|
|
OPTIMIZED_MODE = False
|
|
USE_MINIFIED_ASSETS = False
|
|
DISABLE_ANIMATIONS = False
|
|
|
|
# Session-Konfiguration (weniger restriktiv für Development)
|
|
SESSION_COOKIE_SECURE = False
|
|
SESSION_COOKIE_HTTPONLY = True
|
|
SESSION_COOKIE_SAMESITE = 'Lax'
|
|
|
|
# Reload-Features für Development
|
|
TEMPLATES_AUTO_RELOAD = True
|
|
EXPLAIN_TEMPLATE_LOADING = False
|
|
|
|
class ProductionConfig:
|
|
"""Konfiguration für Produktionsumgebung"""
|
|
|
|
# Produktions-Einstellungen
|
|
DEBUG = False
|
|
TESTING = False
|
|
|
|
# HTTPS-Only Konfiguration
|
|
FORCE_HTTPS = True
|
|
SSL_REQUIRED = True
|
|
HTTPS_PORT = 443
|
|
|
|
# Performance-Optimierungen
|
|
OPTIMIZED_MODE = True
|
|
USE_MINIFIED_ASSETS = True
|
|
DISABLE_ANIMATIONS = True
|
|
|
|
# Sicherheits-Einstellungen
|
|
SESSION_COOKIE_SECURE = True
|
|
SESSION_COOKIE_HTTPONLY = True
|
|
SESSION_COOKIE_SAMESITE = 'Strict'
|
|
WTF_CSRF_ENABLED = True
|
|
|
|
# Template-Optimierungen
|
|
TEMPLATES_AUTO_RELOAD = False
|
|
EXPLAIN_TEMPLATE_LOADING = False
|
|
|
|
# SSL-Konfiguration
|
|
SSL_CERT_PATH = None # Wird automatisch erkannt
|
|
SSL_KEY_PATH = None # Wird automatisch erkannt
|
|
|
|
# =========================== SSL-SETUP ===========================
|
|
|
|
def get_ssl_paths():
|
|
"""Ermittelt die SSL-Zertifikat-Pfade plattformspezifisch"""
|
|
|
|
if platform.system() == 'Windows':
|
|
ssl_dir = os.path.join(os.path.dirname(__file__), 'ssl')
|
|
else:
|
|
# Probiere verschiedene Standard-Pfade
|
|
possible_dirs = [
|
|
'/opt/myp/ssl',
|
|
'/etc/ssl/myp',
|
|
os.path.join(os.path.dirname(__file__), 'ssl'),
|
|
'./ssl'
|
|
]
|
|
|
|
ssl_dir = None
|
|
for dir_path in possible_dirs:
|
|
if os.path.exists(dir_path):
|
|
ssl_dir = dir_path
|
|
break
|
|
|
|
if not ssl_dir:
|
|
ssl_dir = possible_dirs[0] # Erstelle in /opt/myp/ssl
|
|
|
|
cert_file = os.path.join(ssl_dir, 'cert.pem')
|
|
key_file = os.path.join(ssl_dir, 'key.pem')
|
|
|
|
return ssl_dir, cert_file, key_file
|
|
|
|
def setup_ssl_certificates():
|
|
"""Erstellt SSL-Zertifikate falls sie nicht existieren"""
|
|
|
|
ssl_dir, cert_file, key_file = get_ssl_paths()
|
|
|
|
app_logger.info(f"🔐 Prüfe SSL-Zertifikate in: {ssl_dir}")
|
|
|
|
# Erstelle SSL-Verzeichnis
|
|
os.makedirs(ssl_dir, exist_ok=True)
|
|
|
|
# Prüfe ob Zertifikate existieren
|
|
if os.path.exists(cert_file) and os.path.exists(key_file):
|
|
try:
|
|
# Teste Zertifikat-Gültigkeit
|
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
context.load_cert_chain(cert_file, key_file)
|
|
app_logger.info("✅ Bestehende SSL-Zertifikate sind gültig")
|
|
return cert_file, key_file
|
|
except Exception as e:
|
|
app_logger.warning(f"⚠️ Bestehende SSL-Zertifikate ungültig: {e}")
|
|
|
|
# Erstelle neue Zertifikate
|
|
app_logger.info("🔧 Erstelle neue SSL-Zertifikate...")
|
|
|
|
try:
|
|
# Versuche existierende SSL-Utilities zu verwenden
|
|
if os.path.exists('./ssl/ssl_fix.py'):
|
|
try:
|
|
import subprocess
|
|
result = subprocess.run([
|
|
sys.executable, './ssl/ssl_fix.py'
|
|
], capture_output=True, text=True, timeout=60)
|
|
|
|
if result.returncode == 0:
|
|
app_logger.info("✅ SSL-Zertifikate mit ssl_fix.py erstellt")
|
|
return cert_file, key_file
|
|
except Exception as e:
|
|
app_logger.warning(f"⚠️ ssl_fix.py fehlgeschlagen: {e}")
|
|
|
|
# Fallback: Einfache SSL-Erstellung
|
|
create_simple_ssl_certificates(ssl_dir, cert_file, key_file)
|
|
return cert_file, key_file
|
|
|
|
except Exception as e:
|
|
app_logger.error(f"❌ SSL-Zertifikat-Erstellung fehlgeschlagen: {e}")
|
|
raise Exception(f"SSL-Setup fehlgeschlagen: {e}")
|
|
|
|
def create_simple_ssl_certificates(ssl_dir, cert_file, key_file):
|
|
"""Erstellt einfache selbstsignierte SSL-Zertifikate"""
|
|
|
|
try:
|
|
# Versuche mit Python Cryptography Library
|
|
from cryptography import x509
|
|
from cryptography.x509.oid import NameOID
|
|
from cryptography.hazmat.primitives import hashes, serialization
|
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
import ipaddress
|
|
|
|
app_logger.info("🐍 Erstelle SSL-Zertifikate mit Python Cryptography...")
|
|
|
|
# Private Key generieren
|
|
private_key = rsa.generate_private_key(
|
|
public_exponent=65537,
|
|
key_size=2048,
|
|
)
|
|
|
|
# Subject und Issuer
|
|
subject = issuer = x509.Name([
|
|
x509.NameAttribute(NameOID.COUNTRY_NAME, "DE"),
|
|
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Baden-Wuerttemberg"),
|
|
x509.NameAttribute(NameOID.LOCALITY_NAME, "Stuttgart"),
|
|
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Mercedes-Benz AG"),
|
|
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, "MYP Druckerverwaltung"),
|
|
x509.NameAttribute(NameOID.COMMON_NAME, platform.node()),
|
|
])
|
|
|
|
# Subject Alternative Names
|
|
san_list = [
|
|
x509.DNSName("localhost"),
|
|
x509.DNSName("127.0.0.1"),
|
|
x509.IPAddress(ipaddress.IPv4Address("127.0.0.1")),
|
|
x509.DNSName(platform.node()),
|
|
]
|
|
|
|
# Zertifikat erstellen
|
|
cert = x509.CertificateBuilder().subject_name(
|
|
subject
|
|
).issuer_name(
|
|
issuer
|
|
).public_key(
|
|
private_key.public_key()
|
|
).serial_number(
|
|
x509.random_serial_number()
|
|
).not_valid_before(
|
|
datetime.now()
|
|
).not_valid_after(
|
|
datetime.now() + timedelta(days=365)
|
|
).add_extension(
|
|
x509.SubjectAlternativeName(san_list),
|
|
critical=False,
|
|
).sign(private_key, hashes.SHA256())
|
|
|
|
# Private Key schreiben
|
|
with open(key_file, 'wb') as f:
|
|
f.write(private_key.private_bytes(
|
|
encoding=serialization.Encoding.PEM,
|
|
format=serialization.PrivateFormat.PKCS8,
|
|
encryption_algorithm=serialization.NoEncryption()
|
|
))
|
|
|
|
# Zertifikat schreiben
|
|
with open(cert_file, 'wb') as f:
|
|
f.write(cert.public_bytes(serialization.Encoding.PEM))
|
|
|
|
# Berechtigungen setzen (Unix)
|
|
try:
|
|
os.chmod(cert_file, 0o644)
|
|
os.chmod(key_file, 0o600)
|
|
except:
|
|
pass # Windows hat andere Berechtigungen
|
|
|
|
app_logger.info("✅ SSL-Zertifikate mit Python Cryptography erstellt")
|
|
|
|
except ImportError:
|
|
# Fallback: OpenSSL verwenden
|
|
app_logger.info("🔧 Erstelle SSL-Zertifikate mit OpenSSL...")
|
|
import subprocess
|
|
|
|
# Private Key erstellen
|
|
subprocess.run([
|
|
'openssl', 'genrsa', '-out', key_file, '2048'
|
|
], check=True, capture_output=True)
|
|
|
|
# Selbstsigniertes Zertifikat erstellen
|
|
subprocess.run([
|
|
'openssl', 'req', '-new', '-x509',
|
|
'-key', key_file,
|
|
'-out', cert_file,
|
|
'-days', '365',
|
|
'-subj', f'/C=DE/ST=Baden-Wuerttemberg/L=Stuttgart/O=Mercedes-Benz AG/CN={platform.node()}'
|
|
], check=True, capture_output=True)
|
|
|
|
app_logger.info("✅ SSL-Zertifikate mit OpenSSL erstellt")
|
|
|
|
def get_ssl_context():
|
|
"""Erstellt SSL-Kontext mit Zertifikaten"""
|
|
|
|
if not should_use_ssl():
|
|
return None
|
|
|
|
try:
|
|
cert_file, key_file = setup_ssl_certificates()
|
|
|
|
# SSL-Kontext erstellen
|
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
|
context.load_cert_chain(cert_file, key_file)
|
|
|
|
# Sichere SSL-Einstellungen
|
|
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
|
|
context.options |= ssl.OP_NO_SSLv2
|
|
context.options |= ssl.OP_NO_SSLv3
|
|
context.options |= ssl.OP_NO_TLSv1
|
|
context.options |= ssl.OP_NO_TLSv1_1
|
|
|
|
app_logger.info("✅ SSL-Kontext erfolgreich konfiguriert")
|
|
return context
|
|
|
|
except Exception as e:
|
|
app_logger.error(f"❌ SSL-Kontext-Erstellung fehlgeschlagen: {e}")
|
|
app_logger.warning("⚠️ Fallback zu HTTP ohne SSL")
|
|
return None
|
|
|
|
# =========================== APP-KONFIGURATION ===========================
|
|
|
|
def configure_app_for_environment(environment):
|
|
"""Konfiguriert die App für die erkannte Umgebung"""
|
|
|
|
if environment == 'production':
|
|
config_class = ProductionConfig
|
|
app_logger.info("🚀 Produktions-Modus aktiviert")
|
|
else:
|
|
config_class = DevelopmentConfig
|
|
app_logger.info("🔧 Entwicklungs-Modus aktiviert")
|
|
|
|
# Konfiguration anwenden
|
|
for attr in dir(config_class):
|
|
if not attr.startswith('_'):
|
|
app.config[attr] = getattr(config_class, attr)
|
|
|
|
# Jinja-Globals setzen
|
|
app.jinja_env.globals.update({
|
|
'environment': environment,
|
|
'optimized_mode': config_class.OPTIMIZED_MODE,
|
|
'use_minified_assets': config_class.USE_MINIFIED_ASSETS if hasattr(config_class, 'USE_MINIFIED_ASSETS') else False,
|
|
'disable_animations': config_class.DISABLE_ANIMATIONS if hasattr(config_class, 'DISABLE_ANIMATIONS') else False,
|
|
})
|
|
|
|
return config_class
|
|
|
|
# =========================== MIDDLEWARE ===========================
|
|
|
|
@app.before_request
|
|
def force_https_if_required():
|
|
"""Erzwingt HTTPS wenn in der Konfiguration aktiviert"""
|
|
if (app.config.get('FORCE_HTTPS', False) and
|
|
not request.is_secure and
|
|
not request.headers.get('X-Forwarded-Proto') == 'https'):
|
|
|
|
# Redirect zu HTTPS
|
|
url = request.url.replace('http://', 'https://', 1)
|
|
if ':5000' in url:
|
|
url = url.replace(':5000', ':443')
|
|
elif ':80' in url:
|
|
url = url.replace(':80', ':443')
|
|
|
|
return redirect(url, code=301)
|
|
|
|
@app.after_request
|
|
def add_environment_headers(response):
|
|
"""Fügt umgebungsspezifische Headers hinzu"""
|
|
|
|
if app.config.get('FORCE_HTTPS', False):
|
|
# Produktions-Sicherheits-Headers
|
|
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
|
|
response.headers['X-Content-Type-Options'] = 'nosniff'
|
|
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
|
response.headers['X-XSS-Protection'] = '1; mode=block'
|
|
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
|
|
|
|
# Cache-Headers für statische Dateien
|
|
if request.endpoint == 'static' or '/static/' in request.path:
|
|
if app.config.get('OPTIMIZED_MODE', False):
|
|
response.headers['Cache-Control'] = 'public, max-age=31536000'
|
|
else:
|
|
response.headers['Cache-Control'] = 'public, max-age=3600'
|
|
|
|
return response
|
|
|
|
# =========================== LOGGING-SETUP ===========================
|
|
|
|
def setup_environment_logging(environment):
|
|
"""Konfiguriert Logging für die Umgebung"""
|
|
|
|
if environment == 'production':
|
|
# Produktions-Logging: Weniger verbose
|
|
logging.getLogger('werkzeug').setLevel(logging.WARNING)
|
|
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
|
app_logger.setLevel(logging.INFO)
|
|
|
|
# Entferne Debug-Handler
|
|
for handler in app_logger.handlers[:]:
|
|
if handler.level == logging.DEBUG:
|
|
app_logger.removeHandler(handler)
|
|
else:
|
|
# Development-Logging: Vollständig
|
|
app_logger.setLevel(logging.DEBUG)
|
|
|
|
app_logger.info(f"✅ Logging für {environment} konfiguriert")
|
|
|
|
# =========================== ARGUMENT-PARSER ===========================
|
|
|
|
def parse_arguments():
|
|
"""Parst Kommandozeilen-Argumente für vereinheitlichte Steuerung"""
|
|
parser = argparse.ArgumentParser(description='MYP Druckerverwaltung - Unified Server')
|
|
|
|
parser.add_argument('--production', '--prod', action='store_true',
|
|
help='Starte im Produktions-Modus')
|
|
parser.add_argument('--ssl', '--https', action='store_true',
|
|
help='Erzwinge SSL/HTTPS')
|
|
parser.add_argument('--port', type=int, default=None,
|
|
help='Port-Nummer')
|
|
|
|
return parser.parse_args()
|
|
|
|
def show_usage_info():
|
|
"""Zeigt Nutzungsinformationen an"""
|
|
environment = "Production" if '--production' in sys.argv else "Development"
|
|
ssl_enabled = '--ssl' in sys.argv or '--production' in sys.argv
|
|
|
|
app_logger.info("🎯 MYP Unified App - Eine einzige funktionale App!")
|
|
app_logger.info(f"📋 Modus: {environment}")
|
|
app_logger.info(f"🔐 SSL: {'Aktiviert' if ssl_enabled else 'Deaktiviert'}")
|
|
app_logger.info(f"💻 Plattform: {platform.system()}")
|
|
app_logger.info("=" * 60)
|
|
|
|
# =========================== HAUPTFUNKTION ===========================
|
|
|
|
def main():
|
|
"""Hauptfunktion für den unified Server"""
|
|
|
|
try:
|
|
# Argumente parsen
|
|
args = parse_arguments()
|
|
|
|
# Umgebung ermitteln
|
|
environment = detect_environment()
|
|
|
|
# Logging für Umgebung konfigurieren
|
|
setup_environment_logging(environment)
|
|
|
|
app_logger.info("🚀 MYP Unified Server startet...")
|
|
app_logger.info(f"📅 Start-Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
app_logger.info(f"🖥️ Hostname: {platform.node()}")
|
|
app_logger.info(f"🐍 Python: {sys.version}")
|
|
app_logger.info(f"🌍 Umgebung: {environment}")
|
|
app_logger.info(f"💻 Plattform: {platform.system()} {platform.release()}")
|
|
|
|
# App für Umgebung konfigurieren
|
|
config_class = configure_app_for_environment(environment)
|
|
|
|
# Root-Berechtigung prüfen (nur für Production + Port 443)
|
|
if (environment == 'production' and
|
|
config_class.HTTPS_PORT == 443 and
|
|
hasattr(os, 'geteuid') and
|
|
os.geteuid() != 0):
|
|
app_logger.error("❌ Root-Berechtigung erforderlich für Port 443")
|
|
app_logger.error("💡 Führe aus mit: sudo python app_unified.py --production")
|
|
sys.exit(1)
|
|
elif platform.system() == 'Windows' and environment == 'production':
|
|
app_logger.info("🪟 Windows: Root-Check übersprungen")
|
|
|
|
# SSL-Kontext erstellen falls erforderlich
|
|
ssl_context = get_ssl_context()
|
|
|
|
# Datenbank initialisieren
|
|
init_database()
|
|
create_initial_admin()
|
|
|
|
# Background-Services starten
|
|
start_queue_manager()
|
|
|
|
scheduler = get_job_scheduler()
|
|
if scheduler:
|
|
scheduler.start()
|
|
app_logger.info("✅ Job-Scheduler gestartet")
|
|
|
|
# Server-Konfiguration
|
|
if args.port:
|
|
port = args.port
|
|
elif ssl_context and environment == 'production':
|
|
port = 443
|
|
elif environment == 'production':
|
|
port = 5443 # Alternative HTTPS-Port falls keine Root-Rechte
|
|
else:
|
|
port = 5000 # Development HTTP-Port
|
|
|
|
# Debug-Modus
|
|
debug_mode = (environment == 'development' and not ssl_context)
|
|
|
|
# Server-Informationen anzeigen
|
|
protocol = 'https' if ssl_context else 'http'
|
|
app_logger.info(f"🌐 Server läuft auf: {protocol}://{platform.node()}:{port}")
|
|
if platform.system() == 'Windows':
|
|
app_logger.info(f"🏠 Lokaler Zugriff: {protocol}://localhost:{port}")
|
|
|
|
if ssl_context:
|
|
app_logger.info("🔐 SSL/HTTPS aktiviert")
|
|
else:
|
|
app_logger.info("🔓 HTTP-Modus (unverschlüsselt)")
|
|
|
|
# Flask-Server starten
|
|
app.run(
|
|
host=platform.node(),
|
|
port=port,
|
|
ssl_context=ssl_context,
|
|
debug=debug_mode,
|
|
threaded=True,
|
|
use_reloader=False # Deaktiviert für Produktionsstabilität
|
|
)
|
|
|
|
except PermissionError:
|
|
app_logger.error("❌ Berechtigung verweigert")
|
|
if platform.system() != 'Windows':
|
|
app_logger.error("💡 Führe als Root aus: sudo python app_unified.py --production")
|
|
else:
|
|
app_logger.error("💡 Führe als Administrator aus")
|
|
sys.exit(1)
|
|
|
|
except OSError as e:
|
|
if "Address already in use" in str(e):
|
|
app_logger.error("❌ Port bereits belegt")
|
|
app_logger.error("💡 Andere Services stoppen oder anderen Port verwenden")
|
|
else:
|
|
app_logger.error(f"❌ Netzwerk-Fehler: {e}")
|
|
sys.exit(1)
|
|
|
|
except KeyboardInterrupt:
|
|
app_logger.info("🛑 Server durch Benutzer gestoppt")
|
|
sys.exit(0)
|
|
|
|
except Exception as e:
|
|
app_logger.error(f"❌ Kritischer Fehler beim Server-Start: {e}")
|
|
import traceback
|
|
app_logger.error(f"Traceback: {traceback.format_exc()}")
|
|
sys.exit(1)
|
|
|
|
finally:
|
|
# Cleanup
|
|
try:
|
|
stop_queue_manager()
|
|
if 'scheduler' in locals() and scheduler:
|
|
scheduler.shutdown()
|
|
cleanup_rate_limiter()
|
|
app_logger.info("✅ Cleanup abgeschlossen")
|
|
except:
|
|
pass
|
|
|
|
if __name__ == "__main__":
|
|
args = parse_arguments()
|
|
show_usage_info()
|
|
|
|
# Verwende die existierende App-Main-Funktion
|
|
app_main() |