From bc360a0b7f79e0aba33b3cd8905dc04e1021197f Mon Sep 17 00:00:00 2001 From: alientim Date: Fri, 14 Aug 2026 09:09:57 +0200 Subject: [PATCH] Produktiver WSGI-Server (Gunicorn) statt Flasks Entwicklungsserver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tesm.service startete die App bisher direkt per 'python3 app.py', was Flasks eingebauten Werkzeug-Entwicklungsserver nutzt -- der meldet bei jedem Start 'WARNING: This is a development server. Do not use it in a production deployment.' und ist bewusst nicht für Dauerbetrieb gebaut. Umgestellt auf Gunicorn, mit zwei bewusst gesetzten Einschraenkungen: - '--workers 1': die App haelt laufende Wartungs-Jobs/Import-Vorschauen nur im Arbeitsspeicher DES EINEN Prozesses (_maintenance_jobs, _pending_imports, ...) -- mehrere Worker-Prozesse wuerden diesen Zustand auf mehrere, sich nicht kennende Prozesse verteilen. - '--worker-class gthread --threads 16' statt eines async-Workers: das Browser-SSH-Terminal (flask-sock) belegt pro offener WebSocket- Verbindung einen blockierenden Thread. - '--timeout 0': deaktiviert Gunicorns eigenen 30s-Worker-Timeout, der sonst auch eine offene, aber gerade inaktive SSH-Terminal-Verbindung killen wuerde. Live auf POETEST verifiziert: keine WARNING-Zeile mehr, genau 1 Master- + 1 Worker-Prozess, alle Kernseiten weiterhin 200, und als kritischer Regressionstest das WebSocket-SSH-Terminal Ende-zu-Ende gegen einen echten Host erfolgreich getestet (Host-Key/Passwort-Prompt/Login/Shell). Dabei einen unabhaengigen Altlast-Fehler in POETESTs venv gefunden und allgemein in install.sh gefixt: './venv/bin/pip' hat eine zur Venv-Erstellung fest einprogrammierte Shebang-Zeile -- nach der historischen Umbenennung 'poe_manager' -> 'tesm' zeigte sie auf einen nicht mehr existierenden Pfad und schlug mit 'cannot execute: required file not found' fehl. 'python3 -m pip' umgeht das, da nur der (korrekt referenzierte) venv-Python-Interpreter selbst gebraucht wird -- betrifft sonst auch jedes kuenftige In-Place-Update auf einem aehnlich gewachsenen Host. --- etc/systemd/system/tesm.service | 12 ++++++++++-- install.sh | 13 +++++++++++-- srv/tesm/VERSION | 2 +- srv/tesm/requirements.txt | 13 +++++++++++++ 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/etc/systemd/system/tesm.service b/etc/systemd/system/tesm.service index 1c59b3e..caa078c 100644 --- a/etc/systemd/system/tesm.service +++ b/etc/systemd/system/tesm.service @@ -6,8 +6,16 @@ After=network.target Type=simple User=root WorkingDirectory=/srv/tesm -# Nutze die virtuelle Umgebung -ExecStart=/srv/tesm/venv/bin/python3 /srv/tesm/app.py +# Produktiver WSGI-Server (gunicorn) statt Flasks eigenem app.run()- +# Entwicklungsserver -- siehe requirements.txt für die ausführliche +# Begründung von "--workers 1" (In-Memory-Zustand) und "--worker-class +# gthread" (blockierende WebSocket-Threads für das Browser-SSH-Terminal). +# "--timeout 0" deaktiviert Gunicorns Worker-Timeout: der greift sonst +# nach 30s auch bei einer offenen, aber gerade inaktiven SSH-Terminal- +# WebSocket-Verbindung und würde den Worker (und damit JEDE Verbindung, +# da nur ein Prozess läuft) killen. +ExecStart=/srv/tesm/venv/bin/gunicorn --workers 1 --worker-class gthread --threads 16 \ + --timeout 0 --bind 0.0.0.0:5000 app:app Restart=always RestartSec=5 diff --git a/install.sh b/install.sh index 81a00da..3b38e67 100644 --- a/install.sh +++ b/install.sh @@ -45,11 +45,20 @@ sudo rsync -a --delete --exclude 'venv' --exclude 'sqlite.db' --exclude 'fernet. print_status "Application files copied" # ---- Python venv ---- +# "python3 -m pip" statt des "pip"-Skripts im venv selbst: dessen Shebang +# enthält einen zur Erstellungszeit FEST einprogrammierten, absoluten Pfad +# zum venv-Python. Wird das venv-Verzeichnis später umbenannt/verschoben +# (wie hier historisch bei der Umbenennung "poe_manager" -> "tesm" +# passiert), bleibt die alte, nicht mehr existierende Pfadangabe stehen -- +# "./venv/bin/pip install ..." schlägt dann mit "cannot execute: required +# file not found" fehl (live auf POETEST reproduziert), obwohl das venv +# selbst voll funktionsfähig ist. "python3 -m pip" braucht dagegen nur den +# (korrekt referenzierten) venv-Python-Interpreter selbst. step "Setting up Python virtual environment" cd /srv/tesm sudo python3 -m venv venv -sudo ./venv/bin/pip install --upgrade pip >>/var/log/tesm-install.log 2>&1 -sudo ./venv/bin/pip install -r requirements.txt >>/var/log/tesm-install.log 2>&1 +sudo ./venv/bin/python3 -m pip install --upgrade pip >>/var/log/tesm-install.log 2>&1 +sudo ./venv/bin/python3 -m pip install -r requirements.txt >>/var/log/tesm-install.log 2>&1 print_status "Virtual environment ready" # ---- Datenbank ---- diff --git a/srv/tesm/VERSION b/srv/tesm/VERSION index 7dea76e..6d7de6e 100644 --- a/srv/tesm/VERSION +++ b/srv/tesm/VERSION @@ -1 +1 @@ -1.0.1 +1.0.2 diff --git a/srv/tesm/requirements.txt b/srv/tesm/requirements.txt index 226ede6..ccf1ecf 100644 --- a/srv/tesm/requirements.txt +++ b/srv/tesm/requirements.txt @@ -7,6 +7,7 @@ Flask==3.1.2 Flask-Bcrypt==1.0.1 Flask-Login==0.6.3 Flask-Sock==0.7.0 +gunicorn==23.0.0 itsdangerous==2.2.0 Jinja2==3.1.6 ldap3==2.9.1 @@ -31,3 +32,15 @@ Werkzeug==3.1.3 # ldap3 (reines Python, keine System-Bibliothek wie libldap nötig) für die # optionale Active-Directory/LDAP-Anmeldung (Systemeinstellungen → LDAP) — # Search+Bind gegen einen AD-Domain-Controller oder generischen LDAP-Server. +# +# gunicorn: produktiver WSGI-Server für tesm.service (siehe dort) statt +# Flasks eigenem app.run()-Entwicklungsserver ("WARNING: This is a +# development server..."). Läuft bewusst mit genau EINEM Worker-Prozess, +# aber mehreren Threads (--worker-class gthread) -- mehrere Prozesse +# würden den In-Memory-Zustand für laufende Wartungs-Jobs/Import- +# Vorschauen (_maintenance_jobs/_pending_imports/...) auf mehrere, +# voneinander isolierte Prozesse verteilen, wodurch eine Anfrage, die vom +# jeweils ANDEREN Worker beantwortet wird, diesen Zustand nie sehen würde. +# Mehrere Threads (statt eines async-Workers) sind nötig, weil das +# Browser-SSH-Terminal (flask-sock) pro offener WebSocket-Verbindung einen +# blockierenden Thread belegt.