3131import argparse
3232import subprocess
3333import shutil
34+ import tarfile
3435from datetime import datetime , timedelta
3536from pathlib import Path
3637from typing import Dict , List , Optional , Tuple
@@ -182,6 +183,31 @@ def update_backup_time(self, vm_id: str, backup_type: str):
182183 logger .debug (
183184 f"Updated { backup_type } backup timestamp for VM { vm_id } to { datetime .fromtimestamp (current_time )} " )
184185
186+ def _backup_vm_configs (self , vm_dir : Path , backup_dir : Path ) -> bool :
187+ """Backup VM configuration files as tar.gz"""
188+ vm_configs = ["vm-manifest.json" , "shared/" ]
189+ config_archive = backup_dir / "vm-configs.tar.gz"
190+
191+ logger .info (f"Creating VM config backup: { config_archive } " )
192+
193+ try :
194+ with tarfile .open (config_archive , 'w:gz' ) as tar :
195+ for config_item in vm_configs :
196+ config_path = vm_dir / config_item
197+ if config_path .exists ():
198+ # Add to archive with relative path
199+ tar .add (config_path , arcname = config_item )
200+ logger .debug (f"Added { config_item } to config archive" )
201+ else :
202+ logger .warning (
203+ f"Config item { config_item } not found, skipping" )
204+
205+ logger .info (f"VM config backup completed: { config_archive } " )
206+ return True
207+ except Exception as e :
208+ logger .error (f"Failed to create VM config backup: { e } " )
209+ return False
210+
185211 def perform_backup (self , vm_id : str , vm_name : str , backup_type : str , hd : str ) -> bool :
186212 """Perform a backup for the specified VM"""
187213 logger .info (f"Performing { backup_type } backup..." )
@@ -198,13 +224,16 @@ def perform_backup(self, vm_id: str, vm_name: str, backup_type: str, hd: str) ->
198224 # Set backup level based on type
199225 backup_level = "full" if backup_type == "full" else "inc"
200226
227+ vm_configs = ["vm-manifest.json" , "shared/" ]
228+
201229 # Create or update latest symlink
202230 latest_dir = backup_dir / "latest"
203231
204232 def do_backup ():
205233 # For full backups, clear bitmaps first
206234 if backup_level == "full" :
207- logger .info (f"Clearing bitmaps for full backup of VM { vm_name } " )
235+ logger .info (
236+ f"Clearing bitmaps for full backup of VM { vm_name } " )
208237 if qmp_socket .exists ():
209238 try :
210239 # Use absolute path for qmp_socket
@@ -259,7 +288,13 @@ def do_backup():
259288 # Get return code
260289 returncode = process .wait ()
261290 if returncode == 0 :
262- logger .info (f"Backup successful" )
291+ logger .info (f"Disk backup successful" )
292+
293+ # Backup VM configuration files
294+ if not self ._backup_vm_configs (vm_dir , abs_latest_dir ):
295+ logger .error ("VM config backup failed" )
296+ return False
297+
263298 self .update_backup_time (vm_id , backup_type )
264299
265300 # Rotate backups if needed
@@ -385,7 +420,8 @@ def run(self):
385420 hd = vm ['hd' ]
386421
387422 logger .info ("-" * 50 )
388- logger .info (f"[{ i + 1 } /{ total_vms } ] Processing VM: { vm_name } ({ vm_id } )" )
423+ logger .info (
424+ f"[{ i + 1 } /{ total_vms } ] Processing VM: { vm_name } ({ vm_id } )" )
389425
390426 # Check if backup is needed
391427 backup_type = self .needs_backup (vm_id )
0 commit comments