#!/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

# **Der venv muss fuer den Dienstbenutzer lesbar sein.**
#
# Dieses Programm laeuft als ExecStartPre der Dienst-Unit -- und erbt damit
# deren UMask=0027. Alles, was es anlegt, gehoerte root und waere 0750: der
# Dienst selbst laeuft unprivilegiert und koennte sein eigenes site-packages
# nicht einmal betreten. Gemessen: nach der Reparatur ging "import gunicorn"
# als root, als Benutzer tesm nicht -- die Reparatur baute etwas, das genau
# der Prozess nicht benutzen konnte, fuer den sie gedacht war.
#
# Im venv liegt nichts Geheimes; Schluessel und Datenbank stehen woanders.
umask 022

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

# Und noch einmal ausdruecklich, fuer den Fall, dass ein frueherer Lauf mit
# strengerer Maske gebaut hat: was hier liegt, muss lesbar sein. Idempotent
# und in einer Sekunde durch.
chmod -R a+rX "$VENV" 2>/dev/null || true

[[ "$MODUS" == "immer" ]] || melden "INFO" "Python-Umgebung von $NAME ist wieder vollstaendig."
rm -f "$MERKER" 2>/dev/null || true
exit 0
