From 6a9424be439b157faba4b1b929b42e9f39c74909 Mon Sep 17 00:00:00 2001 From: sascha Date: Wed, 5 Aug 2026 14:04:37 +0200 Subject: [PATCH] feat: stale Borg-Locks kontrolliert aufloesen --- app.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/app.py b/app.py index 8c4b1d2..80e44df 100644 --- a/app.py +++ b/app.py @@ -574,6 +574,35 @@ timeout 50s sudo -n borgmatic list --last 1 --json 2>&1 || true } +@app.post("/backup/break-lock/{hostname}") +async def backup_break_lock(hostname: str, _=Depends(_verify)): + """Break a stale Borg repository lock only while the backup service is inactive.""" + 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) + rc_state, state, state_err = await asyncio.to_thread( + _ssh, f'{host["user"]}@{host["ip"]}', + "sudo -n systemctl is-active borg-backup.service 2>&1 || true", 15 + ) + if state.strip() in ("active", "activating"): + return JSONResponse( + {"error": "backup service is active; refusing to break lock", "state": state.strip()}, + status_code=409, + ) + rc, out, err = await asyncio.to_thread( + _ssh, f'{host["user"]}@{host["ip"]}', + "sudo -n borgmatic borg break-lock", 120 + ) + _audit(f"/backup/break-lock/{hostname}", "POST", 200 if rc == 0 else 500, f"rc={rc}") + if rc != 0: + return JSONResponse( + {"error": "borg break-lock failed", "hostname": hostname, + "stdout": out[-4000:], "stderr": err[-4000:]}, + status_code=500, + ) + return {"status": "lock_broken", "hostname": hostname, "detail": out.strip()[-4000:]} + + @app.post("/backup/run/{hostname}") async def backup_run(hostname: str, _=Depends(_verify)): """Start one inventory host Borgmatic service asynchronously."""