Compare commits
9
Commits
d066dbf03d
...
v1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b3051c984 | ||
|
|
6dd412c6e1 | ||
|
|
3c3fbd8f23 | ||
|
|
d77327cee1 | ||
|
|
d72db26a86 | ||
|
|
0b9b92a228 | ||
|
|
2a092af508 | ||
|
|
c55f9d992d | ||
|
|
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.1
|
||||||
+121
-9
@@ -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:
|
||||||
@@ -4424,9 +4448,22 @@ def _import_groups(conn, items):
|
|||||||
|
|
||||||
|
|
||||||
def _import_ldap(conn, data):
|
def _import_ldap(conn, data):
|
||||||
|
# set_setting() bewusst NICHT verwendet: das öffnet eine EIGENE
|
||||||
|
# sqlite3-Verbindung -- import_apply() ruft mehrere IMPORT_APPLIERS
|
||||||
|
# nacheinander auf DERSELBEN, bereits offenen "conn" auf und committet
|
||||||
|
# erst ganz am Ende. Ist zu diesem Zeitpunkt (z.B. durch eine vorher im
|
||||||
|
# selben Import verarbeitete Kategorie) bereits eine Schreib-Transaktion
|
||||||
|
# auf "conn" offen, blockiert set_settings()' eigene Verbindung darauf,
|
||||||
|
# bis der SQLite-Standard-Timeout zuschlägt -- "sqlite3.OperationalError:
|
||||||
|
# database is locked", live in Produktion reproduziert. Direkt auf der
|
||||||
|
# übergebenen "conn" schreiben vermeidet die zweite Verbindung komplett.
|
||||||
for key, value in data.get("settings", {}).items():
|
for key, value in data.get("settings", {}).items():
|
||||||
if key in LDAP_SETTING_KEYS:
|
if key in LDAP_SETTING_KEYS:
|
||||||
set_setting(key, value)
|
conn.execute(
|
||||||
|
"INSERT INTO settings (key, value) VALUES (?, ?) "
|
||||||
|
"ON CONFLICT(key) DO UPDATE SET value=excluded.value",
|
||||||
|
(key, value),
|
||||||
|
)
|
||||||
if data.get("bind_username"):
|
if data.get("bind_username"):
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO service_accounts (purpose, username, password) VALUES (?, ?, ?) "
|
"INSERT INTO service_accounts (purpose, username, password) VALUES (?, ?, ?) "
|
||||||
@@ -4445,9 +4482,16 @@ def _import_ldap(conn, data):
|
|||||||
|
|
||||||
|
|
||||||
def _import_dhcp(conn, data):
|
def _import_dhcp(conn, data):
|
||||||
|
# set_setting() bewusst NICHT verwendet -- siehe ausführlicher Kommentar
|
||||||
|
# in _import_ldap() (dieselbe "database is locked"-Falle bei mehreren
|
||||||
|
# IMPORT_APPLIERS auf derselben offenen Transaktion).
|
||||||
for key, value in data.get("settings", {}).items():
|
for key, value in data.get("settings", {}).items():
|
||||||
if key in DHCP_DEFAULTS:
|
if key in DHCP_DEFAULTS:
|
||||||
set_setting(key, value)
|
conn.execute(
|
||||||
|
"INSERT INTO settings (key, value) VALUES (?, ?) "
|
||||||
|
"ON CONFLICT(key) DO UPDATE SET value=excluded.value",
|
||||||
|
(key, value),
|
||||||
|
)
|
||||||
n = 0
|
n = 0
|
||||||
# Subnetze additiv (nie löschen/ersetzen) -- ein bestehendes, unabhängig
|
# Subnetze additiv (nie löschen/ersetzen) -- ein bestehendes, unabhängig
|
||||||
# vom Ziel-Admin konfiguriertes Subnetz soll ein Import nie entfernen.
|
# vom Ziel-Admin konfiguriertes Subnetz soll ein Import nie entfernen.
|
||||||
@@ -5572,6 +5616,27 @@ def maintenance_status():
|
|||||||
|
|
||||||
|
|
||||||
_MAINTENANCE_OUTPUT_MAX_LINES = 400
|
_MAINTENANCE_OUTPUT_MAX_LINES = 400
|
||||||
|
TESM_CLIENT_UPDATE_LOG_DIR = os.path.join(TESM_LOG_DIR, "clientupdate")
|
||||||
|
|
||||||
|
|
||||||
|
def _write_client_update_error_log(name, exit_code, lines):
|
||||||
|
"""Schreibt bei einem mit Exit-Code fehlgeschlagenen Wartungs-Update
|
||||||
|
eine eigene Fehler-Logdatei pro Client (überschreibt eine vorherige aus
|
||||||
|
einem früheren Fehlschlag desselben Clients) -- zusätzlich zur ohnehin
|
||||||
|
im Job-Status/Live-Fenster sichtbaren Ausgabe, damit ein Fehlschlag
|
||||||
|
auch ohne offenes Browser-Fenster im Nachhinein auffindbar bleibt."""
|
||||||
|
try:
|
||||||
|
os.makedirs(TESM_CLIENT_UPDATE_LOG_DIR, exist_ok=True)
|
||||||
|
os.chmod(TESM_CLIENT_UPDATE_LOG_DIR, 0o777)
|
||||||
|
safe_name = secure_filename(name) or "unbenannt"
|
||||||
|
path = os.path.join(TESM_CLIENT_UPDATE_LOG_DIR, f"{safe_name}.error")
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
f.write(f"Update fehlgeschlagen: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
|
||||||
|
f.write(f"Exit-Code: {exit_code}\n\n")
|
||||||
|
f.write("\n".join(lines))
|
||||||
|
f.write("\n")
|
||||||
|
except OSError:
|
||||||
|
app.logger.error("_write_client_update_error_log failed:\n%s", traceback.format_exc())
|
||||||
|
|
||||||
|
|
||||||
def _maintenance_run_update(mac, name, host, port, username, password):
|
def _maintenance_run_update(mac, name, host, port, username, password):
|
||||||
@@ -5623,6 +5688,7 @@ def _maintenance_run_update(mac, name, host, port, username, password):
|
|||||||
message=f"Update fehlgeschlagen (Exit-Code {result['exit_code']}).",
|
message=f"Update fehlgeschlagen (Exit-Code {result['exit_code']}).",
|
||||||
output="\n".join(lines), finished=finished,
|
output="\n".join(lines), finished=finished,
|
||||||
)
|
)
|
||||||
|
_write_client_update_error_log(name, result["exit_code"], lines)
|
||||||
log_action_system("maintenance.update", name, f"fehlgeschlagen: Exit-Code {result['exit_code']}")
|
log_action_system("maintenance.update", name, f"fehlgeschlagen: Exit-Code {result['exit_code']}")
|
||||||
|
|
||||||
|
|
||||||
@@ -6084,18 +6150,64 @@ def _ssh_terminal_inner(ws):
|
|||||||
# ihn möglich sind -- beide Wege teilen sich dieselbe SSH_KNOWN_HOSTS_PATH.
|
# ihn möglich sind -- beide Wege teilen sich dieselbe SSH_KNOWN_HOSTS_PATH.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Jeder Schritt einzeln benannt (statt nur mit "&&" verkettet), damit nach
|
||||||
|
# jedem Schritt ein eigener "Schritt N -> erfolgreich/fehlgeschlagen"-Marker
|
||||||
|
# ins Live-Log geschrieben werden kann (siehe _apt_upgrade_command()) --
|
||||||
|
# apt/apt-get statt der literal vorgegebenen "apt"-Aufrufe, weil apt-get
|
||||||
|
# eine stabile, für Skripte gedachte Schnittstelle hat (die apt(8)-Manpage
|
||||||
|
# warnt ausdrücklich: "the apt command is intended to be pleasant for
|
||||||
|
# end users and does not need to be backward compatible" -- also nichts
|
||||||
|
# für Automatisierung).
|
||||||
|
_LINUX_UPDATE_STEPS = [
|
||||||
|
("apt update", "apt-get update"),
|
||||||
|
("initramfs MODULES=most setzen", "sed -i 's/^MODULES=.*/MODULES=most/' /etc/initramfs-tools/initramfs.conf"),
|
||||||
|
(
|
||||||
|
"apt upgrade",
|
||||||
|
'apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"apt full-upgrade",
|
||||||
|
'apt-get -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" full-upgrade',
|
||||||
|
),
|
||||||
|
("apt clean", "apt-get clean"),
|
||||||
|
("apt autoremove", "apt-get -y autoremove"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _apt_upgrade_command():
|
def _apt_upgrade_command():
|
||||||
"""Sicheres, nicht-interaktives 'apt update && apt upgrade -y':
|
"""Baut die Wartungs-Update-Kette aus _LINUX_UPDATE_STEPS zusammen --
|
||||||
|
jeder Schritt einzeln mit eigenem "=== Schritt N/M: ... ===" und
|
||||||
|
danach "Schritt N (...) -> erfolgreich"/"-> fehlgeschlagen (Exit-Code
|
||||||
|
...)" im Live-Log, statt nur einer einzigen Erfolgs-/Fehlermeldung für
|
||||||
|
die ganze Kette. Bricht beim ersten fehlgeschlagenen Schritt sofort ab
|
||||||
|
(dessen Exit-Code wird zum Exit-Code des Gesamtkommandos), statt
|
||||||
|
blind mit dem nächsten Schritt weiterzumachen.
|
||||||
|
|
||||||
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).
|
||||||
inner = (
|
|
||||||
"env DEBIAN_FRONTEND=noninteractive apt-get update && "
|
MODULES=most wird -- anders als zuvor -- dauerhaft in
|
||||||
'env DEBIAN_FRONTEND=noninteractive apt-get -y '
|
/etc/initramfs-tools/initramfs.conf gesetzt (nicht nur für diesen einen
|
||||||
'-o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" upgrade'
|
Aufruf per Umgebungsvariable): falls apt upgrade/full-upgrade einen
|
||||||
|
neuen Kernel installiert und dabei ein initramfs neu gebaut wird,
|
||||||
|
landen so ALLE verfügbaren Treiber darin (statt nur die per "dep"
|
||||||
|
erkannten), damit das Gerät nach einem Kernel-Wechsel zuverlässig
|
||||||
|
bootet -- und das bleibt auch für jeden künftigen, unabhängig von
|
||||||
|
dieser App ausgelösten Kernel-Update/initramfs-Rebuild so bestehen."""
|
||||||
|
script_lines = ["export DEBIAN_FRONTEND=noninteractive"]
|
||||||
|
total = len(_LINUX_UPDATE_STEPS)
|
||||||
|
for i, (label, cmd) in enumerate(_LINUX_UPDATE_STEPS, start=1):
|
||||||
|
script_lines.append(f"echo '=== Schritt {i}/{total}: {label} ==='")
|
||||||
|
script_lines.append(cmd)
|
||||||
|
script_lines.append("_rc=$?")
|
||||||
|
script_lines.append(
|
||||||
|
f'if [ "$_rc" -eq 0 ]; then echo "Schritt {i} ({label}) -> erfolgreich"; '
|
||||||
|
f'else echo "Schritt {i} ({label}) -> fehlgeschlagen (Exit-Code $_rc)"; exit "$_rc"; fi'
|
||||||
)
|
)
|
||||||
return f"sudo -S -p '' bash -c {shlex.quote(inner)}"
|
script = "\n".join(script_lines)
|
||||||
|
return f"sudo -S -p '' bash -c {shlex.quote(script)}"
|
||||||
|
|
||||||
|
|
||||||
def _resolve_device_ssh_target(conn, device):
|
def _resolve_device_ssh_target(conn, device):
|
||||||
|
|||||||
+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
|
||||||
echo "Bitte jetzt eines von beidem tun:"
|
echo "Alle Geräte/Switche/Zugangsdaten/Benutzer/Einstellungen sind unverändert"
|
||||||
echo " a) Alle Geräte/Switche/Zugangsdaten/Benutzer/Einstellungen manuell neu anlegen, oder"
|
echo "vorhanden -- kein manuelles Neuanlegen oder Re-Import nötig."
|
||||||
echo " b) Ein vor dem Update exportiertes Backup unter Einstellungen -> Im-/Export importieren."
|
else
|
||||||
echo
|
echo -e "${GREEN} Frische TESM-Installation (Release \"${RELEASE_TAG}\") abgeschlossen.${NC}"
|
||||||
if [ "${#BACKED_UP_DIRS[@]}" -gt 0 ]; then
|
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:"
|
echo "Falls kein Export gemacht wurde: die alte(n) Datenbank(en) liegen unverändert unter:"
|
||||||
for dest in "${BACKED_UP_DIRS[@]}"; do
|
for dest in "${BACKED_UP_DIRS[@]}"; do
|
||||||
echo " ${dest}/sqlite.db"
|
echo " ${dest}/sqlite.db"
|
||||||
done
|
done
|
||||||
echo "und können bei Bedarf manuell ausgelesen werden (z.B. mit sqlite3)."
|
echo "und können bei Bedarf manuell ausgelesen werden (z.B. mit sqlite3)."
|
||||||
echo
|
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)."
|
|
||||||
+21
-40
@@ -29,16 +29,21 @@ END
|
|||||||
|
|
||||||
SLEEP=${SLEEP:-300}
|
SLEEP=${SLEEP:-300}
|
||||||
|
|
||||||
# disable_poe()/enable_poe() geben jetzt den TATSÄCHLICHEN Erfolg als
|
# disable_poe()/enable_poe(): bewusst OHNE expliziten "timeout { exit 1 }"/
|
||||||
# Exit-Code zurück (0 = Switch erreicht UND Kommando bestätigt, 1 =
|
# "eof { exit 1 }"-Abbruch in den einzelnen expect-Schritten -- ein nicht
|
||||||
# fehlgeschlagen), statt blind anzunehmen, dass ein SSH-Verbindungsversuch
|
# matchender Schritt laesst expect nach Ablauf von "set timeout 5" einfach
|
||||||
# immer klappt. Jeder kritische expect-Schritt (Verbindungsaufbau,
|
# zur naechsten Zeile weiterlaufen, statt abzubrechen. Das ist absichtlich
|
||||||
# configure-terminal-Prompt, Interface-Prompt, Bestätigung des
|
# so belassen: ein Versuch, hier "echte" Fehlererkennung nachzuruesten
|
||||||
# power-over-ethernet-Kommandos) hat dafür einen eigenen timeout-Zweig,
|
# (Exit-Code 0 nur bei bestaetigtem Erfolg je Schritt), hat sich live als
|
||||||
# der explizit mit "exit 1" abbricht -- vorher lief das expect-Skript bei
|
# Regression herausgestellt. Grund: das Login-Prompt-Pattern
|
||||||
# einem unerreichbaren Switch einfach durch alle Timeouts durch und endete
|
# "-re ".*> $"" unten matcht nur ein Operator-Prompt ("switchname>"). Auf
|
||||||
# trotzdem mit Exit-Code 0, wodurch der Aufrufer fälschlich "PoE
|
# ArubaOS-Switches, bei denen der SSH-Account (z.B. "admin") mit
|
||||||
# deaktiviert/aktiviert" geloggt hat, obwohl am Switch nichts passiert war.
|
# 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() {
|
function disable_poe() {
|
||||||
local switch_ip=$1
|
local switch_ip=$1
|
||||||
local switch_port=$2
|
local switch_port=$2
|
||||||
@@ -52,24 +57,13 @@ expect {
|
|||||||
"assword:" { send "$password\r"; exp_continue }
|
"assword:" { send "$password\r"; exp_continue }
|
||||||
"Press any key" { send "\r"; exp_continue }
|
"Press any key" { send "\r"; exp_continue }
|
||||||
-re ".*> $" { }
|
-re ".*> $" { }
|
||||||
timeout { exit 1 }
|
|
||||||
eof { exit 1 }
|
|
||||||
}
|
}
|
||||||
send "configure terminal\r"
|
send "configure terminal\r"
|
||||||
expect {
|
expect "(config)#"
|
||||||
"(config)#" { }
|
|
||||||
timeout { exit 1 }
|
|
||||||
}
|
|
||||||
send "interface $switch_port\r"
|
send "interface $switch_port\r"
|
||||||
expect {
|
expect "(eth-$switch_port)#"
|
||||||
"(eth-$switch_port)#" { }
|
|
||||||
timeout { exit 1 }
|
|
||||||
}
|
|
||||||
send "no power-over-ethernet\r"
|
send "no power-over-ethernet\r"
|
||||||
expect {
|
expect "(eth-$switch_port)#"
|
||||||
"(eth-$switch_port)#" { }
|
|
||||||
timeout { exit 1 }
|
|
||||||
}
|
|
||||||
send "exit\r"
|
send "exit\r"
|
||||||
expect "(config)#"
|
expect "(config)#"
|
||||||
send "exit\r"
|
send "exit\r"
|
||||||
@@ -78,7 +72,6 @@ send "exit\r"
|
|||||||
expect ">"; send "exit\r"
|
expect ">"; send "exit\r"
|
||||||
expect "Do you want to log out (y/n)?" { send "y\r" }
|
expect "Do you want to log out (y/n)?" { send "y\r" }
|
||||||
expect eof
|
expect eof
|
||||||
exit 0
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,24 +88,13 @@ expect {
|
|||||||
"assword:" { send "$password\r"; exp_continue }
|
"assword:" { send "$password\r"; exp_continue }
|
||||||
"Press any key" { send "\r"; exp_continue }
|
"Press any key" { send "\r"; exp_continue }
|
||||||
-re ".*> $" { }
|
-re ".*> $" { }
|
||||||
timeout { exit 1 }
|
|
||||||
eof { exit 1 }
|
|
||||||
}
|
}
|
||||||
send "configure terminal\r"
|
send "configure terminal\r"
|
||||||
expect {
|
expect "(config)#"
|
||||||
"(config)#" { }
|
|
||||||
timeout { exit 1 }
|
|
||||||
}
|
|
||||||
send "interface $switch_port\r"
|
send "interface $switch_port\r"
|
||||||
expect {
|
expect "(eth-$switch_port)#"
|
||||||
"(eth-$switch_port)#" { }
|
|
||||||
timeout { exit 1 }
|
|
||||||
}
|
|
||||||
send "power-over-ethernet\r"
|
send "power-over-ethernet\r"
|
||||||
expect {
|
expect "(eth-$switch_port)#"
|
||||||
"(eth-$switch_port)#" { }
|
|
||||||
timeout { exit 1 }
|
|
||||||
}
|
|
||||||
send "exit\r"
|
send "exit\r"
|
||||||
expect "(config)#"
|
expect "(config)#"
|
||||||
send "exit\r"
|
send "exit\r"
|
||||||
@@ -121,7 +103,6 @@ send "exit\r"
|
|||||||
expect ">"; send "exit\r"
|
expect ">"; send "exit\r"
|
||||||
expect "Do you want to log out (y/n)?" { send "y\r" }
|
expect "Do you want to log out (y/n)?" { send "y\r" }
|
||||||
expect eof
|
expect eof
|
||||||
exit 0
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user