Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37c508297f | ||
|
|
13bf07fecf | ||
|
|
849b821501 | ||
|
|
00a27018d9 |
No files matched your search
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "tesm"
|
||||
version = "2.8.31"
|
||||
version = "2.8.35"
|
||||
description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
|
||||
+16
-43
@@ -418,49 +418,16 @@ info "Dateien abgeglichen (Instanzdaten unter data/ bleiben unangetastet)"
|
||||
|
||||
# -- venv --------------------------------------------------------------------
|
||||
step "Python-Umgebung"
|
||||
# Ein venv haengt an der **Minor-Fassung**, mit der er gebaut wurde: die Pakete
|
||||
# liegen unter lib/python3.X. Wechselt die Systemfassung -- ein Release-Upgrade
|
||||
# von Ubuntu bringt genau das, 24.04 hat 3.12 und 26.04 hat 3.14 --, dann ist
|
||||
# venv/bin/python3 weiter ausfuehrbar: der Verweis zeigt auf /usr/bin/python3,
|
||||
# also auf den **neuen** Interpreter. Der venv sieht heil aus und ist es nicht.
|
||||
# Er findet danach kein einziges Paket mehr, nicht einmal pip.
|
||||
#
|
||||
# Gemessen auf dem Testserver, mit verschobenem lib-Verzeichnis:
|
||||
# [[ -x venv/bin/python3 ]] -> wahr
|
||||
# venv/bin/python3 -m pip --version -> "No module named pip"
|
||||
#
|
||||
# Die alte Pruefung fragte nur nach der Ausfuehrbarkeit. Sie haette den toten
|
||||
# venv behalten und waere eine Zeile spaeter an "python3 -m pip" gescheitert --
|
||||
# mitten in einer Aktualisierung, auf einem Server, dessen Dienste ohnehin
|
||||
# schon stehen. Gefragt wird deshalb, was gebraucht wird: laesst sich in
|
||||
# diesem venv pip benutzen?
|
||||
venv_traegt() {
|
||||
[[ -x "$ROOT/venv/bin/python3" ]] || return 1
|
||||
"$ROOT/venv/bin/python3" -c "import pip" >/dev/null 2>&1
|
||||
}
|
||||
if ! venv_traegt; then
|
||||
# Ohne --clear: ein erneutes "venv" ergaenzt das fehlende lib-Verzeichnis und
|
||||
# holt pip zurueck; die Abhaengigkeiten kommen gleich darunter ohnehin neu.
|
||||
# Auch das ist gemessen und nicht angenommen.
|
||||
python3 -m venv "$ROOT/venv"
|
||||
info "Python-Umgebung aufgebaut ($(python3 -V 2>&1))"
|
||||
fi
|
||||
# Immer ueber "python3 -m pip" -- der pip-Shim im venv baeckt einen absoluten
|
||||
# Pfad in seinen Shebang, der nach einer Umbenennung des Verzeichnisses bricht.
|
||||
"$ROOT/venv/bin/python3" -m pip install --quiet --upgrade pip setuptools wheel
|
||||
PAKETE=("$ROOT/packages/tesm-licensing" "$ROOT/packages/tesm-core")
|
||||
# Die Serverhaelfte des Lizenzprotokolls -- ausstellen, erneuern, antworten --
|
||||
# gehoert ausschliesslich zum Lizenzserver. Ein Client verifiziert Lizenzen und
|
||||
# stellt Anfragen; den Code zum Ausstellen soll er nicht einmal mitbringen.
|
||||
if [[ "$APP" == "tesm-license" ]]; then
|
||||
[[ -d "$ROOT/packages/tesm-licensing-server" ]] ||
|
||||
fail "packages/tesm-licensing-server fehlt -- ohne sie kann der Lizenzserver nichts ausstellen"
|
||||
PAKETE+=("$ROOT/packages/tesm-licensing-server")
|
||||
fi
|
||||
"$ROOT/venv/bin/python3" -m pip install --quiet "${PAKETE[@]}" "$ROOT/apps/$APP" gunicorn
|
||||
if [[ "$APP" == "tesm" ]]; then
|
||||
"$ROOT/venv/bin/python3" -m pip install --quiet flask-sock
|
||||
fi
|
||||
# Aufbauen, pruefen und reparieren steht in genau einem Programm -- und
|
||||
# dasselbe laeuft als ExecStartPre vor jedem Dienststart. Zwei getrennte
|
||||
# Paketlisten waeren zwei Listen, die auseinanderlaufen: die Reparatur baute
|
||||
# dann eine Umgebung, die nicht mehr der Installation entspricht.
|
||||
# Ueber "bash" und nicht als Programm: Dateien im Baum tragen kein
|
||||
# Ausfuehrungsrecht -- git kennt auf einer Windows-Buildmaschine keines, und
|
||||
# install.sh, tesm-helper und tesm-motd werden aus demselben Grund ebenso
|
||||
# aufgerufen oder mit ausdruecklichem Modus installiert. Ohne das brach die
|
||||
# Aktualisierung mit "Permission denied" ab (und rollte zurueck).
|
||||
bash "$ROOT/deploy/tesm-venv" "$ROOT" "$APP" --immer
|
||||
info "Abhaengigkeiten installiert"
|
||||
|
||||
# -- Statische Dateien fuer nginx --------------------------------------------
|
||||
@@ -490,6 +457,12 @@ install_if_changed() {
|
||||
install_if_changed "$ROOT/deploy/tesm-helper" /usr/local/lib/tesm/tesm-helper 0755 && \
|
||||
info "Helfer aktualisiert" || info "Helfer unveraendert"
|
||||
|
||||
# Derselbe Baustein, der oben die Umgebung gebaut hat: die Dienste rufen ihn
|
||||
# vor jedem Start auf und reparieren damit, was ein Release-Upgrade des
|
||||
# Betriebssystems ihnen unter den Fuessen weggezogen hat.
|
||||
install_if_changed "$ROOT/deploy/tesm-venv" /usr/local/lib/tesm/tesm-venv 0755 >/dev/null \
|
||||
&& info "Umgebungspfleger aktualisiert" || true
|
||||
|
||||
# visudo -c prueft die Datei, bevor sie aktiv wird -- eine kaputte sudoers-Datei
|
||||
# waere sonst ein Totalausfall der Administration.
|
||||
TMP_SUDO="$(mktemp)"
|
||||
|
||||
@@ -15,11 +15,23 @@ Environment=TESM_LICENSE_LOG_DIR=/var/log/tesm-license
|
||||
Environment=TESM_LICENSE_WEB_PROCESS=1
|
||||
Environment=TESM_LICENSE_COOKIE_SECURE=1
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
# Vor jedem Start: traegt die Python-Umgebung noch? Ein Release-Upgrade des
|
||||
# Betriebssystems tauscht die Minor-Fassung von Python aus, und der venv haengt
|
||||
# daran -- danach findet der Dienst kein einziges Paket mehr und scheitert mit
|
||||
# "No module named gunicorn". Ist alles heil, kostet diese Zeile Millisekunden;
|
||||
# sonst baut sie die Umgebung neu, einmal je Systemstart.
|
||||
#
|
||||
# Das "+" laesst den Schritt als root laufen. Noetig, weil der venv root
|
||||
# gehoert, der Dienst aber als unprivilegierter Benutzer laeuft.
|
||||
ExecStartPre=+/usr/local/lib/tesm/tesm-venv /srv/tesm-license tesm-license
|
||||
ExecStart=/srv/tesm-license/venv/bin/gunicorn \
|
||||
--workers 1 --worker-class gthread --threads 8 \
|
||||
--bind 127.0.0.1:5001 \
|
||||
--access-logfile - --error-logfile - \
|
||||
tesm_license.wsgi:app
|
||||
# Ein Neuaufbau der Python-Umgebung laedt Pakete und dauert Minuten --
|
||||
# die Vorgabe von 90 Sekunden wuerde ihn mittendrin abschneiden.
|
||||
TimeoutStartSec=900
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
|
||||
@@ -12,7 +12,19 @@ Environment=TESM_BASE_DIR=/srv/tesm
|
||||
Environment=TESM_DATA_DIR=/srv/tesm/data
|
||||
Environment=TESM_LOG_DIR=/var/log/tesm
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
# Vor jedem Start: traegt die Python-Umgebung noch? Ein Release-Upgrade des
|
||||
# Betriebssystems tauscht die Minor-Fassung von Python aus, und der venv haengt
|
||||
# daran -- danach findet der Dienst kein einziges Paket mehr und scheitert mit
|
||||
# "No module named gunicorn". Ist alles heil, kostet diese Zeile Millisekunden;
|
||||
# sonst baut sie die Umgebung neu, einmal je Systemstart.
|
||||
#
|
||||
# Das "+" laesst den Schritt als root laufen. Noetig, weil der venv root
|
||||
# gehoert, der Dienst aber als unprivilegierter Benutzer laeuft.
|
||||
ExecStartPre=+/usr/local/lib/tesm/tesm-venv /srv/tesm tesm
|
||||
ExecStart=/srv/tesm/venv/bin/tesm-monitor
|
||||
# Ein Neuaufbau der Python-Umgebung laedt Pakete und dauert Minuten --
|
||||
# die Vorgabe von 90 Sekunden wuerde ihn mittendrin abschneiden.
|
||||
TimeoutStartSec=900
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
|
||||
@@ -15,11 +15,23 @@ Environment=TESM_LOG_DIR=/var/log/tesm
|
||||
Environment=TESM_WEB_PROCESS=1
|
||||
Environment=TESM_COOKIE_SECURE=1
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
# Vor jedem Start: traegt die Python-Umgebung noch? Ein Release-Upgrade des
|
||||
# Betriebssystems tauscht die Minor-Fassung von Python aus, und der venv haengt
|
||||
# daran -- danach findet der Dienst kein einziges Paket mehr und scheitert mit
|
||||
# "No module named gunicorn". Ist alles heil, kostet diese Zeile Millisekunden;
|
||||
# sonst baut sie die Umgebung neu, einmal je Systemstart.
|
||||
#
|
||||
# Das "+" laesst den Schritt als root laufen. Noetig, weil der venv root
|
||||
# gehoert, der Dienst aber als unprivilegierter Benutzer laeuft.
|
||||
ExecStartPre=+/usr/local/lib/tesm/tesm-venv /srv/tesm tesm
|
||||
ExecStart=/srv/tesm/venv/bin/gunicorn \
|
||||
--workers 1 --worker-class gthread --threads 16 --timeout 0 \
|
||||
--bind 127.0.0.1:5000 \
|
||||
--access-logfile - --error-logfile - \
|
||||
tesm.wsgi:app
|
||||
# Ein Neuaufbau der Python-Umgebung laedt Pakete und dauert Minuten --
|
||||
# die Vorgabe von 90 Sekunden wuerde ihn mittendrin abschneiden.
|
||||
TimeoutStartSec=900
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#!/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
|
||||
@@ -0,0 +1,303 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# Rueckbau einer Installation von TESM oder TESM-Lizenzserver.
|
||||
#
|
||||
# Aufruf:
|
||||
# sudo bash deploy/uninstall.sh --app tesm
|
||||
# sudo bash deploy/uninstall.sh --app tesm-license --instance test
|
||||
# sudo bash deploy/uninstall.sh --app tesm --dry-run
|
||||
#
|
||||
# Das genaue Gegenstueck zu install.sh -- und es fehlte. Wer eine Installation
|
||||
# von Hand loeschte ("rm -rf /srv/tesm-probe"), liess zurueck: die systemd-Unit
|
||||
# (die systemd danach in einer Dauerschleife neu zu starten versuchte), den
|
||||
# Begruessungshaken (der bei jeder Anmeldung seinen Banner druckte), den
|
||||
# Shim unter /usr/local/bin, die Umgebungsdatei, die nginx-Site und den
|
||||
# Dienstbenutzer. Auf dem Testserver standen so neun tote Dienste und vierzehn
|
||||
# Banner. Genau das raeumt dieses Skript ab.
|
||||
#
|
||||
# Zwei Regeln, die den Unterschied machen:
|
||||
#
|
||||
# * **Daten bleiben.** Datenbank, Schluessel und Lizenz werden vorher in ein
|
||||
# Archiv gelegt, nicht geloescht. Wer sie wirklich los sein will, sagt es
|
||||
# mit --purge. Ein Rueckbau, der beilaeufig die Datenbank mitnimmt, ist
|
||||
# keiner, sondern ein Unfall.
|
||||
# * **Geteiltes bleibt, solange es jemand braucht.** sudoers-Datei und
|
||||
# Dienstbenutzer gehoeren der *Anwendung* (nicht der Instanz), der Helfer
|
||||
# unter /usr/local/lib/tesm allen Installationen beider Anwendungen. Sie
|
||||
# verschwinden erst, wenn die letzte geht -- sonst legt der Rueckbau einer
|
||||
# Testinstanz die Produktion daneben lahm.
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
APP=""
|
||||
INSTANCE=""
|
||||
PURGE=0
|
||||
ASSUME_YES=0
|
||||
DRY_RUN=0
|
||||
|
||||
#: Ueberschreibbar, damit sich die Entscheidungen dieses Skripts in einem Test
|
||||
#: wirklich ausfuehren lassen -- und nicht nur als Textmuster geprueft werden.
|
||||
ETC_TESM="${TESM_ETC_DIR:-/etc/tesm}"
|
||||
SRV_DIR="${TESM_SRV_DIR:-/srv}"
|
||||
UNIT_DIR="${TESM_SYSTEMD_DIR:-/etc/systemd/system}"
|
||||
MOTD_DIR="${TESM_MOTD_DIR:-/etc/update-motd.d}"
|
||||
BIN_DIR="${TESM_BIN_DIR:-/usr/local/bin}"
|
||||
LIB_DIR="${TESM_LIB_DIR:-/usr/local/lib/tesm}"
|
||||
SUDOERS_DIR="${TESM_SUDOERS_DIR:-/etc/sudoers.d}"
|
||||
LOGROTATE_DIR="${TESM_LOGROTATE_DIR:-/etc/logrotate.d}"
|
||||
NGINX_DIR="${TESM_NGINX_DIR:-/etc/nginx}"
|
||||
LOG_BASE="${TESM_LOG_BASE:-/var/log}"
|
||||
|
||||
COLOR_OK=$'\033[32m'; COLOR_WARN=$'\033[33m'; COLOR_ERR=$'\033[31m'; COLOR_OFF=$'\033[0m'
|
||||
step() { printf '\n%s==>%s %s\n' "$COLOR_OK" "$COLOR_OFF" "$*"; }
|
||||
warn() { printf '%s !!%s %s\n' "$COLOR_WARN" "$COLOR_OFF" "$*" ; }
|
||||
fail() { printf '%s !!%s %s\n' "$COLOR_ERR" "$COLOR_OFF" "$*" >&2; exit 1; }
|
||||
info() { printf ' %s\n' "$*"; }
|
||||
|
||||
usage() {
|
||||
cat <<'TEXT'
|
||||
Rueckbau einer Installation.
|
||||
|
||||
--app <tesm|tesm-license> welche Anwendung (Pflicht)
|
||||
--instance <name> benannte Instanz (sonst die Hauptinstallation)
|
||||
--purge Daten nicht sichern, sondern loeschen
|
||||
--dry-run nur zeigen, was geschehen wuerde
|
||||
--yes, -y nicht nachfragen
|
||||
TEXT
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--app) APP="${2:?}"; shift 2 ;;
|
||||
--instance) INSTANCE="${2:?}"; shift 2 ;;
|
||||
--purge) PURGE=1; shift ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
--yes|-y) ASSUME_YES=1; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
*) fail "unbekannte Option: $1" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ -n "$APP" ]] || { usage; fail "--app fehlt"; }
|
||||
[[ "$APP" == "tesm" || "$APP" == "tesm-license" ]] || fail "--app muss tesm oder tesm-license sein"
|
||||
[[ -z "$INSTANCE" || "$INSTANCE" =~ ^[a-z0-9-]{1,16}$ ]] || fail "ungueltiger Instanzname"
|
||||
# Root braucht es wegen /srv, /etc und systemd. Wer die Pfade umbiegt (Test),
|
||||
# fasst nichts davon an -- dann ist die Huerde keine Sicherheit, sondern nur im
|
||||
# Weg, und ein Rueckbau, der sich nicht pruefen laesst, ist ein Risiko fuer sich.
|
||||
if [[ $DRY_RUN -eq 0 && "$SRV_DIR" == "/srv" && "$(id -u)" -ne 0 ]]; then
|
||||
fail "bitte mit sudo ausfuehren"
|
||||
fi
|
||||
|
||||
NAME="$APP"
|
||||
[[ -n "$INSTANCE" ]] && NAME="$APP-$INSTANCE"
|
||||
ROOT="$SRV_DIR/$NAME"
|
||||
|
||||
# Das Datenverzeichnis kann verschoben sein -- dann steht es in der
|
||||
# Umgebungsdatei, und nur dort. Ein Rueckbau, der stattdessen "$ROOT/data"
|
||||
# annimmt, sichert ein leeres Verzeichnis und laesst die echte Datenbank liegen.
|
||||
ENV_FILE="$ETC_TESM/$NAME.env"
|
||||
DATEN_DIR="$(sed -n 's/^TESM_DATA_DIR=//p' "$ENV_FILE" 2>/dev/null | tail -1 || true)"
|
||||
DATEN_DIR="${DATEN_DIR:-$ROOT/data}"
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Wer teilt sich was?
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
#: Gibt es neben dieser noch eine weitere Installation **derselben** Anwendung?
|
||||
#: Daran haengen sudoers-Datei und Dienstbenutzer -- beide tragen den Namen der
|
||||
#: Anwendung, nicht den der Instanz.
|
||||
weitere_dieser_anwendung() {
|
||||
local eintrag name
|
||||
for eintrag in "$ETC_TESM/$APP.env" "$ETC_TESM/$APP-"*.env; do
|
||||
[[ -f "$eintrag" ]] || continue
|
||||
name="$(basename "$eintrag" .env)"
|
||||
[[ "$name" == "$NAME" ]] && continue
|
||||
return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
#: Gibt es ueberhaupt noch irgendeine Installation? Daran haengen der Helfer
|
||||
#: und das Banner-Programm unter /usr/local/lib/tesm -- die gehoeren allen.
|
||||
weitere_ueberhaupt() {
|
||||
local eintrag name
|
||||
for eintrag in "$ETC_TESM"/*.env; do
|
||||
[[ -f "$eintrag" ]] || continue
|
||||
name="$(basename "$eintrag" .env)"
|
||||
[[ "$name" == "$NAME" ]] && continue
|
||||
return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Der Plan -- erst zeigen, dann tun
|
||||
# --------------------------------------------------------------------------- #
|
||||
UNITS=("$NAME.service" "$NAME-monitor.service" "$NAME-cleanup.service" "$NAME-cleanup.timer")
|
||||
DATEIEN=(
|
||||
"$NGINX_DIR/sites-enabled/$NAME"
|
||||
"$NGINX_DIR/sites-available/$NAME"
|
||||
"$MOTD_DIR/50-$NAME"
|
||||
"$BIN_DIR/$NAME-admin"
|
||||
"$LOGROTATE_DIR/$NAME"
|
||||
"$ENV_FILE"
|
||||
)
|
||||
VERZEICHNISSE=("$ETC_TESM/certs/$NAME" "$LOG_BASE/$NAME")
|
||||
|
||||
gefunden=0
|
||||
step "Rueckbau von $NAME"
|
||||
info "Anwendung : $APP${INSTANCE:+ (Instanz $INSTANCE)}"
|
||||
info "Verzeichnis : $ROOT"
|
||||
|
||||
echo
|
||||
echo " Dienste:"
|
||||
for unit in "${UNITS[@]}"; do
|
||||
if [[ -f "$UNIT_DIR/$unit" ]]; then info "- $unit"; gefunden=1; fi
|
||||
done
|
||||
[[ $gefunden -eq 1 ]] || info "(keine)"
|
||||
|
||||
echo
|
||||
echo " Dateien und Verzeichnisse:"
|
||||
for pfad in "${DATEIEN[@]}" "${VERZEICHNISSE[@]}"; do
|
||||
if [[ -e "$pfad" ]]; then info "- $pfad"; gefunden=1; fi
|
||||
done
|
||||
if [[ -d "$ROOT" ]]; then info "- $ROOT"; gefunden=1; fi
|
||||
|
||||
if [[ $gefunden -eq 0 ]]; then
|
||||
echo
|
||||
info "Von '$NAME' ist nichts mehr da -- nichts zu tun."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo
|
||||
echo " Daten (Datenbank, Schluessel, Lizenz):"
|
||||
if [[ -d "$DATEN_DIR" ]]; then
|
||||
if [[ $PURGE -eq 1 ]]; then
|
||||
warn "$DATEN_DIR wird GELOESCHT (--purge)"
|
||||
else
|
||||
info "$DATEN_DIR wird vorher gesichert"
|
||||
fi
|
||||
else
|
||||
info "(kein Datenverzeichnis gefunden)"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo " Bleibt stehen:"
|
||||
if weitere_dieser_anwendung; then
|
||||
info "sudoers und Dienstbenutzer '$APP' -- eine weitere Installation braucht sie"
|
||||
else
|
||||
info "- (sudoers und Dienstbenutzer '$APP' werden entfernt)"
|
||||
fi
|
||||
if weitere_ueberhaupt; then
|
||||
info "Helfer unter $LIB_DIR -- andere Installationen brauchen ihn"
|
||||
else
|
||||
info "- (Helfer unter $LIB_DIR wird entfernt)"
|
||||
fi
|
||||
|
||||
if [[ $DRY_RUN -eq 1 ]]; then
|
||||
echo
|
||||
info "Probelauf -- es wurde nichts geaendert."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ $ASSUME_YES -eq 0 ]]; then
|
||||
echo
|
||||
read -r -p "Fortfahren? [j/N] " antwort
|
||||
[[ "$antwort" =~ ^[jJyY]$ ]] || fail "abgebrochen"
|
||||
fi
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Ausfuehren
|
||||
# --------------------------------------------------------------------------- #
|
||||
step "Daten"
|
||||
if [[ -d "$DATEN_DIR" && $PURGE -eq 0 ]]; then
|
||||
ARCHIV="$SRV_DIR/$NAME-daten-$(date +%Y%m%d-%H%M%S).tar.gz"
|
||||
tar -czf "$ARCHIV" -C "$(dirname "$DATEN_DIR")" "$(basename "$DATEN_DIR")"
|
||||
chmod 600 "$ARCHIV"
|
||||
info "gesichert: $ARCHIV"
|
||||
elif [[ -d "$DATEN_DIR" ]]; then
|
||||
info "wird mit dem Verzeichnis geloescht (--purge)"
|
||||
else
|
||||
info "nichts zu sichern"
|
||||
fi
|
||||
|
||||
step "Dienste"
|
||||
for unit in "${UNITS[@]}"; do
|
||||
[[ -f "$UNIT_DIR/$unit" ]] || continue
|
||||
systemctl disable --now "$unit" >/dev/null 2>&1 || true
|
||||
rm -f "$UNIT_DIR/$unit"
|
||||
# Ohne das bleibt eine entfernte Unit als "not-found" im Fehlerzustand
|
||||
# stehen und faerbt "systemctl --failed" rot.
|
||||
systemctl reset-failed "$unit" >/dev/null 2>&1 || true
|
||||
info "entfernt: $unit"
|
||||
done
|
||||
systemctl daemon-reload >/dev/null 2>&1 || true
|
||||
|
||||
step "Webserver"
|
||||
if [[ -e "$NGINX_DIR/sites-enabled/$NAME" || -e "$NGINX_DIR/sites-available/$NAME" ]]; then
|
||||
rm -f "$NGINX_DIR/sites-enabled/$NAME" "$NGINX_DIR/sites-available/$NAME"
|
||||
# Drei Faelle, und sie bedeuten Verschiedenes: kein nginx da (dann gibt es
|
||||
# nichts neu zu laden), nginx da und zufrieden, nginx da und unzufrieden.
|
||||
# Sie in einen Zweig zu werfen hiesse, auf einer Maschine ohne nginx eine
|
||||
# Warnung ueber eine kaputte Konfiguration auszugeben, die es nicht gibt.
|
||||
if ! command -v nginx >/dev/null 2>&1; then
|
||||
info "Site entfernt (nginx ist hier nicht installiert)"
|
||||
elif nginx -t >/dev/null 2>&1; then
|
||||
systemctl reload nginx >/dev/null 2>&1 || true
|
||||
info "Site entfernt, nginx neu geladen"
|
||||
else
|
||||
warn "Site entfernt -- nginx beanstandet die Konfiguration, bitte 'nginx -t' ansehen"
|
||||
fi
|
||||
else
|
||||
info "keine Site vorhanden"
|
||||
fi
|
||||
|
||||
step "Dateien"
|
||||
for pfad in "${DATEIEN[@]}"; do
|
||||
[[ -e "$pfad" ]] && { rm -f "$pfad"; info "entfernt: $pfad"; }
|
||||
done
|
||||
for pfad in "${VERZEICHNISSE[@]}"; do
|
||||
[[ -d "$pfad" ]] && { rm -rf "$pfad"; info "entfernt: $pfad"; }
|
||||
done
|
||||
if [[ -d "$ROOT" ]]; then
|
||||
rm -rf "$ROOT"
|
||||
info "entfernt: $ROOT"
|
||||
fi
|
||||
if [[ $PURGE -eq 1 ]]; then
|
||||
# Die Rueckwege aelterer Aktualisierungen. Sie enthalten dieselben Daten wie
|
||||
# die Installation -- deshalb nur hier, nie im Normalfall.
|
||||
for rest in "$SRV_DIR/$NAME-backup-"* "$SRV_DIR/$NAME-failed-"*; do
|
||||
[[ -d "$rest" ]] && { rm -rf "$rest"; info "entfernt: $rest"; }
|
||||
done
|
||||
fi
|
||||
|
||||
step "Geteiltes"
|
||||
if weitere_dieser_anwendung; then
|
||||
info "sudoers und Dienstbenutzer '$APP' bleiben -- eine weitere Installation braucht sie"
|
||||
else
|
||||
rm -f "$SUDOERS_DIR/$APP"
|
||||
info "entfernt: $SUDOERS_DIR/$APP"
|
||||
if getent passwd "$APP" >/dev/null 2>&1; then
|
||||
# Ein laufender Prozess haelt den Benutzer fest. Das ist kein Grund,
|
||||
# den Rueckbau scheitern zu lassen -- der Dienst ist oben schon gestoppt,
|
||||
# und was sonst noch unter diesem Namen laeuft, gehoert angesehen.
|
||||
if userdel "$APP" >/dev/null 2>&1; then
|
||||
info "entfernt: Dienstbenutzer $APP"
|
||||
else
|
||||
warn "Dienstbenutzer $APP liess sich nicht entfernen -- laeuft noch etwas unter ihm?"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if weitere_ueberhaupt; then
|
||||
info "Helfer unter $LIB_DIR bleibt -- andere Installationen brauchen ihn"
|
||||
else
|
||||
rm -rf "$LIB_DIR"
|
||||
rmdir "$ETC_TESM/certs" "$ETC_TESM" >/dev/null 2>&1 || true
|
||||
info "entfernt: $LIB_DIR"
|
||||
fi
|
||||
|
||||
step "Fertig"
|
||||
info "'$NAME' ist zurueckgebaut."
|
||||
if [[ $PURGE -eq 0 && -n "${ARCHIV:-}" ]]; then
|
||||
info "Die Daten liegen unter $ARCHIV -- dort und sonst nirgends."
|
||||
fi
|
||||
@@ -383,6 +383,87 @@ sudo bash /srv/tesm/deploy/update.sh tesm
|
||||
|
||||
---
|
||||
|
||||
## 4bb. Release-Upgrade des Betriebssystems
|
||||
|
||||
Ein `do-release-upgrade` tauscht die Minor-Fassung von Python aus (24.04 bringt
|
||||
3.12, 26.04 bringt 3.14). Der venv einer Installation hängt daran: seine Pakete
|
||||
liegen unter `lib/python3.12`, während `venv/bin/python3` auf `/usr/bin/python3`
|
||||
zeigt — also nach dem Upgrade auf den **neuen** Interpreter. Er ist ausführbar
|
||||
und trotzdem tot; jeder Dienst scheitert mit `No module named gunicorn`, nginx
|
||||
meldet 502.
|
||||
|
||||
**Das repariert TESM seit 2.8.33 von selbst.** Vor jedem Dienststart läuft
|
||||
|
||||
```
|
||||
ExecStartPre=+/usr/local/lib/tesm/tesm-venv /srv/tesm tesm
|
||||
```
|
||||
|
||||
Ist die Umgebung heil, kostet das Millisekunden. Sonst wird sie neu gebaut und
|
||||
mit denselben Paketen bestückt, die auch die Installation einspielt — es ist
|
||||
dasselbe Programm, `install.sh` ruft es mit `--immer` auf. Nach dem Neustart,
|
||||
der auf jedes Release-Upgrade folgt, kommt der Dienst also von allein zurück;
|
||||
im Live-Protokoll steht dann eine Zeile als Hinweis.
|
||||
|
||||
Von Hand prüfen oder anstoßen:
|
||||
|
||||
```bash
|
||||
/usr/local/lib/tesm/tesm-venv /srv/tesm tesm --pruefen # 0 heil, 1 kaputt
|
||||
/usr/local/lib/tesm/tesm-venv /srv/tesm tesm # reparieren
|
||||
```
|
||||
|
||||
**Grenzen.** Der Neuaufbau lädt Pakete und braucht Zugriff auf das
|
||||
Paketverzeichnis. Ohne Netz kann er nicht gelingen: dann wird **ein** Versuch je
|
||||
Systemstart unternommen (Merker unter `/run`), und es bleibt bei einer klaren
|
||||
Zeile im Journal und im Live-Protokoll. Ohne diese Bremse startete
|
||||
`Restart=always` alle fünf Sekunden einen neuen `pip`-Lauf.
|
||||
|
||||
Die alten Pakete unter `lib/python3.<alt>` werden nicht übernommen —
|
||||
`cryptography`, `argon2-cffi` und `bcrypt` sind kompiliert und für die alte
|
||||
Fassung gebaut.
|
||||
|
||||
Empfohlene Reihenfolge bleibt trotzdem: erst TESM aktualisieren, dann
|
||||
`do-release-upgrade`, dann prüfen.
|
||||
|
||||
---
|
||||
|
||||
## 4c. Installation entfernen
|
||||
|
||||
Das Gegenstück zu `install.sh`:
|
||||
|
||||
```bash
|
||||
sudo bash /srv/tesm/deploy/uninstall.sh --app tesm --dry-run
|
||||
sudo bash /srv/tesm/deploy/uninstall.sh --app tesm
|
||||
sudo bash /srv/tesm/deploy/uninstall.sh --app tesm-license --instance test --yes
|
||||
```
|
||||
|
||||
Entfernt werden Dienste (`.service`, `-monitor`, `-cleanup` samt Timer), die
|
||||
nginx-Site, der Begrüßungshaken, der Shim unter `/usr/local/bin`, die
|
||||
Umgebungsdatei, das Zertifikatsverzeichnis, das Protokollverzeichnis und die
|
||||
Installation selbst.
|
||||
|
||||
**Die Daten bleiben.** Datenbank, Schlüssel und Lizenz gehen vorher in ein
|
||||
Archiv (`/srv/<name>-daten-<zeit>.tar.gz`, nur root). Sie sind das einzige an
|
||||
einer Installation, was sich nicht wiederherstellen lässt. Wer sie wirklich
|
||||
los sein will, nimmt `--purge` — das löscht zusätzlich die Rückwege älterer
|
||||
Aktualisierungen (`/srv/<name>-backup-*`).
|
||||
|
||||
**Geteiltes bleibt, solange es jemand braucht.** Die sudoers-Datei und der
|
||||
Dienstbenutzer tragen den Namen der *Anwendung*, nicht den der Instanz; der
|
||||
Helfer unter `/usr/local/lib/tesm` gehört allen Installationen beider
|
||||
Anwendungen. Sie verschwinden erst mit der letzten. Sonst legte der Rückbau
|
||||
einer Testinstanz die Produktion daneben lahm.
|
||||
|
||||
`--dry-run` zeigt den Plan, ohne etwas zu ändern. Ein zweiter Lauf auf eine
|
||||
bereits entfernte Installation sagt das und endet mit 0.
|
||||
|
||||
Warum es das gibt: wer eine Installation von Hand löscht (`rm -rf /srv/…`),
|
||||
lässt Unit, Begrüßungshaken, Shim, sudoers-Datei und nginx-Site zurück.
|
||||
systemd versucht die Unit danach in einer Dauerschleife zu starten, und der
|
||||
Haken druckt bei jeder Anmeldung seinen Banner. Auf dem Testserver standen so
|
||||
neun tote Dienste und vierzehn Banner.
|
||||
|
||||
---
|
||||
|
||||
## 5. Dienste
|
||||
|
||||
```bash
|
||||
|
||||
Reference in new issue
Block a user