feat: Improve encrypted backups and resize logic

This commit introduces several major improvements to the handling of encrypted incremental backups.

1.  **Fix LUKS Container Resize:**
    The `encryption_helper.sh` script has been fixed to reliably resize LUKS containers. The `cryptsetup resize` command now correctly re-authenticates, resolving an issue where the script would fail because it was incorrectly trying to read a password from an empty stdin stream.

2.  **Refactor Backup Directory Structure:**
    The storage path for encrypted user backups has been flattened. Backups are now stored directly in the encrypted mount point (e.g., `/backup/pybackup/encrypted_videos/BACKUP_NAME`) instead of a deeply nested folder (`.../user/SOURCE_NAME/BACKUP_NAME`). This simplifies the directory structure as requested.

3.  **Correct Incremental Size Estimation:**
    The `estimate_incremental_size` function is now more robust.
    - The `rsync` command for user backups now correctly uses a trailing slash on the source path.
    - This ensures the `--link-dest` comparison works as intended against the new, flat directory structure, leading to an accurate calculation of the required incremental size.

4.  **Refine Container Resize Trigger and Logic:**
    The logic for automatically resizing encrypted containers in `encryption_manager.py` has been completely overhauled to prevent excessive growth:
    - A resize is now only triggered if the projected free space after a backup would fall below a 4 GB buffer.
    - The calculation for the new size now correctly adds the required space plus the 4 GB buffer, ensuring sufficient space without over-provisioning.
This commit is contained in:
2025-09-14 14:23:12 +02:00
parent 7d64544c37
commit ff640ca9ef

View File

@@ -88,7 +88,11 @@ class BackupManager:
if is_system:
return os.path.join(base_data_dir, "system")
# For user backups, store them directly in the mount point if encrypted.
elif is_encrypted:
return base_data_dir
else:
# Keep old structure for unencrypted user backups
return os.path.join(base_data_dir, "user", source_name)
def check_for_full_backup(self, dest_path: str, source_name: str, is_encrypted: bool) -> bool:
@@ -201,7 +205,9 @@ class BackupManager:
full_system_cmd = f"mkdir -p '{rsync_dest}' && {rsync_cmd_str}"
command = ['pkexec', 'bash', '-c', full_system_cmd]
else:
rsync_command_parts.extend([source_path, rsync_dest])
# Add a trailing slash to the source path to ensure rsync copies the content, not the directory itself.
source_with_slash = source_path.rstrip('/') + '/'
rsync_command_parts.extend([source_with_slash, rsync_dest])
os.makedirs(rsync_dest, exist_ok=True)
command = rsync_command_parts
@@ -273,7 +279,7 @@ class BackupManager:
if is_system:
command.extend(['pkexec', 'rsync', '-aAXHvn', '--stats'])
else:
command.extend(['rsync', '-avn', '--stats'])
command.extend(['rsync', '-ain', '--dry-run', '--stats'])
command.append(f"--link-dest={latest_backup_path}")
@@ -283,6 +289,9 @@ class BackupManager:
try:
with tempfile.TemporaryDirectory() as dummy_dest:
# For user backups, add a trailing slash to copy contents, not the dir itself.
if not is_system:
source_path = source_path.rstrip('/') + '/'
command.extend([source_path, dummy_dest])
self.logger.log(