import os import signal import subprocess import sys import time def _is_debug_requested() -> bool: """ True, wenn -D übergeben wurde oder APP_DEBUG gesetzt ist. -D erlaubt den Dev-Start direkt von der Befehlszeile — ohne Devcontainer und ohne Env-Variable setzen zu müssen. APP_DEBUG steuert den Devcontainer-Betrieb (docker-compose.dev.yaml). """ if "-D" in sys.argv[1:]: return True return os.environ.get("APP_DEBUG", "false").lower() in ("true", "1", "yes") def run_server(debug: bool): """ Startet den Server. Prod: Waitress. Dev: Werkzeug mit Reloader. Der Aufruf erfolgt entweder direkt (Prod) oder als supervidierter Subprozess (Dev, siehe run_dev_supervised). debug wird an init_app() durchgereicht, damit app.config["DEBUG"] konsistent ist — davon hängt außer dem Server-Typ auch die Auswahl des JS-Bundles ab (routes/general.py: .js im Dev, .min.js in Prod). """ from waitress import serve from werkzeug import serving from approot import init_app app = init_app(debug=debug) if not debug: print("Server startet mit Server type: waitress", flush=True) serve(app, listen='*:8080') else: print("Server startet mit Server type: werkzeug", flush=True) serving.run_simple("0.0.0.0", 8080, app, use_reloader=True) def run_dev_supervised(): """ Crash-toleranter Dev-Betrieb. Der Werkzeug-Reloader beendet sich komplett, wenn der App-Prozess beim Start crasht (z.B. SQLAlchemy-Fehler beim Import der Modelle). Wäre der Server direkt der Hauptprozess des Containers, würde damit der gesamte Dev-Container stoppen. Der Supervisor startet den Server daher als Subprozess und nach einem Abbruch einfach neu — sobald die fehlerhafte Datei korrigiert ist, läuft der Server automatisch wieder an. Der Supervisor importiert die Anwendung absichtlich nicht (run_server importiert approot erst im Subprozess): Ein defekter Anwendungscode kann ihn nicht mitreißen. """ child = None def _forward_signal(signum, frame): # SIGTERM/SIGINT an den Subprozess weiterleiten, damit der # Container sauber heruntergefahren werden kann. if child is not None and child.poll() is None: child.terminate() sys.exit(0) signal.signal(signal.SIGTERM, _forward_signal) signal.signal(signal.SIGINT, _forward_signal) env = dict(os.environ) env["KBP_DEV_SUPERVISED"] = "true" while True: print("=== Supervisor: starte Dev-Server ===", flush=True) # sys.argv[1:] durchreichen, damit -D (und künftige Parameter) # im Subprozess ebenfalls wirksam sind. proc = subprocess.Popen( [sys.executable, os.path.abspath(__file__)] + sys.argv[1:], env=env, ) child = proc code = proc.wait() print(f"=== Dev-Server beendet (Exit-Code {code}) — Neustart in 2 Sekunden ===", flush=True) time.sleep(2) if __name__ == '__main__': _debug = _is_debug_requested() if _debug and os.environ.get("KBP_DEV_SUPERVISED") != "true": # Dev-Modus und noch nicht beaufsichtigt: Supervisor werden # (im Devcontainer ist das PID 1 und hält den Container am Leben) run_dev_supervised() else: # Prod-Modus, oder wir sind bereits der beaufsichtigte # Dev-Subprozess (der Supervisor setzt KBP_DEV_SUPERVISED): # als eigentlicher Server starten. run_server(_debug)