From ff95bc5344f68079a7209e77ef98fc6511199e8f Mon Sep 17 00:00:00 2001 From: Guido Date: Fri, 21 Aug 2026 14:25:40 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Backup-Pr=C3=BCfung=20nennt=20die=20neu?= =?UTF-8?q?este=20vollst=C3=A4ndige=20Sicherung?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bei 35 Sicherungen hilft eine blosse Liste wenig. Das Skript zeigt jetzt je Sicherung Zeitpunkt, Schlagwörter, Budget-Vorlagen und wie viele Buchungen eine Kategorie tragen – und nennt am Ende die neueste noch vollständige samt der fertigen Befehle zum Zurückspielen. Tabellen, die es zum Sicherungszeitpunkt noch nicht gab, erscheinen als '–' statt als Fehler. Co-Authored-By: Claude Opus 4.8 --- backend/scripts/pruefe_backups.py | 94 ++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 19 deletions(-) mode change 100644 => 100755 backend/scripts/pruefe_backups.py diff --git a/backend/scripts/pruefe_backups.py b/backend/scripts/pruefe_backups.py old mode 100644 new mode 100755 index f500beb..4c1d013 --- a/backend/scripts/pruefe_backups.py +++ b/backend/scripts/pruefe_backups.py @@ -1,22 +1,78 @@ #!/usr/bin/env python3 -"""Listet je Backup, wie viele Schlagwörter/Budget-Vorlagen/kategorisierte -Buchungen darin stecken. So sieht man, welches Backup noch heil ist.""" -import glob, os, sqlite3, sys +"""Prüft die Datenbank-Sicherungen auf Vollständigkeit. -muster = sys.argv[1] if len(sys.argv) > 1 else "/var/lib/certain-finance/backups/*.db" -print(f"{'Datei':<34}{'Schlagw.':>9}{'Budget':>8}{'Buch.m.Kat':>12}") -for f in sorted(glob.glob(muster)): +Listet je Sicherung, wie viele Schlagwörter, Budget-Vorlagen und kategorisierte +Buchungen darin stecken, und nennt am Ende die NEUESTE noch vollständige. So +muss man nach einem Datenverlust nicht nach Zeitstempel raten. + +Aufruf: pruefe_backups.py [muster] +""" +import glob +import os +import sqlite3 +import sys +from datetime import datetime + +MUSTER = sys.argv[1] if len(sys.argv) > 1 else "/var/lib/certain-finance/backups/*.db" + + +def zaehle(pfad: str) -> dict | None: + """Kennzahlen einer Sicherung. None, wenn die Datei unlesbar ist.""" try: - con = sqlite3.connect(f"file:{f}?mode=ro", uri=True) - def z(sql): - try: - return con.execute(sql).fetchone()[0] - except sqlite3.Error: - return "-" - print(f"{os.path.basename(f):<34}" - f"{z('SELECT COUNT(*) FROM categorization_rules'):>9}" - f"{z('SELECT COUNT(*) FROM budget_template_lines'):>8}" - f"{z('SELECT COUNT(*) FROM transactions WHERE category_id IS NOT NULL'):>12}") - con.close() - except Exception as e: - print(f"{os.path.basename(f):<34} Fehler: {e}") + con = sqlite3.connect(f"file:{pfad}?mode=ro", uri=True) + except sqlite3.Error: + return None + def z(sql: str): + try: + return con.execute(sql).fetchone()[0] + except sqlite3.Error: + return None # Tabelle gab es zu dem Zeitpunkt noch nicht + werte = { + "schlagwoerter": z("SELECT COUNT(*) FROM categorization_rules"), + "budget": z("SELECT COUNT(*) FROM budget_template_lines"), + "kategorisiert": z("SELECT COUNT(*) FROM transactions WHERE category_id IS NOT NULL"), + "buchungen": z("SELECT COUNT(*) FROM transactions"), + } + con.close() + return werte + + +def zahl(v) -> str: + return "–" if v is None else str(v) + + +dateien = sorted(glob.glob(MUSTER), key=os.path.getmtime) +if not dateien: + print(f"Keine Sicherungen gefunden unter: {MUSTER}") + raise SystemExit(1) + +print(f"{'Sicherung':<32}{'Zeitpunkt':<18}{'Schlagw.':>9}{'Budget':>8}{'Buch.':>7}{'davon m. Kat.':>15}") +print("-" * 91) +neueste_heil = None +for f in dateien: + w = zaehle(f) + zeit = datetime.fromtimestamp(os.path.getmtime(f)).strftime("%d.%m. %H:%M") + if w is None: + print(f"{os.path.basename(f):<32}{zeit:<18} nicht lesbar") + continue + print(f"{os.path.basename(f):<32}{zeit:<18}{zahl(w['schlagwoerter']):>9}" + f"{zahl(w['budget']):>8}{zahl(w['buchungen']):>7}{zahl(w['kategorisiert']):>15}") + if (w["schlagwoerter"] or 0) > 0: + neueste_heil = (f, w, zeit) + +print() +if neueste_heil: + f, w, zeit = neueste_heil + print(f"NEUESTE Sicherung mit Schlagwörtern: {os.path.basename(f)} ({zeit})") + print(f" {w['schlagwoerter']} Schlagwörter, {zahl(w['budget'])} Budget-Vorlagen, " + f"{zahl(w['kategorisiert'])} von {zahl(w['buchungen'])} Buchungen kategorisiert") + print() + print("Zurückspielen (Dienst stoppen, sichern, ersetzen, starten):") + print(" sudo systemctl stop certain-finance") + print(" sudo cp /var/lib/certain-finance/app.db /var/lib/certain-finance/app.db.kaputt") + print(f" sudo cp {f} /var/lib/certain-finance/app.db") + print(" sudo systemctl start certain-finance") +else: + print("Keine Sicherung enthält Schlagwörter.") + print("Dann waren zum Sicherungszeitpunkt noch keine angelegt – die Zuordnung") + print("lässt sich stattdessen nachträglich über die Schlagwörter neu erzeugen.")