diff --git a/backend/app/api/backups.py b/backend/app/api/backups.py index a587ba0..149101e 100644 --- a/backend/app/api/backups.py +++ b/backend/app/api/backups.py @@ -1281,8 +1281,19 @@ def _try_key_candidate(candidate: str | None) -> str | None: detail="Encryption key file (.key) not found on disk or remote storage.", ) - # Decrypt to temp file - temp_fd, temp_name = tempfile.mkstemp(suffix=".tar.gz") + # Decrypt to temp file — derive suffix from the original archive name + # so _get_tar_mode picks the correct open mode (.tar vs .tar.gz etc.). + original_name = os.path.basename(backup.file_path or "") + if original_name.endswith(".enc"): + original_name = original_name[: -len(".enc")] + # Extract suffix(es) like .tar.gz, .tar, .tar.zst etc. + if ".tar." in original_name: + tar_suffix = original_name[original_name.index(".tar") :] + elif original_name.endswith(".tar"): + tar_suffix = ".tar" + else: + tar_suffix = ".tar.gz" # safe fallback + temp_fd, temp_name = tempfile.mkstemp(suffix=tar_suffix) os.close(temp_fd) temp_path = Path(temp_name) @@ -1309,17 +1320,22 @@ def _try_key_candidate(candidate: str | None) -> str | None: def _get_tar_mode(archive_path: str) -> str: - """Determine tarfile open mode from file extension.""" + """Determine tarfile open mode from file extension, with auto-detect fallback.""" if archive_path.endswith(".tar.gz") or archive_path.endswith(".tgz"): return "r:gz" elif archive_path.endswith(".tar.bz2"): return "r:bz2" elif archive_path.endswith(".tar.xz"): return "r:xz" + elif archive_path.endswith(".tar.zst") or archive_path.endswith(".tar.zstd"): + # zstd is not natively supported by tarfile; fall through to raw tar + # after external decompression, or handle separately + return "r:" elif archive_path.endswith(".tar"): return "r:" else: - raise HTTPException(status_code=400, detail="Unsupported archive format") + # Auto-detect: let tarfile figure out the compression + return "r:*" class BrowseEncryptedRequest(BaseModel): diff --git a/backend/app/encryption.py b/backend/app/encryption.py index e0247a8..9da9e47 100644 --- a/backend/app/encryption.py +++ b/backend/app/encryption.py @@ -453,18 +453,28 @@ async def list_backup_contents( Returns list of files with name, size, and type. """ - # Create temp file for decrypted backup - with tempfile.NamedTemporaryFile(suffix=".tar.gz", delete=False) as f: + # Derive temp suffix from original archive name (strip .enc) + original_name = encrypted_path.name + if original_name.endswith(".enc"): + original_name = original_name[: -len(".enc")] + if ".tar." in original_name: + tar_suffix = original_name[original_name.index(".tar") :] + elif original_name.endswith(".tar"): + tar_suffix = ".tar" + else: + tar_suffix = ".tar.gz" + + with tempfile.NamedTemporaryFile(suffix=tar_suffix, delete=False) as f: temp_path = Path(f.name) try: # Decrypt to temp await decrypt_backup(encrypted_path, key_path, private_key, temp_path) - # List tar contents + # List tar contents — use -tf (no gzip flag) and let tar auto-detect process = await asyncio.create_subprocess_exec( "tar", - "-tzf", + "-tf", str(temp_path), stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE,