From 9e35e51bbfbcea895daef45a7190a48f0e1b3e98 Mon Sep 17 00:00:00 2001 From: Micha Date: Mon, 6 Apr 2026 13:18:07 +0000 Subject: [PATCH] fix(backrest): use Connect-RPC endpoints with Basic Auth --- .../backend/app/clients/backrest_client.py | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/apps/dashboard/backend/app/clients/backrest_client.py b/apps/dashboard/backend/app/clients/backrest_client.py index 036947c..5f4e6b1 100644 --- a/apps/dashboard/backend/app/clients/backrest_client.py +++ b/apps/dashboard/backend/app/clients/backrest_client.py @@ -14,38 +14,43 @@ class BackrestClient(BaseHTTPClient): def __init__(self, settings: Settings) -> None: super().__init__(settings, "backrest", settings.backrest_base_url) + def _auth(self) -> tuple[str, str] | None: + if self.settings.backrest_username and self.settings.backrest_password: + return (self.settings.backrest_username, self.settings.backrest_password) + return None + async def fetch_status(self) -> BackrestSnapshot: snapshot = BackrestSnapshot() if not self.base_url: logger.info("backrest skipped: base URL missing") return snapshot - # Backrest uses Connect-RPC: POST /v1.Backrest/{Method} - try: - data = await self._request_json("POST", "/v1.Backrest/GetConfig", json={}) - except Exception as exc: - logger.warning("backrest fetch_status GetConfig failed: %s", exc) - return snapshot + auth = self._auth() + # Get config (repo list) via Connect-RPC + data = await self._request_json( + "POST", "/v1.Backrest/GetConfig", + json={}, + auth=auth, + ) if not isinstance(data, dict): return snapshot repos = data.get("repos") or [] repo_count = len(repos) if isinstance(repos, list) else 0 - # Fetch recent operations for backup status + # Get recent operations via Connect-RPC last_backup_age_hours: float | None = None error_count = 0 last_backup_status = "unknown" - try: - ops_data = await self._request_json( - "POST", - "/v1.Backrest/GetOperations", - json={"lastN": 20}, - ) - ops = ops_data.get("operations") or [] if isinstance(ops_data, dict) else [] - # backupOp is a oneof field serialised directly at the operation level in proto3 JSON + ops_data = await self._request_json( + "POST", "/v1.Backrest/GetOperations", + json={"lastN": 20}, + auth=auth, + ) + if isinstance(ops_data, dict): + ops = ops_data.get("operations") or [] backup_ops = [ op for op in ops if isinstance(op, dict) and op.get("backupOp") is not None @@ -61,13 +66,12 @@ class BackrestClient(BaseHTTPClient): now = datetime.now(timezone.utc) last_backup_age_hours = round((now - ended).total_seconds() / 3600, 1) status_str = latest.get("status", "") - last_backup_status = ( - "ok" if status_str == "STATUS_SUCCESS" - else "error" if status_str == "STATUS_ERROR" - else "unknown" - ) - except Exception as exc: - logger.warning("backrest fetch_status GetOperations failed: %s", exc) + if status_str == "STATUS_SUCCESS": + last_backup_status = "ok" + elif status_str == "STATUS_ERROR": + last_backup_status = "error" + else: + last_backup_status = "unknown" return BackrestSnapshot( source_status="online",