diff --git a/install.sh b/install.sh index 3b38e67..6fe8dcc 100644 --- a/install.sh +++ b/install.sh @@ -1,15 +1,37 @@ #!/bin/bash # ============================================================================ -# TESM — Installer +# TESM — Installer/Updater # Richtet System-Pakete, die Flask-App (systemd) sowie den Erreichbarkeits- # Check-Dienst (systemd + Timer) ein. Auszuführen als root, nachdem dieses -# Repo z.B. nach /root/tesm geklont wurde. +# Repo/Release-Paket z.B. nach /root/tesm entpackt wurde. +# +# Erkennt selbstständig, ob unter /srv/tesm bereits eine Installation +# existiert (an sqlite.db), und verhält sich entsprechend: +# +# - Keine vorhandene Installation -> normale Frischinstallation. +# - Vorhanden, SCHEMA_VERSION unverändert -> einfaches In-Place-Update +# (Code/Templates/Abhängigkeiten aktualisieren; sqlite.db bleibt +# unangetastet -- rsync schließt sie ohnehin aus). Kein Backup nötig, +# da sich am Datenbank-Schema nichts ändert. +# - Vorhanden, SCHEMA_VERSION unbekannt oder abweichend -> Update MIT +# Sicherheitsnetz: die komplette bisherige Installation (Code, venv, +# Datenbank, Schlüssel) wird vorher 1:1 nach /srv/tesm-backup-pre- +# migration- kopiert. Die eigentliche Schema-Migration +# übernimmt danach die App selbst beim Start (siehe _ensure_schema() +# in app.py). Kommt der Dienst anschließend nachweislich NICHT gesund +# hoch (HTTP-Check auf /login), wird automatisch die komplette +# Sicherung zurückgespielt (alter Code + alte Datenbank zusammen -- +# ein reines DB-Rollback allein würde denselben Migrationsfehler beim +# nächsten Start mit dem neuen Code sofort wiederholen) und das +# Backup bleibt zur manuellen Prüfung erhalten. Kommt er gesund hoch, +# gilt die Migration als erfolgreich und das Backup wird gelöscht. # ============================================================================ set -e REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RED='\033[0;31m' GREEN='\033[0;32m' +YELLOW='\033[1;33m' NC='\033[0m' print_status() { echo -e "${GREEN}✔${NC} ${1} completed."; } @@ -23,6 +45,41 @@ step() { echo -e "${RED}→${NC} ${1}..." | tee -a /var/log/tesm-install # nie fatal. clear 2>/dev/null || true +# ---- Bestehende Installation erkennen ---- +EXISTING_INSTALL=0 +[ -f /srv/tesm/sqlite.db ] && EXISTING_INSTALL=1 + +NEEDS_MIGRATION_GUARD=0 +BACKUP_DIR="" + +if [ "$EXISTING_INSTALL" -eq 1 ]; then + OLD_SCHEMA_VERSION="" + [ -f /srv/tesm/SCHEMA_VERSION ] && OLD_SCHEMA_VERSION="$(tr -d '[:space:]' < /srv/tesm/SCHEMA_VERSION)" + NEW_SCHEMA_VERSION="" + [ -f "$REPO_DIR/srv/tesm/SCHEMA_VERSION" ] && NEW_SCHEMA_VERSION="$(tr -d '[:space:]' < "$REPO_DIR/srv/tesm/SCHEMA_VERSION")" + + step "Stopping tesm.service for update" + systemctl stop tesm.service tesm-check.service tesm-check-restart.timer 2>/dev/null || true + print_status "Service stopped" + + if [ -n "$OLD_SCHEMA_VERSION" ] && [ -n "$NEW_SCHEMA_VERSION" ] && [ "$OLD_SCHEMA_VERSION" == "$NEW_SCHEMA_VERSION" ]; then + echo -e "${GREEN}Bestehende Installation erkannt, Datenbank-Schema unverändert (Version ${OLD_SCHEMA_VERSION}):${NC} einfaches In-Place-Update, kein Backup nötig." + else + if [ -n "$OLD_SCHEMA_VERSION" ] && [ -n "$NEW_SCHEMA_VERSION" ]; then + reason="Datenbank-Schema hat sich geändert (${OLD_SCHEMA_VERSION} -> ${NEW_SCHEMA_VERSION})" + else + reason="Schema-Version der bestehenden Installation oder dieses Pakets unbekannt" + fi + echo -e "${YELLOW}Bestehende Installation erkannt, ${reason}:${NC}" + echo "Update mit automatischer Migration und Sicherheitsnetz (Rückroll-Backup)." + NEEDS_MIGRATION_GUARD=1 + BACKUP_DIR="/srv/tesm-backup-pre-migration-$(date +%Y%m%d-%H%M%S)" + step "Backing up current installation to $BACKUP_DIR before migration" + cp -a /srv/tesm "$BACKUP_DIR" + print_status "Backup created" + fi +fi + # ---- Pakete ---- step "Installing system packages" sudo apt-get update >>/var/log/tesm-install.log 2>&1 && print_status "apt update" @@ -107,4 +164,34 @@ sudo systemctl enable --now tesm.service sudo systemctl enable --now tesm-check.service tesm-check-restart.timer print_status "Services enabled and started" +# ---- Migrations-Sicherheitsnetz prüfen ---- +# Nur relevant, wenn oben tatsächlich eine riskante Migration (abweichende/ +# unbekannte SCHEMA_VERSION) erkannt wurde -- beim einfachen In-Place-Update +# oder einer Frischinstallation ist BACKUP_DIR leer und dieser Block wird +# komplett übersprungen. +if [ "$NEEDS_MIGRATION_GUARD" -eq 1 ]; then + step "Verifying migrated installation" + HEALTHY=0 + for i in $(seq 1 15); do + sleep 2 + if curl -fsS -o /dev/null http://127.0.0.1:5000/login 2>/dev/null; then + HEALTHY=1 + break + fi + done + if [ "$HEALTHY" -eq 1 ]; then + print_status "Migration verified healthy" + rm -rf "$BACKUP_DIR" + echo -e "${GREEN}✔${NC} Migration erfolgreich -- Backup wieder entfernt." + else + echo -e "${RED}✖ Migration fehlgeschlagen -- Dienst antwortet nicht gesund. Rolle zurück...${NC}" + sudo systemctl stop tesm.service 2>/dev/null || true + sudo rm -rf /srv/tesm + sudo mv "$BACKUP_DIR" /srv/tesm + sudo systemctl start tesm.service + echo -e "${YELLOW}Zurückgerollt auf den Stand vor dem Update (alter Code + alte Datenbank zusammen).${NC}" + echo "Bitte /var/log/tesm/app.log prüfen oder Export/Import nutzen, bevor erneut aktualisiert wird." + fi +fi + echo -e "${GREEN}✔${NC} Installation abgeschlossen. Web-App erreichbar auf Port 80 (nginx) bzw. 5000 (Flask direkt)." diff --git a/srv/tesm/VERSION b/srv/tesm/VERSION index 6d7de6e..21e8796 100644 --- a/srv/tesm/VERSION +++ b/srv/tesm/VERSION @@ -1 +1 @@ -1.0.2 +1.0.3 diff --git a/update.sh b/update.sh index c8f881a..fde8ecd 100644 --- a/update.sh +++ b/update.sh @@ -1,39 +1,26 @@ #!/bin/bash # ============================================================================ -# TESM — Update/Reinstall -# Ersetzt eine bestehende Installation -- ganz gleich welche der drei -# historischen Stufen dieses Hosts gerade aktiv ist: +# TESM — Update/Reinstall (Bootstrapper) +# Lädt ein gepacktes Gitea-Release per HTTP herunter (kein "git clone" +# nötig) und übergibt die eigentliche Installations-/Update-Logik an +# install.sh aus dem Paket -- das erkennt selbstständig, ob unter +# /srv/tesm schon eine Installation existiert, und aktualisiert sie +# entsprechend (In-Place bei unverändertem Datenbank-Schema, sonst mit +# automatischem Backup + Migrations-Sicherheitsnetz samt Rückroll- +# Möglichkeit -- siehe install.sh für Details). +# +# Dieses Skript selbst ist nur noch für das Ablösen ÄLTERER, TESM +# vorausgehender Installationsstufen auf diesem Host zuständig: # 1. Das ursprüngliche, reine Bash-Setup aus dem separaten Repo # https://gitea.int.eertmoed.net/WiS/Aruba-PoE (rpi-check.service + # statische ips.list, keine Datenbank/Web-UI). # 2. Die spätere Flask-App "PoE Manager" unter /srv/poe_manager # (poe_web.service, Vorgänger von TESM in diesem Repo vor der # Umbenennung). -# 3. Eine bereits während der Entwicklung ad-hoc gepflegte TESM-Instanz -# unter /srv/tesm. -# -- durch eine frische Installation aus einem gepackten Gitea-Release. -# Lädt dafür NUR den Release-Tarball per HTTP herunter (kein "git clone", -# kein Git auf dem Zielsystem nötig). +# Beide werden NICHT gelöscht, sondern nur gestoppt/deaktiviert und +# (falls vorhanden) beiseite verschoben. # -# /srv/poe_manager (Stufe 2, komplett anderes Schema/App) wird IMMER nur -# gestoppt/deaktiviert und beiseite verschoben, nie in-place übernommen. -# -# /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): +# Auszuführen als root auf dem Zielsystem: # sudo ./update.sh # Optional übersteuerbar (z.B. für eine bestimmte Version statt "latest"): # TESM_RELEASE_TAG=v1.0.0 sudo -E ./update.sh @@ -69,6 +56,21 @@ echo -e "${YELLOW}============================================================== echo -e "${YELLOW} TESM Update/Reinstall — Release \"${RELEASE_TAG}\"${NC}" echo -e "${YELLOW}============================================================================${NC}" echo +echo "Lädt das Release-Paket herunter und übergibt Installation/Update an" +echo "install.sh daraus -- das erkennt selbst, ob es sich um eine frische" +echo "Installation oder ein Update einer bestehenden handelt, und sichert" +echo "die Datenbank bei einer Schema-Änderung automatisch vorsorglich ab." +echo +echo "Ältere, TESM vorausgehende Installationsstufen (PoE Manager unter" +echo "/srv/poe_manager, das ursprüngliche reine Bash-Setup) werden dabei" +echo "erkannt, gestoppt/deaktiviert und beiseite verschoben (nicht gelöscht)." +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} Lade Paket von ${DOWNLOAD_URL}..." mkdir -p "$WORK_DIR" @@ -84,127 +86,28 @@ 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 +echo -e "${RED}→${NC} Stoppe/deaktiviere ältere Installationsstufen (falls vorhanden)..." +# "|| true" macht nichts kaputt, falls einzelne Units gar nicht vorhanden +# sind. tesm.service selbst wird NICHT hier, sondern von install.sh +# gestoppt -- das entscheidet dort auch gleich, ob ein Backup nötig ist. 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 "${GREEN}✔${NC} Ältere Installationsstufen bereinigt." echo -e "${RED}→${NC} Starte install.sh aus dem Paket..." bash "$WORK_DIR/pkg/install.sh" echo echo -e "${GREEN}============================================================================${NC}" -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 -e "${GREEN} TESM Update/Reinstall (Release \"${RELEASE_TAG}\") abgeschlossen.${NC}" +echo -e "${GREEN}============================================================================${NC}" +echo "Siehe Ausgabe von install.sh oben für Details (In-Place-Update, frische" +echo "Installation, oder Migrationsergebnis inkl. eventuellem Rückroll-Hinweis)."