#!/usr/bin/env bash # ============================================================================= # Die Python-Umgebung einer Installation -- aufbauen, pruefen, reparieren. # # tesm-venv /srv/tesm tesm # reparieren, falls noetig (Dienststart) # tesm-venv /srv/tesm tesm --immer # neu bestuecken (Installation) # tesm-venv /srv/tesm tesm --pruefen # nur melden: 0 heil, 1 kaputt # # **Warum es das gibt.** Ein venv haengt an der Minor-Fassung von Python: die # Pakete liegen unter lib/python3.X. Ein Release-Upgrade des Betriebssystems # tauscht diese Fassung aus (Ubuntu 24.04 bringt 3.12, 26.04 bringt 3.14) -- # und danach zeigt venv/bin/python3 weiter auf /usr/bin/python3, also auf den # **neuen** Interpreter. Der venv ist ausfuehrbar und trotzdem tot: er findet # kein einziges Paket mehr, nicht einmal pip. Jeder Dienst scheitert mit # "ModuleNotFoundError: No module named 'gunicorn'", nginx meldet 502. # # Real passiert, auf beiden Testservern, am 16.09.2026. # # Dieses Programm laeuft deshalb als ExecStartPre vor jedem Dienststart. Ist # alles heil, endet es nach Millisekunden. Sonst baut es die Umgebung neu -- # aus denselben Paketen, die auch die Installation einspielt, denn es ist # dasselbe Programm: install.sh ruft es mit --immer auf. Zwei getrennte Listen # waeren zwei Listen, die auseinanderlaufen. # # **Was es nicht tut:** die alten Pakete unter lib/python3.X uebernehmen. # cryptography, argon2-cffi und bcrypt sind kompiliert und fuer die alte # Fassung gebaut; ein halb uebernommener venv waere schlimmer als ein neuer. # ============================================================================= set -euo pipefail ROOT="${1:-}" APP="${2:-}" MODUS="reparieren" [[ "${3:-}" == "--immer" ]] && MODUS="immer" [[ "${3:-}" == "--pruefen" ]] && MODUS="pruefen" [[ -n "$ROOT" && -n "$APP" ]] || { echo "Aufruf: tesm-venv [--immer|--pruefen]" >&2 exit 2 } [[ "$APP" == "tesm" || "$APP" == "tesm-license" ]] || { echo "tesm-venv: unbekannte Anwendung '$APP'" >&2 exit 2 } [[ -d "$ROOT" ]] || { echo "tesm-venv: $ROOT gibt es nicht" >&2; exit 2; } NAME="$(basename "$ROOT")" VENV="$ROOT/venv" PY="$VENV/bin/python3" MODUL="tesm"; [[ "$APP" == "tesm-license" ]] && MODUL="tesm_license" LOG_DIR="${TESM_LOG_DIR:-/var/log/$NAME}" #: Ein Versuch je Systemstart. /run ist ein tmpfs, der Merker verschwindet #: also beim naechsten Start von selbst. Ohne ihn wuerde "Restart=always" nach #: einem misslungenen Neuaufbau (kein Netz, kein PyPI) alle fuenf Sekunden #: einen neuen pip-Lauf starten -- gegen einen Server, der ohnehin gerade #: nicht erreichbar ist. MERKER="/run/tesm-venv-$NAME.versucht" melden() { local stufe="$1" text="$2" # Ins Journal: ExecStartPre-Ausgabe landet dort ohnehin. printf 'tesm-venv: %s\n' "$text" >&2 # Und ins laufende Protokoll, damit es in der Oberflaeche sichtbar ist. # Fehlschlaege hier duerfen den Start nicht kosten -- ein Protokoll ist # kein Grund, einen Dienst nicht zu starten. if [[ -d "$LOG_DIR" ]]; then printf '%s %-7s %-10s %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$stufe" "venv" "$text" \ >> "$LOG_DIR/live.log" 2>/dev/null || true chown "$APP:$APP" "$LOG_DIR/live.log" 2>/dev/null || true fi } #: Traegt diese Umgebung? Gefragt wird nicht nach der Versionsnummer, sondern #: nach dem, was gebraucht wird -- so faellt auch ein venv auf, dem jemand ein #: Paket herausgeloescht hat. traegt() { [[ -x "$PY" ]] || return 1 "$PY" -c "import pip" >/dev/null 2>&1 || return 1 # Beim allerersten Lauf ist die Anwendung noch nicht eingespielt -- dann # zaehlt allein, dass pip benutzbar ist. [[ "$MODUS" == "immer" ]] && return 0 "$PY" -c "import $MODUL" >/dev/null 2>&1 } bestuecken() { "$PY" -m pip install --quiet --upgrade pip setuptools wheel local pakete=("$ROOT/packages/tesm-licensing" "$ROOT/packages/tesm-core") if [[ "$APP" == "tesm-license" ]]; then [[ -d "$ROOT/packages/tesm-licensing-server" ]] || { echo "tesm-venv: packages/tesm-licensing-server fehlt" >&2 return 1 } pakete+=("$ROOT/packages/tesm-licensing-server") fi "$PY" -m pip install --quiet "${pakete[@]}" "$ROOT/apps/$APP" gunicorn # Die WebSocket-Bruecke braucht nur TESM (Terminal und Bildschirm). [[ "$APP" == "tesm" ]] && "$PY" -m pip install --quiet flask-sock return 0 } # --------------------------------------------------------------------------- # if [[ "$MODUS" == "pruefen" ]]; then traegt && exit 0 || exit 1 fi if traegt && [[ "$MODUS" != "immer" ]]; then exit 0 fi if [[ "$MODUS" != "immer" && -e "$MERKER" ]]; then melden "WARNUNG" \ "Die Python-Umgebung von $NAME ist unbrauchbar, und der Neuaufbau ist seit dem letzten Systemstart schon einmal gescheitert. Erneuter Versuch: 'bash $ROOT/deploy/install.sh --app $APP --yes'." exit 1 fi touch "$MERKER" 2>/dev/null || true if ! [[ -x "$PY" ]] || ! "$PY" -c "import pip" >/dev/null 2>&1; then # Ohne --clear: ein erneutes "venv" ergaenzt das fehlende lib-Verzeichnis # und holt pip zurueck. Gemessen, nicht angenommen. python3 -m venv "$VENV" [[ "$MODUS" == "immer" ]] || melden "HINWEIS" \ "Python-Umgebung von $NAME neu aufgebaut ($(python3 -V 2>&1)) -- das System hat seine Python-Fassung gewechselt." fi if ! bestuecken; then melden "FEHLER" \ "Die Pakete fuer $NAME liessen sich nicht einspielen. Hat dieser Server Zugriff auf das Paketverzeichnis?" exit 1 fi [[ "$MODUS" == "immer" ]] || melden "INFO" "Python-Umgebung von $NAME ist wieder vollstaendig." rm -f "$MERKER" 2>/dev/null || true exit 0