From c019d2dcecbb8c3041e2d704f69f099b13224f76 Mon Sep 17 00:00:00 2001 From: sascha Date: Wed, 5 Aug 2026 14:01:37 +0200 Subject: [PATCH] feat: Borg-Diagnose und manuellen Backup-Start ergaenzen --- app.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/app.py b/app.py index 5c26a67..8c4b1d2 100644 --- a/app.py +++ b/app.py @@ -546,6 +546,61 @@ async def backup_status(_=Depends(_verify)): return await _collect_backup_status() +@app.get("/backup/diagnose/{hostname}") +async def backup_diagnose(hostname: str, _=Depends(_verify)): + """Safe Borgmatic timer/service diagnostics without exposing backup secrets.""" + host = await asyncio.to_thread(_find_inventory_host, hostname) + if not host or host["name"].startswith("node"): + return JSONResponse({"error": "inventory host not found"}, status_code=404) + command = """echo __TIMER_ENABLED__ +sudo -n systemctl is-enabled borg-backup.timer 2>&1 || true +echo __TIMER_ACTIVE__ +sudo -n systemctl is-active borg-backup.timer 2>&1 || true +echo __TIMER_SCHEDULE__ +sudo -n systemctl list-timers borg-backup.timer --all --no-pager 2>&1 || true +echo __SERVICE_STATE__ +sudo -n systemctl show borg-backup.service -p ActiveState -p SubState -p Result -p ExecMainStatus --no-pager 2>&1 || true +echo __RECENT_LOGS__ +sudo -n journalctl -u borg-backup.service --since '2 days ago' -n 120 --no-pager 2>&1 || true +echo __LATEST_ARCHIVE__ +timeout 50s sudo -n borgmatic list --last 1 --json 2>&1 || true +""" + rc, out, err = await asyncio.to_thread( + _ssh, f'{host["user"]}@{host["ip"]}', command, 75 + ) + return { + "hostname": host["name"], "ip": host["ip"], "user": host["user"], + "rc": rc, "output": out[-30000:], "stderr": err[-2000:], + } + + +@app.post("/backup/run/{hostname}") +async def backup_run(hostname: str, _=Depends(_verify)): + """Start one inventory host Borgmatic service asynchronously.""" + host = await asyncio.to_thread(_find_inventory_host, hostname) + if not host or host["name"].startswith("node"): + return JSONResponse({"error": "inventory host not found"}, status_code=404) + command = ( + "sudo -n systemctl reset-failed borg-backup.service 2>/dev/null || true; " + "sudo -n systemctl start --no-block borg-backup.service; sleep 2; " + "sudo -n systemctl show borg-backup.service -p ActiveState -p SubState -p Result --no-pager" + ) + rc, out, err = await asyncio.to_thread( + _ssh, f'{host["user"]}@{host["ip"]}', command, 20 + ) + _audit(f"/backup/run/{hostname}", "POST", 202 if rc == 0 else 500, f"rc={rc}") + if rc != 0: + return JSONResponse( + {"error": "backup start failed", "hostname": hostname, + "stdout": out[-2000:], "stderr": err[-2000:]}, + status_code=500, + ) + return JSONResponse( + {"status": "started", "hostname": hostname, "detail": out.strip()}, + status_code=202, + ) + + async def _collect_disk_usage(concurrency: int = 10) -> dict: semaphore = asyncio.Semaphore(concurrency)