""" Zentrale Konfigurationsdatei für das 3D-Druck-Management-System Diese Datei enthält alle Konfigurationseinstellungen, die zuvor im config-Ordner waren. """ import os import json from datetime import timedelta def get_env_variable(name: str, default: str = None) -> str: """ Holt eine Umgebungsvariable oder gibt den Standardwert zurück. Args: name: Name der Umgebungsvariable default: Standardwert, falls die Variable nicht gesetzt ist Returns: str: Wert der Umgebungsvariable oder Standardwert """ return os.environ.get(name, default) # ===== GRUNDLEGENDE KONFIGURATION ===== # Hardcodierte Konfiguration SECRET_KEY = "7445630171969DFAC92C53CEC92E67A9CB2E00B3CB2F" # Dynamische Pfade basierend auf dem aktuellen Arbeitsverzeichnis BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Projekt-Wurzel DATABASE_PATH = os.path.join(BASE_DIR, "instance", "printer_manager.db") # ===== SMART PLUG KONFIGURATION ===== # TP-Link Tapo P110 Standardkonfiguration TAPO_USERNAME = "till.tomczak@mercedes-benz.com" TAPO_PASSWORD = "744563017196A" # Automatische Steckdosen-Erkennung aktivieren TAPO_AUTO_DISCOVERY = True # Standard-Steckdosen-IPs (diese können später in der Datenbank überschrieben werden) DEFAULT_TAPO_IPS = [ "192.168.0.103", # Erreichbare Steckdose laut Test "192.168.0.104", # Erreichbare Steckdose laut Test "192.168.0.100", "192.168.0.101", "192.168.0.102", "192.168.0.105" ] # Timeout-Konfiguration für Tapo-Verbindungen TAPO_TIMEOUT = 10 # Sekunden TAPO_RETRY_COUNT = 3 # Anzahl Wiederholungsversuche # ===== DRUCKER-KONFIGURATION ===== PRINTERS = { "Printer 1": {"ip": "192.168.0.100"}, "Printer 2": {"ip": "192.168.0.101"}, "Printer 3": {"ip": "192.168.0.102"}, "Printer 4": {"ip": "192.168.0.103"}, "Printer 5": {"ip": "192.168.0.104"}, "Printer 6": {"ip": "192.168.0.106"} } # ===== LOGGING-KONFIGURATION ===== LOG_DIR = os.path.join(BASE_DIR, "logs") LOG_SUBDIRS = ["app", "scheduler", "auth", "jobs", "printers", "errors", "user", "kiosk", "admin", "admin_api", "guest", "analytics", "uploads", "sessions"] LOG_LEVEL = "INFO" LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" LOG_FILE_MAX_BYTES = 10 * 1024 * 1024 # 10MB LOG_BACKUP_COUNT = 5 # ===== FLASK-KONFIGURATION ===== FLASK_HOST = "0.0.0.0" FLASK_PORT = 443 # Kann auf 8443 geändert werden für nicht-privilegierte Ports FLASK_FALLBACK_PORT = 8080 FLASK_DEBUG = False # In Produktion auf False setzen! SESSION_LIFETIME = timedelta(hours=2) # Session-Dauer # ===== UPLOAD-KONFIGURATION ===== UPLOAD_FOLDER = os.path.join(BASE_DIR, "uploads") ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'gcode', '3mf', 'stl', 'obj', 'amf'} MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB Maximum-Dateigröße MAX_FILE_SIZE = 16 * 1024 * 1024 # 16MB Maximum-Dateigröße für Drag & Drop System # ===== UMGEBUNGSKONFIGURATION ===== ENVIRONMENT = get_env_variable("MYP_ENVIRONMENT", "development") # ===== SSL-KONFIGURATION ===== SSL_ENABLED = get_env_variable("MYP_SSL_ENABLED", "True").lower() in ("true", "1", "yes") SSL_CERT_PATH = os.path.join(BASE_DIR, "certs", "myp.crt") SSL_KEY_PATH = os.path.join(BASE_DIR, "certs", "myp.key") SSL_HOSTNAME = get_env_variable("MYP_SSL_HOSTNAME", "localhost") # ===== SCHEDULER-KONFIGURATION ===== SCHEDULER_INTERVAL = 60 # Sekunden SCHEDULER_ENABLED = True # ===== DATENBANK-KONFIGURATION ===== DB_ENGINE = f"sqlite:///{DATABASE_PATH}" SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_ENGINE_OPTIONS = { 'pool_pre_ping': True, 'pool_recycle': 300, } # ===== SICHERHEITSKONFIGURATION ===== WTF_CSRF_ENABLED = True WTF_CSRF_TIME_LIMIT = 3600 # 1 Stunde SESSION_COOKIE_SECURE = SSL_ENABLED # Nur bei HTTPS SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_SAMESITE = 'Lax' # ===== E-MAIL-KONFIGURATION (Optional) ===== MAIL_SERVER = get_env_variable('MAIL_SERVER') MAIL_PORT = int(get_env_variable('MAIL_PORT', '587')) MAIL_USE_TLS = get_env_variable('MAIL_USE_TLS', 'true').lower() in ['true', 'on', '1'] MAIL_USERNAME = get_env_variable('MAIL_USERNAME') MAIL_PASSWORD = get_env_variable('MAIL_PASSWORD') # ===== HILFSFUNKTIONEN ===== def get_log_file(category: str) -> str: """ Gibt den Pfad zur Log-Datei für eine bestimmte Kategorie zurück. Args: category: Log-Kategorie (app, scheduler, auth, jobs, printers, errors, etc.) Returns: str: Pfad zur Log-Datei """ if category not in LOG_SUBDIRS: category = "app" return os.path.join(LOG_DIR, category, f"{category}.log") def ensure_log_directories(): """Erstellt alle erforderlichen Log-Verzeichnisse.""" os.makedirs(LOG_DIR, exist_ok=True) for subdir in LOG_SUBDIRS: os.makedirs(os.path.join(LOG_DIR, subdir), exist_ok=True) def ensure_database_directory(): """Erstellt das Datenbank-Verzeichnis.""" db_dir = os.path.dirname(DATABASE_PATH) if db_dir: os.makedirs(db_dir, exist_ok=True) def ensure_ssl_directory(): """Erstellt das SSL-Verzeichnis, falls es nicht existiert.""" ssl_dir = os.path.dirname(SSL_CERT_PATH) if ssl_dir and not os.path.exists(ssl_dir): os.makedirs(ssl_dir, exist_ok=True) def ensure_upload_directory(): """Erstellt das Upload-Verzeichnis, falls es nicht existiert.""" if not os.path.exists(UPLOAD_FOLDER): os.makedirs(UPLOAD_FOLDER, exist_ok=True) # Erstelle Unterordner für verschiedene Upload-Typen subdirs = ['jobs', 'guests', 'avatars', 'assets', 'logs', 'backups', 'temp'] for subdir in subdirs: os.makedirs(os.path.join(UPLOAD_FOLDER, subdir), exist_ok=True) def get_ssl_context(): """ Gibt den SSL-Kontext für Flask zurück, wenn SSL aktiviert ist. Returns: tuple oder None: Tuple mit Zertifikat- und Schlüsselpfad, wenn SSL aktiviert ist, sonst None """ if not SSL_ENABLED: return None # Wenn Zertifikate nicht existieren, diese automatisch erstellen if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH): ensure_ssl_directory() # Im Entwicklungsmodus versuchen wir, einfache Zertifikate zu erstellen if FLASK_DEBUG: print("SSL-Zertifikate nicht gefunden. Erstelle einfache selbstsignierte Zertifikate...") try: # Einfache Zertifikate mit Python erstellen create_simple_ssl_cert() # Prüfen, ob die Zertifikate erfolgreich erstellt wurden if not os.path.exists(SSL_CERT_PATH) or not os.path.exists(SSL_KEY_PATH): print("Konnte keine SSL-Zertifikate erstellen.") return None except Exception as e: print(f"Fehler beim Erstellen der SSL-Zertifikate: {e}") return None else: print("WARNUNG: SSL-Zertifikate nicht gefunden und Nicht-Debug-Modus. SSL wird deaktiviert.") return None return (SSL_CERT_PATH, SSL_KEY_PATH) def create_simple_ssl_cert(): """ Erstellt ein Mercedes-Benz SSL-Zertifikat mit dem SSL-Manager. """ try: # Verwende den SSL-Manager from utils.ssl_manager import ssl_manager success = ssl_manager.generate_mercedes_certificate() if success: print(f"Mercedes-Benz SSL-Zertifikat erfolgreich erstellt: {SSL_CERT_PATH}") return True else: print("Fehler beim Erstellen des Mercedes-Benz SSL-Zertifikats") return None except ImportError as e: print(f"SSL-Manager nicht verfügbar: {e}") return None except Exception as e: print(f"Fehler beim Erstellen der SSL-Zertifikate: {e}") return None # ===== KONFIGURATIONSKLASSEN FÜR VERSCHIEDENE UMGEBUNGEN ===== class Config: """Basis-Konfigurationsklasse mit gemeinsamen Einstellungen.""" SECRET_KEY = SECRET_KEY PERMANENT_SESSION_LIFETIME = SESSION_LIFETIME SESSION_COOKIE_SECURE = SESSION_COOKIE_SECURE SESSION_COOKIE_HTTPONLY = SESSION_COOKIE_HTTPONLY SESSION_COOKIE_SAMESITE = SESSION_COOKIE_SAMESITE SQLALCHEMY_DATABASE_URI = DB_ENGINE SQLALCHEMY_TRACK_MODIFICATIONS = SQLALCHEMY_TRACK_MODIFICATIONS SQLALCHEMY_ENGINE_OPTIONS = SQLALCHEMY_ENGINE_OPTIONS UPLOAD_FOLDER = UPLOAD_FOLDER MAX_CONTENT_LENGTH = MAX_CONTENT_LENGTH ALLOWED_EXTENSIONS = ALLOWED_EXTENSIONS WTF_CSRF_ENABLED = WTF_CSRF_ENABLED WTF_CSRF_TIME_LIMIT = WTF_CSRF_TIME_LIMIT LOG_LEVEL = LOG_LEVEL LOG_FILE_MAX_BYTES = LOG_FILE_MAX_BYTES LOG_BACKUP_COUNT = LOG_BACKUP_COUNT SCHEDULER_ENABLED = SCHEDULER_ENABLED SCHEDULER_INTERVAL = SCHEDULER_INTERVAL SSL_ENABLED = SSL_ENABLED SSL_CERT_PATH = SSL_CERT_PATH SSL_KEY_PATH = SSL_KEY_PATH DEFAULT_PORT = FLASK_PORT DEFAULT_HOST = FLASK_HOST @staticmethod def init_app(app): """Initialisiere Anwendung mit dieser Konfiguration.""" pass class DevelopmentConfig(Config): """Entwicklungsumgebung-Konfiguration.""" DEBUG = True TESTING = False LOG_LEVEL = 'DEBUG' SESSION_COOKIE_SECURE = False WTF_CSRF_ENABLED = False # Für einfacheres API-Testing @staticmethod def init_app(app): Config.init_app(app) import logging logging.basicConfig(level=logging.DEBUG) class TestingConfig(Config): """Test-Umgebung-Konfiguration.""" TESTING = True DEBUG = True SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:' WTF_CSRF_ENABLED = False PERMANENT_SESSION_LIFETIME = timedelta(minutes=5) @staticmethod def init_app(app): Config.init_app(app) class ProductionConfig(Config): """Produktionsumgebung-Konfiguration.""" DEBUG = False TESTING = False SESSION_COOKIE_SECURE = True # Erfordert HTTPS WTF_CSRF_ENABLED = True LOG_LEVEL = 'WARNING' SSL_ENABLED = True @staticmethod def init_app(app): Config.init_app(app) # Produktions-spezifische Initialisierung import logging from logging.handlers import RotatingFileHandler # Log-Verzeichnis sicherstellen ensure_log_directories() # Datei-Logging für Produktion einrichten file_handler = RotatingFileHandler( get_log_file('app'), maxBytes=Config.LOG_FILE_MAX_BYTES, backupCount=Config.LOG_BACKUP_COUNT ) file_handler.setFormatter(logging.Formatter(LOG_FORMAT)) file_handler.setLevel(logging.WARNING) app.logger.addHandler(file_handler) app.logger.setLevel(logging.WARNING) # Konfigurations-Dictionary für einfachen Zugriff config = { 'development': DevelopmentConfig, 'testing': TestingConfig, 'production': ProductionConfig, 'default': DevelopmentConfig } def get_config_by_name(config_name): """ Hole Konfigurationsklasse nach Name. Args: config_name (str): Name der Konfiguration ('development', 'testing', 'production') Returns: Config: Konfigurationsklasse """ return config.get(config_name, config['default'])