feat(runserver): Selbst-supervidierenden Dev-Server, -D-Parameter funktional
- Supervisor-Modus im Dev-Betrieb: Der Server läuft als Subprozess und wird nach einem Abbruch neu gestartet. Der Werkzeug-Reloader beendet sich sonst komplett, wenn der App-Prozess beim Start crasht — als Hauptprozess des Containers würde damit der gesamte Dev-Container stoppen. Der Supervisor importiert die App nicht und überlebt so jeden Anwendungsfehler. - -D-Parameter ist jetzt funktional: Dev-Start direkt von der Befehlszeile ohne Devcontainer/Env-Variable. Debug-Entscheidung: -D ODER APP_DEBUG. - approot.py: init_app(debug) reicht die Debug-Entscheidung durch, damit Server-Typ und JS-Bundle-Auswahl (.js/.min.js) konsistent bleiben. wsgi.py (init_app() ohne Argument) unverändert. Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
1 parent
4e721e9a47
commit
24b492c5c1
2 files changed
+96
-15
No files matched your search
@@ -9,7 +9,7 @@ import os
|
|||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
|
|
||||||
|
|
||||||
def init_app():
|
def init_app(debug: bool | None = None):
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
config: Configuration = Configuration("/usr/opt/pythonapp/App_Server/root/config.ini")
|
config: Configuration = Configuration("/usr/opt/pythonapp/App_Server/root/config.ini")
|
||||||
@@ -20,7 +20,13 @@ def init_app():
|
|||||||
"auth": "sqlite:////usr/var/pythonappdb/auth.db",
|
"auth": "sqlite:////usr/var/pythonappdb/auth.db",
|
||||||
"appdb": "sqlite:////usr/var/pythonappdb/{}.db".format(_projektname)
|
"appdb": "sqlite:////usr/var/pythonappdb/{}.db".format(_projektname)
|
||||||
}
|
}
|
||||||
|
# Dev-Modus: CLI-Parameter -D (via runserver) oder APP_DEBUG-Env.
|
||||||
|
# debug=None = wie bisher allein über die Env-Variable entscheiden
|
||||||
|
# (unverändertes Verhalten für wsgi.py).
|
||||||
|
if debug is None:
|
||||||
app.config["DEBUG"] = os.environ.get("APP_DEBUG", "false").lower() in ("true", "1", "yes")
|
app.config["DEBUG"] = os.environ.get("APP_DEBUG", "false").lower() in ("true", "1", "yes")
|
||||||
|
else:
|
||||||
|
app.config["DEBUG"] = debug
|
||||||
app.config["PROJEKTNAME"] = config.app_projectname
|
app.config["PROJEKTNAME"] = config.app_projectname
|
||||||
app.config["APPID"] = config.app_appid
|
app.config["APPID"] = config.app_appid
|
||||||
app.config["APPVERSION"] = config.clientapp_version
|
app.config["APPVERSION"] = config.clientapp_version
|
||||||
|
|||||||
@@ -1,23 +1,98 @@
|
|||||||
|
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 waitress import serve
|
||||||
from werkzeug import serving
|
from werkzeug import serving
|
||||||
from approot import init_app
|
from approot import init_app
|
||||||
|
|
||||||
|
app = init_app(debug=debug)
|
||||||
def run_server():
|
if not debug:
|
||||||
try:
|
print("Server startet mit Server type: waitress", flush=True)
|
||||||
if app.config["DEBUG"] is False:
|
|
||||||
_server = "waitress"
|
|
||||||
# serve(app, listen='nginx-proxy:8080') # Funktioniert aus unbekannten Gründen nicht. Der Hostname wird im Docker Container nicht korrekt aufgelöst
|
|
||||||
print(f"Server startet mit Server type: {_server}", flush=True)
|
|
||||||
serve(app, listen='*:8080')
|
serve(app, listen='*:8080')
|
||||||
else:
|
else:
|
||||||
_server = "werkzeug"
|
print("Server startet mit Server type: werkzeug", flush=True)
|
||||||
print(f"Server startet mit Server type: {_server}", flush=True)
|
|
||||||
serving.run_simple("0.0.0.0", 8080, app, use_reloader=True)
|
serving.run_simple("0.0.0.0", 8080, app, use_reloader=True)
|
||||||
except Exception as ex:
|
|
||||||
print(str(ex))
|
|
||||||
print(f"Server gestartet. Server type: {_server}", flush=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__':
|
if __name__ == '__main__':
|
||||||
app = init_app()
|
_debug = _is_debug_requested()
|
||||||
run_server()
|
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)
|
||||||
Reference in new issue
Block a user