Compare commits
6
Commits
c55f9d992d
..
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c3fbd8f23 | ||
|
|
d77327cee1 | ||
|
|
d72db26a86 | ||
|
|
0b9b92a228 | ||
|
|
2a092af508 | ||
|
|
9f4195b800 |
No files matched your search
@@ -0,0 +1,104 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Akzeptiert die SSH-Host-Keys aller aktiven Geraete (Clients) in einem
|
||||||
|
Rutsch -- macht technisch dasselbe wie "Verbindung testen" im Browser-
|
||||||
|
Terminal (Verbindungsaufbau -> Host-Key abfragen -> "yes" -> trennen),
|
||||||
|
nur automatisiert fuer alle Geraete statt eines einzelnen.
|
||||||
|
|
||||||
|
Nutzt bewusst dieselbe known_hosts-Datei/dasselbe Format wie die App
|
||||||
|
(SSH_KNOWN_HOSTS_PATH in app.py) -- Geraete, deren Key hier akzeptiert
|
||||||
|
wird, muessen danach in der Weboberflaeche NICHT mehr einzeln bestaetigt
|
||||||
|
werden (SSH-Verbindungstest und Wartungs-Aktionen teilen sich dieselbe
|
||||||
|
Datei).
|
||||||
|
|
||||||
|
Aufruf auf dem Server: sudo /srv/tesm/venv/bin/python3 accept_all_hostkeys.py
|
||||||
|
"""
|
||||||
|
import socket
|
||||||
|
import sqlite3
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
|
||||||
|
import paramiko
|
||||||
|
|
||||||
|
DB_PATH = "/srv/tesm/sqlite.db"
|
||||||
|
KNOWN_HOSTS_PATH = "/srv/tesm/known_hosts"
|
||||||
|
DEFAULT_SSH_PORT = 22
|
||||||
|
CONNECT_TIMEOUT = 4
|
||||||
|
MAX_WORKERS = 32
|
||||||
|
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
devices = conn.execute(
|
||||||
|
"SELECT name, ip, ssh_port FROM devices WHERE deleted_at IS NULL ORDER BY name"
|
||||||
|
).fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_key(dev):
|
||||||
|
"""Nur der Handshake (Verbindungsaufbau + Schluesseltausch), KEIN Login
|
||||||
|
-- exakt der Teil, der im Browser-Terminal vor der Passwortabfrage
|
||||||
|
passiert. Parallel je Gerät, sonst dauert eine Liste mit vielen
|
||||||
|
offline Geraeten (je bis zu CONNECT_TIMEOUT Sekunden) unnoetig lange."""
|
||||||
|
host = dev["ip"]
|
||||||
|
port = dev["ssh_port"] or DEFAULT_SSH_PORT
|
||||||
|
try:
|
||||||
|
sock = socket.create_connection((host, port), timeout=CONNECT_TIMEOUT)
|
||||||
|
transport = paramiko.Transport(sock)
|
||||||
|
transport.start_client(timeout=CONNECT_TIMEOUT)
|
||||||
|
server_key = transport.get_remote_server_key()
|
||||||
|
transport.close()
|
||||||
|
return dev, server_key, None
|
||||||
|
except (OSError, paramiko.SSHException, EOFError) as e:
|
||||||
|
return dev, None, str(e)
|
||||||
|
|
||||||
|
|
||||||
|
hostkeys = paramiko.HostKeys()
|
||||||
|
try:
|
||||||
|
hostkeys.load(KNOWN_HOSTS_PATH)
|
||||||
|
except (OSError, paramiko.SSHException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
accepted, already_known, changed, unreachable = [], [], [], []
|
||||||
|
|
||||||
|
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
|
||||||
|
futures = [pool.submit(fetch_key, dev) for dev in devices]
|
||||||
|
for future in as_completed(futures):
|
||||||
|
dev, server_key, err = future.result()
|
||||||
|
host = dev["ip"]
|
||||||
|
port = dev["ssh_port"] or DEFAULT_SSH_PORT
|
||||||
|
lookup_host = host if port == DEFAULT_SSH_PORT else f"[{host}]:{port}"
|
||||||
|
|
||||||
|
if err is not None:
|
||||||
|
unreachable.append((dev["name"], host, err))
|
||||||
|
continue
|
||||||
|
|
||||||
|
existing = hostkeys.lookup(lookup_host)
|
||||||
|
if existing and existing.get(server_key.get_name()):
|
||||||
|
if existing[server_key.get_name()] == server_key:
|
||||||
|
already_known.append((dev["name"], host))
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Nicht blind ueberschreiben -- das waere genau das MITM-
|
||||||
|
# Szenario, vor dem die App im Terminal auch schuetzt (harte
|
||||||
|
# Ablehnung statt automatischer Uebernahme eines GEAENDERTEN
|
||||||
|
# Keys).
|
||||||
|
changed.append((dev["name"], host))
|
||||||
|
continue
|
||||||
|
|
||||||
|
hostkeys.add(lookup_host, server_key.get_name(), server_key)
|
||||||
|
accepted.append((dev["name"], host))
|
||||||
|
|
||||||
|
hostkeys.save(KNOWN_HOSTS_PATH)
|
||||||
|
|
||||||
|
print(f"Neu akzeptiert: {len(accepted)}")
|
||||||
|
for name, host in accepted:
|
||||||
|
print(f" + {name} ({host})")
|
||||||
|
|
||||||
|
print(f"Bereits bekannt: {len(already_known)}")
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
print(f"ACHTUNG -- Host-Key geaendert, NICHT automatisch uebernommen ({len(changed)}):")
|
||||||
|
for name, host in changed:
|
||||||
|
print(f" ! {name} ({host}) -- manuell in der App pruefen (moeglicher MITM oder Geraetetausch)")
|
||||||
|
|
||||||
|
print(f"Nicht erreichbar: {len(unreachable)}")
|
||||||
|
for name, host, err in unreachable:
|
||||||
|
print(f" - {name} ({host}): {err}")
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Weist allen aktiven Geraeten (Clients) auf einen Schlag dieselben
|
||||||
|
Zugangsdaten zu -- spart das manuelle Durchklicken jedes einzelnen
|
||||||
|
Geraets in der Weboberflaeche.
|
||||||
|
|
||||||
|
Aufruf auf dem Server:
|
||||||
|
sudo /srv/tesm/venv/bin/python3 assign_credential_to_all_devices.py [NAME]
|
||||||
|
|
||||||
|
NAME ist der Name des Zugangsdaten-Eintrags (Standard: "HAPS")."""
|
||||||
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
DB_PATH = "/srv/tesm/sqlite.db"
|
||||||
|
CRED_NAME = sys.argv[1] if len(sys.argv) > 1 else "HAPS"
|
||||||
|
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
|
||||||
|
cred = conn.execute(
|
||||||
|
"SELECT id FROM credentials WHERE name = ? AND deleted_at IS NULL", (CRED_NAME,)
|
||||||
|
).fetchone()
|
||||||
|
if not cred:
|
||||||
|
print(f"Fehler: Zugangsdaten '{CRED_NAME}' nicht gefunden (oder im Papierkorb).")
|
||||||
|
raise SystemExit(1)
|
||||||
|
credential_id = cred[0]
|
||||||
|
|
||||||
|
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
cur = conn.execute(
|
||||||
|
"UPDATE devices SET credential_id=?, last_modified_by=?, last_modified_at=? WHERE deleted_at IS NULL",
|
||||||
|
(credential_id, "Admin (Bulk)", now),
|
||||||
|
)
|
||||||
|
count = cur.rowcount
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO audit_log (ts, username, action, target, details) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
(now, "system", "devices.bulk_credential", CRED_NAME, f"{count} Geräte auf Zugangsdaten '{CRED_NAME}' gesetzt"),
|
||||||
|
)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print(f"{count} Geräte auf Zugangsdaten '{CRED_NAME}' (id={credential_id}) gesetzt.")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
1.0.0
|
||||||
+41
-4
@@ -509,6 +509,30 @@ def inject_current_year():
|
|||||||
return {"current_year": datetime.now().year}
|
return {"current_year": datetime.now().year}
|
||||||
|
|
||||||
|
|
||||||
|
def _read_tesm_version():
|
||||||
|
"""Liest die Versionsnummer aus VERSION (im Repo neben app.py, wird von
|
||||||
|
install.sh 1:1 nach /srv/tesm mitkopiert). Fehlt die Datei (z.B. lokale
|
||||||
|
Entwicklung ohne vollständigen Checkout), lieber "unbekannt" anzeigen
|
||||||
|
als die App deswegen abstürzen zu lassen."""
|
||||||
|
try:
|
||||||
|
with open(os.path.join(BASE_DIR, "VERSION"), "r", encoding="utf-8") as f:
|
||||||
|
return f.read().strip() or "unbekannt"
|
||||||
|
except OSError:
|
||||||
|
return "unbekannt"
|
||||||
|
|
||||||
|
|
||||||
|
TESM_VERSION = _read_tesm_version()
|
||||||
|
|
||||||
|
|
||||||
|
@app.context_processor
|
||||||
|
def inject_tesm_version():
|
||||||
|
"""Für die Versionsanzeige im Sidebar-Footer (siehe base.html) — beim
|
||||||
|
Modulimport einmalig gelesen (Neustart des Dienstes nach einem Update
|
||||||
|
erforderlich, damit eine neue Versionsnummer sichtbar wird — geschieht
|
||||||
|
ohnehin bei jedem install.sh/update.sh-Lauf)."""
|
||||||
|
return {"tesm_version": TESM_VERSION}
|
||||||
|
|
||||||
|
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
def inject_nav():
|
def inject_nav():
|
||||||
if not current_user.is_authenticated:
|
if not current_user.is_authenticated:
|
||||||
@@ -6089,11 +6113,24 @@ def _apt_upgrade_command():
|
|||||||
DEBIAN_FRONTEND=noninteractive plus --force-confdef/--force-confold
|
DEBIAN_FRONTEND=noninteractive plus --force-confdef/--force-confold
|
||||||
verhindern, dass ein Paket-Postinst-Skript auf eine Konfig-Datei-
|
verhindern, dass ein Paket-Postinst-Skript auf eine Konfig-Datei-
|
||||||
Rückfrage wartet, die nie kommt (das Kommando würde sonst ohne PTY
|
Rückfrage wartet, die nie kommt (das Kommando würde sonst ohne PTY
|
||||||
unbeaufsichtigt hängen bleiben, bis der Timeout zuschlägt)."""
|
unbeaufsichtigt hängen bleiben, bis der Timeout zuschlägt).
|
||||||
|
|
||||||
|
MODULES=most (initramfs-tools) wird nur für diesen einen Aufruf per
|
||||||
|
Umgebungsvariable gesetzt, nicht dauerhaft in /etc/initramfs-tools/
|
||||||
|
initramfs.conf geändert -- falls das Update einen neuen Kernel mitbringt
|
||||||
|
und dabei ein initramfs neu gebaut wird, landen so vorsorglich ALLE
|
||||||
|
verfügbaren Treiber darin (statt nur die per "dep" erkannten), damit das
|
||||||
|
Gerät nach einem Kernel-Wechsel zuverlässig bootet, auch wenn sich z.B.
|
||||||
|
Storage-/Netzwerk-Hardware nicht sauber per Abhängigkeitserkennung
|
||||||
|
ermitteln lässt. Am Ende noch "apt-get autoremove"/"apt-get clean", damit
|
||||||
|
nicht mehr benötigte Kernel/Pakete sowie der heruntergeladene .deb-Cache
|
||||||
|
nicht auf jedem Client unbegrenzt Platz belegen."""
|
||||||
inner = (
|
inner = (
|
||||||
"env DEBIAN_FRONTEND=noninteractive apt-get update && "
|
"env DEBIAN_FRONTEND=noninteractive MODULES=most apt-get update && "
|
||||||
'env DEBIAN_FRONTEND=noninteractive apt-get -y '
|
'env DEBIAN_FRONTEND=noninteractive MODULES=most apt-get -y '
|
||||||
'-o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade'
|
'-o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade && '
|
||||||
|
"env DEBIAN_FRONTEND=noninteractive apt-get -y autoremove && "
|
||||||
|
"apt-get clean"
|
||||||
)
|
)
|
||||||
return f"sudo -S -p '' bash -c {shlex.quote(inner)}"
|
return f"sudo -S -p '' bash -c {shlex.quote(inner)}"
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -1,5 +1,16 @@
|
|||||||
#!/usr/bin/env python3
|
#!/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 os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="sidebar-copyright" style="padding:10px 20px 4px; font-size:10.5px; color:var(--text-faint); text-align:center;">
|
<div class="sidebar-copyright" style="padding:10px 20px 4px; font-size:10.5px; color:var(--text-faint); text-align:center;">
|
||||||
© {{ current_year }} TESM — TimEShepManager
|
© {{ current_year }} TESM — TimEShepManager v{{ tesm_version }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-block" style="margin-top:6px;">Anmelden</button>
|
<button type="submit" class="btn btn-primary btn-block" style="margin-top:6px;">Anmelden</button>
|
||||||
</form>
|
</form>
|
||||||
|
<div style="margin-top:16px; font-size:10.5px; color:var(--text-faint); text-align:center;">TESM v{{ tesm_version }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -15,16 +15,23 @@
|
|||||||
# Lädt dafür NUR den Release-Tarball per HTTP herunter (kein "git clone",
|
# Lädt dafür NUR den Release-Tarball per HTTP herunter (kein "git clone",
|
||||||
# kein Git auf dem Zielsystem nötig).
|
# kein Git auf dem Zielsystem nötig).
|
||||||
#
|
#
|
||||||
# Alle ALTEN Installationen werden dabei NICHT gelöscht, sondern nur
|
# /srv/poe_manager (Stufe 2, komplett anderes Schema/App) wird IMMER nur
|
||||||
# gestoppt/deaktiviert und (falls vorhanden) beiseite verschoben (siehe
|
# gestoppt/deaktiviert und beiseite verschoben, nie in-place übernommen.
|
||||||
# OLD_DIRS/BACKED_UP_DIRS) -- als Rückfalloption UND als Datenquelle für
|
|
||||||
# einen späteren Export/Import.
|
|
||||||
#
|
#
|
||||||
# WICHTIG: Das Ergebnis ist eine FRISCHE Installation OHNE die Daten der
|
# /srv/tesm (Stufe 3) dagegen wird per SCHEMA_VERSION-Datei verglichen:
|
||||||
# alten Instanz (Geräte, Switche, Zugangsdaten, Benutzer, Einstellungen, …).
|
# stimmt die Versionsnummer der bereits installierten mit der des neuen
|
||||||
# Vor dem Ausführen unbedingt unter Einstellungen -> Im-/Export ein Backup
|
# Release-Pakets überein, hat sich am Datenbank-Schema nichts geändert --
|
||||||
# der alten Instanz exportieren -- danach entweder alles neu einrichten oder
|
# dann läuft install.sh IN-PLACE gegen das bestehende /srv/tesm (dessen
|
||||||
# dieses Backup in die neue Installation importieren.
|
# 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 (z.B. POETEST):
|
||||||
# sudo ./update.sh
|
# 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}"
|
DOWNLOAD_URL="${GITEA_BASE}/${REPO_OWNER}/${REPO_NAME}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
||||||
|
|
||||||
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
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)"
|
WORK_DIR="/tmp/tesm-update-$(date +%s)"
|
||||||
PACKAGE_FILE="$WORK_DIR/${PACKAGE_NAME}"
|
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} TESM Update/Reinstall — Release \"${RELEASE_TAG}\"${NC}"
|
||||||
echo -e "${YELLOW}============================================================================${NC}"
|
echo -e "${YELLOW}============================================================================${NC}"
|
||||||
echo
|
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 <Verzeichnis>-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}..."
|
echo -e "${RED}→${NC} Lade Paket von ${DOWNLOAD_URL}..."
|
||||||
mkdir -p "$WORK_DIR"
|
mkdir -p "$WORK_DIR"
|
||||||
@@ -127,26 +84,127 @@ mkdir -p "$WORK_DIR/pkg"
|
|||||||
tar xzf "$PACKAGE_FILE" -C "$WORK_DIR/pkg" --strip-components=1
|
tar xzf "$PACKAGE_FILE" -C "$WORK_DIR/pkg" --strip-components=1
|
||||||
echo -e "${GREEN}✔${NC} Paket entpackt."
|
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 <Verzeichnis>-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..."
|
echo -e "${RED}→${NC} Starte install.sh aus dem Paket..."
|
||||||
bash "$WORK_DIR/pkg/install.sh"
|
bash "$WORK_DIR/pkg/install.sh"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo -e "${GREEN}============================================================================${NC}"
|
echo -e "${GREEN}============================================================================${NC}"
|
||||||
echo -e "${GREEN} Frische TESM-Installation (Release \"${RELEASE_TAG}\") abgeschlossen.${NC}"
|
if [ "$IN_PLACE_UPDATE" -eq 1 ]; then
|
||||||
echo -e "${GREEN}============================================================================${NC}"
|
echo -e "${GREEN} TESM-Update (Release \"${RELEASE_TAG}\") abgeschlossen -- Datenbank erhalten.${NC}"
|
||||||
echo
|
echo -e "${GREEN}============================================================================${NC}"
|
||||||
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
|
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
|
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)."
|
|
||||||
Reference in new issue
Block a user