Compare commits
6
Commits
9f4195b800
...
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c3fbd8f23 | ||
|
|
d77327cee1 | ||
|
|
d72db26a86 | ||
|
|
0b9b92a228 | ||
|
|
2a092af508 | ||
|
|
c55f9d992d |
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}
|
||||
|
||||
|
||||
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
|
||||
def inject_nav():
|
||||
if not current_user.is_authenticated:
|
||||
@@ -6089,11 +6113,24 @@ def _apt_upgrade_command():
|
||||
DEBIAN_FRONTEND=noninteractive plus --force-confdef/--force-confold
|
||||
verhindern, dass ein Paket-Postinst-Skript auf eine Konfig-Datei-
|
||||
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 = (
|
||||
"env DEBIAN_FRONTEND=noninteractive apt-get update && "
|
||||
'env DEBIAN_FRONTEND=noninteractive apt-get -y '
|
||||
'-o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade'
|
||||
"env DEBIAN_FRONTEND=noninteractive MODULES=most apt-get update && "
|
||||
'env DEBIAN_FRONTEND=noninteractive MODULES=most apt-get -y '
|
||||
'-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)}"
|
||||
|
||||
|
||||
+12
-1
@@ -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
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<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>
|
||||
</aside>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-block" style="margin-top:6px;">Anmelden</button>
|
||||
</form>
|
||||
<div style="margin-top:16px; font-size:10.5px; color:var(--text-faint); text-align:center;">TESM v{{ tesm_version }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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 <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}..."
|
||||
mkdir -p "$WORK_DIR"
|
||||
@@ -127,11 +84,111 @@ 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 <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..."
|
||||
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
|
||||
@@ -150,3 +207,4 @@ if [ "${#BACKED_UP_DIRS[@]}" -gt 0 ]; then
|
||||
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
|
||||
+15
-10
@@ -29,16 +29,21 @@ END
|
||||
|
||||
SLEEP=${SLEEP:-300}
|
||||
|
||||
# disable_poe()/enable_poe() geben jetzt den TATSÄCHLICHEN Erfolg als
|
||||
# Exit-Code zurück (0 = Switch erreicht UND Kommando bestätigt, 1 =
|
||||
# fehlgeschlagen), statt blind anzunehmen, dass ein SSH-Verbindungsversuch
|
||||
# immer klappt. Jeder kritische expect-Schritt (Verbindungsaufbau,
|
||||
# configure-terminal-Prompt, Interface-Prompt, Bestätigung des
|
||||
# power-over-ethernet-Kommandos) hat dafür einen eigenen timeout-Zweig,
|
||||
# der explizit mit "exit 1" abbricht -- vorher lief das expect-Skript bei
|
||||
# einem unerreichbaren Switch einfach durch alle Timeouts durch und endete
|
||||
# trotzdem mit Exit-Code 0, wodurch der Aufrufer fälschlich "PoE
|
||||
# deaktiviert/aktiviert" geloggt hat, obwohl am Switch nichts passiert war.
|
||||
# disable_poe()/enable_poe(): bewusst OHNE expliziten "timeout { exit 1 }"/
|
||||
# "eof { exit 1 }"-Abbruch in den einzelnen expect-Schritten -- ein nicht
|
||||
# matchender Schritt laesst expect nach Ablauf von "set timeout 5" einfach
|
||||
# zur naechsten Zeile weiterlaufen, statt abzubrechen. Das ist absichtlich
|
||||
# so belassen: ein Versuch, hier "echte" Fehlererkennung nachzuruesten
|
||||
# (Exit-Code 0 nur bei bestaetigtem Erfolg je Schritt), hat sich live als
|
||||
# Regression herausgestellt. Grund: das Login-Prompt-Pattern
|
||||
# "-re ".*> $"" unten matcht nur ein Operator-Prompt ("switchname>"). Auf
|
||||
# ArubaOS-Switches, bei denen der SSH-Account (z.B. "admin") mit
|
||||
# Manager-Rechten direkt im privilegierten Modus landet ("switchname#"
|
||||
# statt "switchname>", bestaetigt an einem 2930M/"ST-2000-Sued"), matcht
|
||||
# das Pattern nie -- mit einem harten Timeout-Abbruch waere das faelschlich
|
||||
# als Fehlschlag gewertet worden, obwohl Passwort und Verbindung in
|
||||
# Ordnung waren. Diese Funktionen hier sind daher wieder exakt auf den
|
||||
# Stand der urspruenglichen, in Produktion bewaehrten Fassung zurueckgesetzt.
|
||||
function disable_poe() {
|
||||
local switch_ip=$1
|
||||
local switch_port=$2
|
||||
|
||||
Reference in new issue
Block a user