fix: Solve space issues in VG→Borg backups

Problem: VG→Borg was creating temp files for all LVs simultaneously,
causing 'no space left' errors when LVs are large.

Solution: Process LVs sequentially instead of simultaneously:
1. Create snapshot for one LV
2. Stream it directly to Borg (no temp files)
3. Remove snapshot immediately
4. Move to next LV

Changes:
- VG→Borg now creates separate archives per LV instead of one big archive
- Each LV gets its own archive: vg_internal-vg_lv_root_20241009_123456
- No temporary files needed - direct streaming
- Space-efficient: only one snapshot exists at a time
- More robust: failure of one LV doesn't affect others

Benefits:
- No more space issues regardless of LV sizes
- Faster cleanup between LVs
- Individual LV recovery possible
- Better error isolation
- Still preserves block-level backup benefits
This commit is contained in:
root
2025-10-09 00:51:15 +02:00
parent aee3d5019c
commit c86fee78cb
4 changed files with 64 additions and 73 deletions

View File

@@ -592,47 +592,41 @@ class SimpleBackupGUI:
self.log(f"Found {len(lv_names)} logical volumes: {', '.join(lv_names)}")
# Create one archive with all LVs
archive_name = f"vg_{source_vg}_{time.strftime('%Y%m%d_%H%M%S')}"
self.log(f"Creating Borg archive (block-level): {archive_name}")
# Create temporary directory for organizing the LV images
import tempfile
temp_dir = tempfile.mkdtemp(prefix="borg_vg_backup_")
snapshots_created = []
try:
# Create snapshots for all LVs
# Process each LV one by one to avoid space issues
for lv_name in lv_names:
snapshot_name = f"{lv_name}_borg_snap"
snapshot_path = f"/dev/{source_vg}/{snapshot_name}"
lv_path = f"/dev/{source_vg}/{lv_name}"
self.log(f"Processing LV: {lv_name}")
self.log(f"Creating snapshot: {snapshot_name}")
success, output = self.run_command(f"lvcreate -L500M -s -n {snapshot_name} {lv_path}")
if not success:
raise Exception(f"Failed to create snapshot for {lv_name}: {output}")
self.log(f"Warning: Failed to create snapshot for {lv_name}: {output}")
continue
snapshots_created.append((snapshot_path, lv_name))
# Create Borg archive with all block devices
archive_name = f"vg_{source_vg}_{time.strftime('%Y%m%d_%H%M%S')}"
self.log(f"Creating Borg archive (block-level): {archive_name}")
self.log("Backing up all LV snapshots as raw block devices...")
# Create temporary directory structure for the archive
import tempfile
temp_dir = tempfile.mkdtemp(prefix="borg_vg_backup_")
try:
# Copy all snapshots to temporary files that Borg can backup
for snapshot_path, lv_name in snapshots_created:
temp_file = os.path.join(temp_dir, f"{lv_name}.img")
self.log(f"Creating temporary image file for {lv_name}")
# Use dd to copy snapshot to temporary file
copy_cmd = f"dd if={snapshot_path} of={temp_file} bs=4M"
success, output = self.run_command(copy_cmd)
if not success:
raise Exception(f"Failed to create temp file for {lv_name}: {output}")
snapshots_created.append(snapshot_path)
# Create Borg backup of all the block device images
borg_cmd = f"borg create --progress --stats {repo_path}::{archive_name} {temp_dir}"
# Stream this LV directly to Borg using append mode (if supported)
# or create individual archives
archive_name_lv = f"vg_{source_vg}_lv_{lv_name}_{time.strftime('%Y%m%d_%H%M%S')}"
self.log(f"Backing up {lv_name} to archive: {archive_name_lv}")
# Run borg with environment
# Use stdin mode to stream the block device
borg_cmd = f"dd if={snapshot_path} bs=4M | borg create --stdin-name '{lv_name}.img' --progress --stats {repo_path}::{archive_name_lv} -"
# Run command with Borg environment
process = subprocess.Popen(borg_cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True, env=borg_env)
@@ -643,23 +637,33 @@ class SimpleBackupGUI:
process.wait()
if process.returncode != 0:
raise Exception("Borg VG backup failed")
self.log(f"Warning: Backup of {lv_name} failed")
else:
self.log(f"Successfully backed up {lv_name}")
self.log("Block-level VG Borg backup completed successfully")
finally:
# Remove temporary directory
import shutil
try:
shutil.rmtree(temp_dir)
self.log("Temporary files cleaned up")
except:
self.log(f"Warning: Could not remove temp directory {temp_dir}")
# Clean up this snapshot immediately to save space
self.log(f"Removing snapshot {snapshot_path}")
success, output = self.run_command(f"lvremove -f {snapshot_path}")
if success:
snapshots_created.remove(snapshot_path)
else:
self.log(f"Warning: Failed to remove snapshot {snapshot_path}: {output}")
self.log("Block-level VG Borg backup completed successfully")
self.log(f"Created individual archives for each LV in VG {source_vg}")
finally:
# Remove all snapshots
for snapshot_path, lv_name in snapshots_created:
self.log(f"Removing snapshot {snapshot_path}")
# Remove temporary directory
import shutil
try:
shutil.rmtree(temp_dir)
self.log("Temporary directory cleaned up")
except:
self.log(f"Warning: Could not remove temp directory {temp_dir}")
# Remove any remaining snapshots
for snapshot_path in snapshots_created:
self.log(f"Removing remaining snapshot {snapshot_path}")
success, output = self.run_command(f"lvremove -f {snapshot_path}")
if not success:
self.log(f"Warning: Failed to remove snapshot {snapshot_path}: {output}")