feat: Backup-Prüfung nennt die neueste vollständige Sicherung

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 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-21 14:25:40 +02:00
co-authored by Claude Opus 4.8
parent 33bbc535cd
commit ff95bc5344
+71 -15
View File
@@ -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):
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 "-"
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}")
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()
except Exception as e:
print(f"{os.path.basename(f):<34} Fehler: {e}")
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.")