136 lines
5.5 KiB
Bash
136 lines
5.5 KiB
Bash
#!/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 <verzeichnis> <tesm|tesm-license> [--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
|