feat: replace tar+DB+LND backup with ext4+rsync workflow

- Rewrite sovran-hub-backup.sh: rsync-based mirror to stable current/
  path, ext4 validation, no tar/pg_dump/mariadb-dump/LND orchestration,
  exit-24 nonfatal for /home, INCOMPLETE/BACKUP_COMPLETE markers, flock
- Update server.py: _is_supported_backup_fstype accepts only ext4
- Update support.js: require ext4, explain database limitations, update
  failure message from exFAT to ext4
- Update sovran-hub.nix: replace pkgs.gnutar with pkgs.rsync + pkgs.acl
- Rewrite test_manual_backup_workflow.py: 40 new tests covering rsync
  options, ext4 acceptance, exit-24 handling, no-delete, stable current/
  path, INCOMPLETE markers, behavioral rsync tests, and regressions
This commit is contained in:
copilot-swe-agent[bot]
2026-07-20 22:45:04 +00:00
committed by GitHub
parent 01db44f0d2
commit 0fa804a430
5 changed files with 797 additions and 1114 deletions
+7 -32
View File
@@ -1494,38 +1494,13 @@ def _is_internal_mount(mnt: str) -> bool:
def _is_supported_backup_fstype(path: str, fstype: str) -> bool:
"""Return whether the target filesystem type is supported for manual backup."""
"""Return whether the target filesystem type is supported for manual backup.
Manual Backup requires ext4 for Linux metadata preservation (ACLs, xattrs,
hard links). exFAT, FAT32, NTFS, and other filesystems are not supported.
"""
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
return fstype == "ext4"
def _detect_external_drives() -> list[dict]:
@@ -3799,7 +3774,7 @@ async def api_backup_run(target: str = ""):
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.",
detail=f"Selected drive filesystem '{selected_fstype}' is not supported for manual backup. Manual Backup requires an ext4-formatted drive.",
)
# Clear stale log before starting