diff --git a/srv/tesm/SCHEMA_VERSION b/srv/tesm/SCHEMA_VERSION new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/srv/tesm/SCHEMA_VERSION @@ -0,0 +1 @@ +1 diff --git a/srv/tesm/create_db.py b/srv/tesm/create_db.py index 9d870cb..809a433 100644 --- a/srv/tesm/create_db.py +++ b/srv/tesm/create_db.py @@ -1,5 +1,16 @@ #!/usr/bin/env python3 -"""Initialisiert die SQLite-Datenbank für TESM.""" +"""Initialisiert die SQLite-Datenbank für TESM. + +WICHTIG: Wer hier eine neue Tabelle oder Spalte hinzufügt, muss auch die +Datei SCHEMA_VERSION (daneben, im selben Verzeichnis) um 1 erhöhen. +update.sh vergleicht diese Zahl zwischen der aktuell installierten und der +neuen Version, um zu entscheiden, ob ein Update in-place laufen darf +(bestehende Datenbank bleibt erhalten) oder eine frische Installation +nötig ist (Datenbank wird beiseitegesichert). Ein vergessenes Hochzählen +würde dazu führen, dass ein Host mit altem Schema faelschlich per +In-Place-Update aktualisiert wird, statt frisch installiert zu werden -- +_ensure_schema() faengt die meisten Faelle zwar zur Laufzeit ab, ist aber +kein Ersatz fuer eine korrekte Versionsnummer hier.""" import os import sqlite3 diff --git a/update.sh b/update.sh index e349879..c8f881a 100644 --- a/update.sh +++ b/update.sh @@ -15,16 +15,23 @@ # Lädt dafür NUR den Release-Tarball per HTTP herunter (kein "git clone", # kein Git auf dem Zielsystem nötig). # -# Alle ALTEN Installationen werden dabei NICHT gelöscht, sondern nur -# gestoppt/deaktiviert und (falls vorhanden) beiseite verschoben (siehe -# OLD_DIRS/BACKED_UP_DIRS) -- als Rückfalloption UND als Datenquelle für -# einen späteren Export/Import. +# /srv/poe_manager (Stufe 2, komplett anderes Schema/App) wird IMMER nur +# gestoppt/deaktiviert und beiseite verschoben, nie in-place übernommen. # -# WICHTIG: Das Ergebnis ist eine FRISCHE Installation OHNE die Daten der -# alten Instanz (Geräte, Switche, Zugangsdaten, Benutzer, Einstellungen, …). -# Vor dem Ausführen unbedingt unter Einstellungen -> Im-/Export ein Backup -# der alten Instanz exportieren -- danach entweder alles neu einrichten oder -# dieses Backup in die neue Installation importieren. +# /srv/tesm (Stufe 3) dagegen wird per SCHEMA_VERSION-Datei verglichen: +# stimmt die Versionsnummer der bereits installierten mit der des neuen +# Release-Pakets überein, hat sich am Datenbank-Schema nichts geändert -- +# dann läuft install.sh IN-PLACE gegen das bestehende /srv/tesm (dessen +# rsync schließt sqlite.db/fernet.key/secret.key ohnehin aus), die +# komplette Datenbank (Geräte, Switche, Zugangsdaten, Benutzer, +# Einstellungen, …) bleibt dabei erhalten. Nur bei einer TATSÄCHLICHEN +# Schema-Änderung (oder wenn die alte Installation noch keine +# SCHEMA_VERSION-Datei kennt, also aus der Zeit vor dieser Prüfung stammt) +# wird wie bisher beiseite verschoben und frisch installiert. +# +# Lädt/entpackt das Release-Paket deshalb VOR der Bestätigungsabfrage -- +# nur so lässt sich vorher schon ansagen, ob es sich um ein +# datenerhaltendes Update oder eine frische Installation handelt. # # Auszuführen als root auf dem Zielsystem (z.B. POETEST): # sudo ./update.sh @@ -45,10 +52,6 @@ PACKAGE_NAME="tesm-${RELEASE_TAG}.tar.gz" DOWNLOAD_URL="${GITEA_BASE}/${REPO_OWNER}/${REPO_NAME}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}" TIMESTAMP="$(date +%Y%m%d-%H%M%S)" -# Alle historischen Installationsorte, die dieser Host je nach Alter tragen -# kann -- siehe Kommentar oben. Jeder, der existiert, wird beiseite -# verschoben statt gelöscht. -OLD_DIRS=("/srv/tesm" "/srv/poe_manager") WORK_DIR="/tmp/tesm-update-$(date +%s)" PACKAGE_FILE="$WORK_DIR/${PACKAGE_NAME}" @@ -66,52 +69,6 @@ echo -e "${YELLOW}============================================================== echo -e "${YELLOW} TESM Update/Reinstall — Release \"${RELEASE_TAG}\"${NC}" echo -e "${YELLOW}============================================================================${NC}" echo -echo -e "${RED}ACHTUNG:${NC} Dies richtet TESM KOMPLETT NEU ein — frische Datenbank, KEINE" -echo "Geräte/Switche/Zugangsdaten/Benutzer/Einstellungen aus der aktuellen Installation." -echo -echo "Alle auf diesem Host ggf. vorhandenen älteren Installationen (/srv/tesm," -echo "/srv/poe_manager) werden dabei NICHT gelöscht, sondern nach dem Stoppen der" -echo "jeweiligen Dienste nach -backup-${TIMESTAMP} verschoben (bleiben" -echo "vollständig erhalten, inkl. sqlite.db)." -echo -echo "Ebenfalls gestoppt/deaktiviert werden ältere Dienste, falls noch vorhanden:" -echo " - poe_web.service (Flask-App \"PoE Manager\" unter /srv/poe_manager)" -echo " - rpi-check.service / rpi-check-restart.* (das ursprüngliche reine" -echo " Bash-Setup aus dem Repo WiS/Aruba-PoE, ganz ohne Datenbank/Web-UI)" -echo -echo -e "${YELLOW}Falls noch nicht geschehen: JETZT abbrechen und zuerst unter${NC}" -echo -e "${YELLOW}Einstellungen -> Im-/Export ein Backup der aktuellen Daten exportieren!${NC}" -echo "Nach dem Update entweder dieses Backup importieren, oder alles manuell neu anlegen." -echo - -read -r -p "Fortfahren? [y/N] " confirm -if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then - echo "Abgebrochen — nichts wurde verändert." - exit 0 -fi - -echo -e "${RED}→${NC} Stoppe und deaktiviere bestehende Dienste..." -# Alle drei historischen Generationen -- je nachdem, was auf diesem Host -# tatsächlich (noch) existiert. "|| true" macht nichts kaputt, falls -# einzelne Units gar nicht vorhanden sind. -systemctl stop tesm.service tesm-check.service tesm-check-restart.timer 2>/dev/null || true -systemctl disable tesm.service tesm-check.service tesm-check-restart.timer 2>/dev/null || true -systemctl stop poe_web.service 2>/dev/null || true -systemctl disable poe_web.service 2>/dev/null || true -systemctl stop rpi-check.service rpi-check-restart.service rpi-check-restart.timer 2>/dev/null || true -systemctl disable rpi-check.service rpi-check-restart.service rpi-check-restart.timer 2>/dev/null || true -echo -e "${GREEN}✔${NC} Dienste gestoppt und deaktiviert." - -BACKED_UP_DIRS=() -for dir in "${OLD_DIRS[@]}"; do - if [ -d "$dir" ]; then - dest="${dir}-backup-${TIMESTAMP}" - echo -e "${RED}→${NC} Sichere ${dir} nach ${dest}..." - mv "$dir" "$dest" - BACKED_UP_DIRS+=("$dest") - echo -e "${GREEN}✔${NC} Gesichert (nicht gelöscht)." - fi -done echo -e "${RED}→${NC} Lade Paket von ${DOWNLOAD_URL}..." mkdir -p "$WORK_DIR" @@ -127,26 +84,127 @@ mkdir -p "$WORK_DIR/pkg" tar xzf "$PACKAGE_FILE" -C "$WORK_DIR/pkg" --strip-components=1 echo -e "${GREEN}✔${NC} Paket entpackt." +# Schema-Vergleich: nur wenn BEIDE Seiten eine SCHEMA_VERSION kennen UND +# sie übereinstimmen UND tatsächlich schon eine Datenbank existiert, darf +# in-place aktualisiert werden. Fehlt die Datei auf einer der beiden +# Seiten (ältere Installation/älteres Paket von vor dieser Prüfung), wird +# im Zweifel IMMER der sichere, datenerhaltende-per-Backup Weg gewählt. +CURRENT_SCHEMA_VERSION="" +[ -f /srv/tesm/SCHEMA_VERSION ] && CURRENT_SCHEMA_VERSION="$(tr -d '[:space:]' < /srv/tesm/SCHEMA_VERSION)" +NEW_SCHEMA_VERSION="" +[ -f "$WORK_DIR/pkg/srv/tesm/SCHEMA_VERSION" ] && NEW_SCHEMA_VERSION="$(tr -d '[:space:]' < "$WORK_DIR/pkg/srv/tesm/SCHEMA_VERSION")" + +IN_PLACE_UPDATE=0 +if [ -f /srv/tesm/sqlite.db ] && [ -n "$CURRENT_SCHEMA_VERSION" ] && [ -n "$NEW_SCHEMA_VERSION" ] \ + && [ "$CURRENT_SCHEMA_VERSION" == "$NEW_SCHEMA_VERSION" ]; then + IN_PLACE_UPDATE=1 +fi + +if [ "$IN_PLACE_UPDATE" -eq 1 ]; then + echo -e "${GREEN}Datenbank-Schema unverändert (Version ${CURRENT_SCHEMA_VERSION}):${NC} dies wird ein" + echo "IN-PLACE-Update. /srv/tesm bleibt bestehen -- Geräte/Switche/Zugangsdaten/" + echo "Benutzer/Einstellungen (sqlite.db) sowie Host-Keys und Verschlüsselungs-" + echo "Schlüssel (fernet.key/secret.key) bleiben unangetastet. Es werden nur" + echo "Anwendungscode, Templates und Abhängigkeiten aktualisiert." +else + if [ -n "$CURRENT_SCHEMA_VERSION" ] && [ -n "$NEW_SCHEMA_VERSION" ]; then + reason="Datenbank-Schema hat sich geändert (${CURRENT_SCHEMA_VERSION} → ${NEW_SCHEMA_VERSION})" + elif [ -f /srv/tesm/sqlite.db ]; then + reason="Schema-Version der bestehenden Installation oder des neuen Pakets unbekannt (Stand von vor dieser Prüfung)" + else + reason="keine bestehende Installation unter /srv/tesm gefunden" + fi + echo -e "${RED}ACHTUNG:${NC} Dies richtet TESM KOMPLETT NEU ein — frische Datenbank, KEINE" + echo "Geräte/Switche/Zugangsdaten/Benutzer/Einstellungen aus der aktuellen Installation." + echo "Grund: ${reason}." + echo + echo "Alle auf diesem Host ggf. vorhandenen älteren Installationen (/srv/tesm," + echo "/srv/poe_manager) werden dabei NICHT gelöscht, sondern nach dem Stoppen der" + echo "jeweiligen Dienste nach -backup-${TIMESTAMP} verschoben (bleiben" + echo "vollständig erhalten, inkl. sqlite.db)." + echo + echo -e "${YELLOW}Falls noch nicht geschehen: JETZT abbrechen und zuerst unter${NC}" + echo -e "${YELLOW}Einstellungen -> Im-/Export ein Backup der aktuellen Daten exportieren!${NC}" + echo "Nach dem Update entweder dieses Backup importieren, oder alles manuell neu anlegen." +fi +echo +echo "In jedem Fall gestoppt/deaktiviert werden ältere Dienste, falls noch vorhanden:" +echo " - poe_web.service (Flask-App \"PoE Manager\" unter /srv/poe_manager)" +echo " - rpi-check.service / rpi-check-restart.* (das ursprüngliche reine" +echo " Bash-Setup aus dem Repo WiS/Aruba-PoE, ganz ohne Datenbank/Web-UI)" +echo + +read -r -p "Fortfahren? [y/N] " confirm +if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + echo "Abgebrochen — nichts wurde verändert." + exit 0 +fi + +echo -e "${RED}→${NC} Stoppe und deaktiviere bestehende Dienste..." +# Alle drei historischen Generationen -- je nachdem, was auf diesem Host +# tatsächlich (noch) existiert. "|| true" macht nichts kaputt, falls +# einzelne Units gar nicht vorhanden sind. tesm.service wird auch beim +# In-Place-Update gestoppt (sauberer Neustart mit neuem Code danach über +# install.sh "Enabling services"), NUR /srv/tesm selbst bleibt erhalten. +systemctl stop tesm.service tesm-check.service tesm-check-restart.timer 2>/dev/null || true +systemctl disable tesm.service tesm-check.service tesm-check-restart.timer 2>/dev/null || true +systemctl stop poe_web.service 2>/dev/null || true +systemctl disable poe_web.service 2>/dev/null || true +systemctl stop rpi-check.service rpi-check-restart.service rpi-check-restart.timer 2>/dev/null || true +systemctl disable rpi-check.service rpi-check-restart.service rpi-check-restart.timer 2>/dev/null || true +echo -e "${GREEN}✔${NC} Dienste gestoppt und deaktiviert." + +BACKED_UP_DIRS=() + +# /srv/poe_manager ist eine komplett andere App/Schema -- die wird IMMER +# beiseite verschoben, nie in-place übernommen. +if [ -d /srv/poe_manager ]; then + dest="/srv/poe_manager-backup-${TIMESTAMP}" + echo -e "${RED}→${NC} Sichere /srv/poe_manager nach ${dest}..." + mv /srv/poe_manager "$dest" + BACKED_UP_DIRS+=("$dest") + echo -e "${GREEN}✔${NC} Gesichert (nicht gelöscht)." +fi + +# /srv/tesm nur beiseite verschieben, wenn KEIN In-Place-Update möglich ist +# (siehe Schema-Vergleich oben) -- sonst bleibt es unangetastet stehen, +# damit install.sh gleich dagegen rsynct und die Datenbank erhalten bleibt. +if [ "$IN_PLACE_UPDATE" -eq 0 ] && [ -d /srv/tesm ]; then + dest="/srv/tesm-backup-${TIMESTAMP}" + echo -e "${RED}→${NC} Sichere /srv/tesm nach ${dest}..." + mv /srv/tesm "$dest" + BACKED_UP_DIRS+=("$dest") + echo -e "${GREEN}✔${NC} Gesichert (nicht gelöscht)." +fi + echo -e "${RED}→${NC} Starte install.sh aus dem Paket..." bash "$WORK_DIR/pkg/install.sh" echo echo -e "${GREEN}============================================================================${NC}" -echo -e "${GREEN} Frische TESM-Installation (Release \"${RELEASE_TAG}\") abgeschlossen.${NC}" -echo -e "${GREEN}============================================================================${NC}" -echo -echo -e "${YELLOW}WICHTIG — dies ist eine FRISCHE Installation ohne die Daten der alten Instanz.${NC}" -echo "Bitte jetzt eines von beidem tun:" -echo " a) Alle Geräte/Switche/Zugangsdaten/Benutzer/Einstellungen manuell neu anlegen, oder" -echo " b) Ein vor dem Update exportiertes Backup unter Einstellungen -> Im-/Export importieren." -echo -if [ "${#BACKED_UP_DIRS[@]}" -gt 0 ]; then - echo "Falls kein Export gemacht wurde: die alte(n) Datenbank(en) liegen unverändert unter:" - for dest in "${BACKED_UP_DIRS[@]}"; do - echo " ${dest}/sqlite.db" - done - echo "und können bei Bedarf manuell ausgelesen werden (z.B. mit sqlite3)." +if [ "$IN_PLACE_UPDATE" -eq 1 ]; then + echo -e "${GREEN} TESM-Update (Release \"${RELEASE_TAG}\") abgeschlossen -- Datenbank erhalten.${NC}" + echo -e "${GREEN}============================================================================${NC}" echo + echo "Alle Geräte/Switche/Zugangsdaten/Benutzer/Einstellungen sind unverändert" + echo "vorhanden -- kein manuelles Neuanlegen oder Re-Import nötig." +else + echo -e "${GREEN} Frische TESM-Installation (Release \"${RELEASE_TAG}\") abgeschlossen.${NC}" + echo -e "${GREEN}============================================================================${NC}" + echo + echo -e "${YELLOW}WICHTIG — dies ist eine FRISCHE Installation ohne die Daten der alten Instanz.${NC}" + echo "Bitte jetzt eines von beidem tun:" + echo " a) Alle Geräte/Switche/Zugangsdaten/Benutzer/Einstellungen manuell neu anlegen, oder" + echo " b) Ein vor dem Update exportiertes Backup unter Einstellungen -> Im-/Export importieren." + echo + if [ "${#BACKED_UP_DIRS[@]}" -gt 0 ]; then + echo "Falls kein Export gemacht wurde: die alte(n) Datenbank(en) liegen unverändert unter:" + for dest in "${BACKED_UP_DIRS[@]}"; do + echo " ${dest}/sqlite.db" + done + echo "und können bei Bedarf manuell ausgelesen werden (z.B. mit sqlite3)." + echo + fi + echo "Hinweis zum Admin-Konto der neuen Installation: siehe Ausgabe von install.sh oben" + echo "(bei einer frischen Datenbank wird dort auf create_admin.py hingewiesen)." fi -echo "Hinweis zum Admin-Konto der neuen Installation: siehe Ausgabe von install.sh oben" -echo "(bei einer frischen Datenbank wird dort auf create_admin.py hingewiesen)."