feat: FritzBox-Sicherung herunterladen + maximale Anzahl je Box einstellbar
- Satellit: fritzbox.backup_get liefert eine vorhandene Sicherung (nur aus dem Sicherungsordner, bis 20 MB). Bei ARIA landet sie unter /shared/uploads/ und kann in den Chat. - Zugangsdaten FritzBox: "Maximale Sicherungen" (1–100, leer = 10). Bei jeder neuen Sicherung fliegt die aelteste dieser Box raus; die Liste meldet die Zahl mit (Kopfzeile im 💾-Fenster). - Diagnostic: ⬇ je Sicherung laedt die .export-Datei herunter. Getestet: Satellit (Limit 2 → die zwei neuesten bleiben, Download, Pfad- schutz), Diagnostic in headless Chromium (Download-Inhalt, Feld laden, 0 abgelehnt, 20 gespeichert). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+31
-3
@@ -1465,6 +1465,16 @@ FB_BACKUP_DIR = os.environ.get("FB_BACKUP_DIR", "/data/fritzbox-backups")
|
||||
FB_BACKUP_KEEP = int(os.environ.get("FB_BACKUP_KEEP", "10") or "10")
|
||||
|
||||
|
||||
def _fb_backup_keep(ip: str) -> int:
|
||||
"""Wie viele Sicherungen je Box bleiben: Zugangsdaten (Feld "Maximale
|
||||
Sicherungen") > FB_BACKUP_KEEP (Standard 10). Aelteste fliegen zuerst."""
|
||||
try:
|
||||
n = int(_creds_for(ip).get("fritzbox", {}).get("backupKeep") or 0)
|
||||
except (TypeError, ValueError):
|
||||
n = 0
|
||||
return max(1, min(100, n)) if n else FB_BACKUP_KEEP
|
||||
|
||||
|
||||
def _fb_backup(ip: str, user: str, pw: str, params: dict) -> dict:
|
||||
"""Einstellungen der Box sichern (.export) — landet auf dem Satelliten unter
|
||||
FB_BACKUP_DIR, die letzten FB_BACKUP_KEEP je Box bleiben. Kennwort der
|
||||
@@ -1517,8 +1527,9 @@ def _fb_backup(ip: str, user: str, pw: str, params: dict) -> dict:
|
||||
"kennwort": "angegeben" if params.get("password") else ("backupPass" if stored else "login")}
|
||||
with open(path[:-len(".export")] + ".json", "w", encoding="utf-8") as fh:
|
||||
json.dump(meta, fh, ensure_ascii=False, indent=1)
|
||||
keep = _fb_backup_keep(ip)
|
||||
old = sorted(f for f in os.listdir(FB_BACKUP_DIR) if f.startswith(label + "_") and f.endswith(".export"))
|
||||
for f in old[:-FB_BACKUP_KEEP]:
|
||||
for f in old[:-keep]:
|
||||
for ext in (".export", ".json"):
|
||||
try:
|
||||
os.remove(os.path.join(FB_BACKUP_DIR, f[:-len(".export")] + ext))
|
||||
@@ -1537,7 +1548,7 @@ def _fb_backup(ip: str, user: str, pw: str, params: dict) -> dict:
|
||||
"kennwort": ("das angegebene Kennwort" if params.get("password")
|
||||
else "das im Satelliten hinterlegte Sicherungs-Kennwort" if stored
|
||||
else "das FritzBox-Login-Kennwort"),
|
||||
"hinweis": f"Liegt auf dem Satelliten, die letzten {FB_BACKUP_KEEP} je Box bleiben. "
|
||||
"hinweis": f"Liegt auf dem Satelliten, die letzten {keep} je Box bleiben. "
|
||||
"Zum Wiederherstellen in der FritzBox: System → Sicherung → Wiederherstellen, mit dem Kennwort."}}
|
||||
|
||||
|
||||
@@ -1600,7 +1611,8 @@ def _fb_backups(ip: str, params: dict) -> dict:
|
||||
out.append({"datei": f, "erstellt": m.get("created", ""), "bytes": m.get("bytes") or
|
||||
os.path.getsize(os.path.join(FB_BACKUP_DIR, f)), "modell": m.get("model", ""),
|
||||
"firmware": m.get("firmware", ""), "kennwort": m.get("kennwort", "")})
|
||||
return {"ok": True, "result": {"ip": ip, "count": len(out), "sicherungen": out}}
|
||||
return {"ok": True, "result": {"ip": ip, "count": len(out), "sicherungen": out,
|
||||
"behalten": _fb_backup_keep(ip)}}
|
||||
|
||||
|
||||
# Lesbare Hinweise aus der Sicherung: AVM-Konfig "schluessel = wert;"
|
||||
@@ -1819,6 +1831,20 @@ def _fb_second_factor_set(ip: str, user: str, pw: str, params: dict) -> dict:
|
||||
"button_wait": params.get("button_wait")})
|
||||
|
||||
|
||||
def _fb_backup_get(params: dict) -> dict:
|
||||
"""Eine vorhandene Sicherung als Datei zurueckgeben (Download im Diagnostic,
|
||||
bei ARIA landet sie unter /shared/uploads/ fuer den Chat)."""
|
||||
path = _fb_backup_path(params.get("file") or params.get("datei") or "")
|
||||
if not path:
|
||||
return {"ok": False, "error": "Sicherung nicht gefunden (Dateiname aus fritzbox.backups)."}
|
||||
data = open(path, "rb").read()
|
||||
if len(data) > 20 * 1024 * 1024:
|
||||
return {"ok": False, "error": "Sicherung zu gross fuer die Uebertragung (> 20 MB)."}
|
||||
return {"ok": True, "result": {"datei": os.path.basename(path), "bytes": len(data),
|
||||
"filename": os.path.basename(path),
|
||||
"file_b64": base64.b64encode(data).decode()}}
|
||||
|
||||
|
||||
def _fb_backup_delete(params: dict) -> dict:
|
||||
path = _fb_backup_path(params.get("file") or params.get("datei") or "")
|
||||
if not path:
|
||||
@@ -1850,6 +1876,8 @@ def _do_fritzbox(action: str, params: dict) -> dict:
|
||||
return _fb_backup_view(params)
|
||||
if action == "fritzbox.backup_delete":
|
||||
return _fb_backup_delete(params)
|
||||
if action == "fritzbox.backup_get":
|
||||
return _fb_backup_get(params)
|
||||
fb = _creds_for(ip).get("fritzbox", {})
|
||||
user = str(params.get("user") or fb.get("user") or "")
|
||||
pw = str(params.get("pass") or params.get("password") or fb.get("pass") or "")
|
||||
|
||||
Reference in New Issue
Block a user