From 36187c0504b608e3f2c5d7d404b61ab27abc727f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:00:32 +0000 Subject: [PATCH] Refine backup validation and manifest details --- .../scripts/sovran-hub-backup.sh | 3 +- app/sovran_systemsos_web/server.py | 37 ++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/app/sovran_systemsos_web/scripts/sovran-hub-backup.sh b/app/sovran_systemsos_web/scripts/sovran-hub-backup.sh index 9d2a2b9..c0c1525 100755 --- a/app/sovran_systemsos_web/scripts/sovran-hub-backup.sh +++ b/app/sovran_systemsos_web/scripts/sovran-hub-backup.sh @@ -355,6 +355,7 @@ if [[ "$LND_AVAILABLE" -eq 1 ]]; then fi ESTIMATED_BYTES=$(( ETC_NIXOS_BYTES + HOME_BYTES + SECRETS_BYTES + VAR_LIB_BYTES + LND_BYTES )) +# Require 20% growth headroom plus an additional fixed 1 GiB safety margin. REQUIRED_BYTES=$(( ESTIMATED_BYTES + (ESTIMATED_BYTES / 5) + SAFETY_MARGIN_BYTES )) FREE_BYTES=$(df -B1 --output=avail "$TARGET" | tail -1 | tr -d ' ') @@ -716,8 +717,6 @@ CHECKSUM_FILE="$BACKUP_DIR/SHA256SUMS.txt" find "$BACKUP_DIR" -mindepth 1 -maxdepth 2 -type f | sort } > "$MANIFEST_FILE" -ARCHIVE_FILES+=("BACKUP_MANIFEST.txt") - # ── Generate checksums for all backup artifacts ───────────────── log "Generating SHA-256 checksums …" diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index 7faa16f..c8cd4e8 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -1493,6 +1493,41 @@ def _is_internal_mount(mnt: str) -> bool: return False +def _is_supported_backup_fstype(path: str, fstype: str) -> bool: + """Return whether the target filesystem type is supported for manual backup.""" + fstype = (fstype or "").lower() + if fstype == "exfat": + return True + if fstype != "fuseblk": + return False + + src_dev = "" + try: + result = subprocess.run( + ["findmnt", "-n", "-o", "SOURCE", "-T", path], + capture_output=True, text=True, timeout=5, + ) + if result.returncode == 0: + src_dev = result.stdout.strip() + except Exception: + src_dev = "" + + if not src_dev: + return False + + for cmd in ( + ["lsblk", "-no", "FSTYPE", src_dev], + ["blkid", "-o", "value", "-s", "TYPE", src_dev], + ): + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=5) + if result.returncode == 0 and result.stdout.strip().lower() in {"exfat", "fuseblk"}: + return True + except Exception: + continue + return False + + def _detect_external_drives() -> list[dict]: """Scan for mounted external USB drives. @@ -3746,7 +3781,7 @@ async def api_backup_run(target: str = ""): selected_target = selected.get("path", "") selected_fstype = (selected.get("fstype") or "").lower() - if selected_fstype and selected_fstype not in {"exfat", "fuseblk"}: + if selected_fstype and not _is_supported_backup_fstype(selected_target, selected_fstype): raise HTTPException( status_code=400, detail=f"Selected drive filesystem '{selected_fstype}' is not supported for manual backup.",