#!/bin/bash show_help() { cat << end_of_help USAGE: [LOCAL_BACKUP_PATH=...] $(basename $0) [ -h | --help ] [-s | --source ] $(basename $0) -- Restores backups of virtual domains created with virtnbdbackup, by using virtnbdrestore REQUIREMENTS: - Virtnbdbackup suite - Qemu/KVM (in most cases) INSTRUCTIONS: - When running from container it will take env var 'LOCAL_BACKUP_PATH' as main path for backups. Use -s or --source to override and use a different path - Choose a source backup to restore, at a specific checkpoint when available - The script will look for existing domains and will try to find the 'best match' possible, showing all possible restoration options, including to restore as new domain - In similar way, it will show all possible options about which image disks to restore and where; including to custom or auto-generated path, whenever possible - During restoration, it will save apart any existing disk images matching name and path with last modification timestamp - Upon restoration success, it will ask for deleting such old images (user can delete them manually later) or will revert it back if restoration fails. NOTES: - For advanced usage and scenarios not covered by this script (e.g. custom restoration of several disk images, etc.) use virtnbdrestore tool directly, instead - It is advised to ensure that domains to be restored from backups are shut off (this script will anyway checks and asks for permission to do so, automatically) - Restored domains into vm-babysitter scheduled backups require a new backup chain. This will occur at next backup schedule, or during service start (whichever happens first) end_of_help } # ############################################################################# # Main execution: # ############################################################################# # Load functions: source /usr/local/bin/vm-functions # ----------------------------------------------------------------------------- # Read user parameters: case $1 in '') # When no arguments are given, main_backups_path is LOCAL_BACKUP_PATH, or '/backups' in last instance [[ -n $LOCAL_BACKUP_PATH ]] \ && main_backups_path=$LOCAL_BACKUP_PATH \ || main_backups_path="/backups" ;; -s|--source) # User provides a custom location for main_backups_path case $2 in [/]*) # Verify the given argument is an existing folder: [[ -d $2 ]] \ && main_backups_path="$2" \ || { echo -e "ERROR: Path '$2' was not found. Provide a valid path to scan for backups\n"; vm_restore_exit_code=1; } ;; *) # Is not an absolute path: echo -e "ERROR: '$2' is not an absolute path. Provide a valid path to scan for backups\n" vm_restore_exit_code=1 ;; esac ;; -h|--help) # Show help: show_help vm_restore_exit_code=0 ;; *) # Unknown option: echo -e "Unknown argument '$1' Usage: $(basename $0) [ -h | --help ] [-s | --source ]\n" vm_restore_exit_code=1 ;; esac if [[ -z $vm_restore_exit_code ]]; then # No exit code means the input parameters are correct. for folder in $(find $main_backups_path -mindepth 1 -maxdepth 1 -type d | sort -u); do # Get a list of file extensions in folder: extensions_list="$(list_extensions $folder)" # Extensions 'data' and 'xml' are proof it's a virtnbdbackup type: [[ -n $(grep -x data <<< $extensions_list) && -n $(grep -x xml <<< $extensions_list) ]] \ && src_backups_list+=($folder) done if [[ -n ${src_backups_list[@]} ]]; then # ----------------------------------------------------------------------------- # Show all restorable backups: # ----------------------------------------------------------------------------- echo -e "\nRetrieving info from backups at folder '$main_backups_path'..." screen_header "-" printf "%3s %-19s %15s %-19s %-19s %s\n" "#" "Backup of" "Checkpoints" "From date ($(date +%Z))" "To date ($(date +%Z))" "Source directory" screen_header "-" for ((i=0; i<${#src_backups_list[@]}; i++)); do # Get domain names, # of checkpoints and times of each checkpoint: src_domains_list+=("$(backup_domain_info --name ${src_backups_list[$i]})") src_checkpoints_num_list+=("$(backup_checkpoints_num ${src_backups_list[$i]})") src_all_dates_list+=("$(backup_times_list ${src_backups_list[$i]})") # Also get first and last checkpoint dates, human readable: first_saved_date=$( date -d @$(echo "${src_all_dates_list[$i]}" | cut -d ' ' -f1) "+%Y-%m-%d %H:%M:%S") last_saved_date=$(date -d @$(echo "${src_all_dates_list[$i]}" | rev | cut -d ' ' -f1 | rev) "+%Y-%m-%d %H:%M:%S") # Show the line with backup information: printf "%3s %-31s %3s %-19s %-19s %s\n" "$(($i + 1))" "${src_domains_list[$i]}" "${src_checkpoints_num_list[$i]}" "$first_saved_date" "$last_saved_date" "$(basename ${src_backups_list[$i]})" done # ----------------------------------------------------------------------------- # Ask the user to choose a backup to restore: # ----------------------------------------------------------------------------- echo "" user_choice=$(($(choose_number "Choose a backup to restore" "1" "${#src_backups_list[@]}") - 1)) # Get all chosen backup metadata, from gathered above and directly from functions: src_domain=${src_domains_list[$user_choice]} src_checkpoints_num=${src_checkpoints_num_list[$user_choice]} src_path=${src_backups_list[$user_choice]} src_dates_list=(${src_all_dates_list[$user_choice]}) # ----------------------------------------------------------------------------- # Show multiple checkpoints, if any: # ----------------------------------------------------------------------------- if [[ $src_checkpoints_num -ge 2 ]]; then # Get specific list of checkpoints in src_path: src_checkpoints_list=($(backup_checkpoints_list $src_path)) # Header to show when there are incremental backups: echo -e "\nRetrieving checkpoints list from backup of domain '$src_domain' at sub-folder '$(basename $src_path)'..." screen_header "-" printf "%3s %-18s %s\n" "#" "Checkpoint Name" "Saved at ($(date +%Z)):" screen_header "-" for ((i=0; i<${#src_checkpoints_list[@]}; i++)); do # Prints on screen detailed (and tabulated) data for the given checkpoint: printf "%3s %-18s %s\n" "$(($i + 1))" "${src_checkpoints_list[$i]}" "$(date -d @$(echo ${src_dates_list[$i]}))" done # ----------------------------------------------------------------------------- # Ask the user to choose a specific checkpoint to restore: # ----------------------------------------------------------------------------- echo "" user_choice=$(($(choose_number "Choose a specific point in time to restore (press ENTER to select the latest one) :" "1" "$src_checkpoints_num" "$src_checkpoints_num") - 1)) if [[ $user_choice -ne $src_checkpoints_num ]]; then # user_choice is different than the default option: chosen_checkpoint="${src_checkpoints_list[$user_choice]}" # Set virtnbdrestore optional argument to retore until this checkpoint only: # ----------------------------------------------------------------------------- until_checkpoint_option="--until $chosen_checkpoint" fi fi # Gets drive(s) and disk path(s) into backup to be restored (at chosen checkpoint, if defined): src_id=$(backup_domain_info --id $src_path) src_drives_list=($(backup_restorable_list --drives $src_path $chosen_checkpoint)) src_imgpaths_list=($(backup_restorable_list --paths $src_path $chosen_checkpoint)) [[ -n $chosen_checkpoint ]] && chosen_checkpoint_message=" (at checkpoint '$chosen_checkpoint')" echo -e "\nBackup of domain '$src_domain' at directory '$src_path'$chosen_checkpoint_message selected for restoration\n" # ----------------------------------------------------------------------------- # When applies, compare backup info (name, id, drives, paths, etc.) with existing domains: # ----------------------------------------------------------------------------- echo -e "Analyzing all possible courses of action..." # Get the (ordered) list of existing domains: existing_domains_list=($(domains_list | sort -u)) for domain in ${existing_domains_list[@]}; do # (Re) initilize variables: dest_domain_matches="" # Detect if a domain with same name exists (there can be only one): [[ $src_domain == $domain ]] && dest_domain_exists=true # Detect if any domain with same uuid exists (there can be only one): [[ $src_id == $(domain_id $domain) ]] && { dest_id_exists=true; dest_id_domain=$domain; } # Detect which domain(s) have total or partial match(es) with image path(s): imgpaths_list=($(domain_img_paths_list $domain)) drives_list=($(domain_drives_list $domain)) if [[ "${src_imgpaths_list[@]}" == "${imgpaths_list[@]}" && "${src_drives_list[@]}" == "${drives_list[@]}" ]]; then # Domain has exactly the same drives and image paths as in chosen backup. # Add domain to list to display with high priority: full_path_match_domains_list+=($domain) # Add domain and metadata to matching lists at the end of this iteration: dest_domain_matches=true else # No exact matches by path. # Attempt to find partial matches first: for ((i=0; i<${#src_imgpaths_list[@]}; i++)); do if [[ ${src_imgpaths_list[$i]} == ${imgpaths_list[$i]} && ${src_drives_list[$i]} == ${drives_list[$i]} ]]; then # At least one match found. Add domain to list to display with mid priority: partial_path_match_domains_list+=($domain) # Add domain and metadata to matching lists at the end of this iteration: dest_domain_matches=true # Interrupt the search: break fi done fi if [[ $dest_domain_matches == true ]]; then # Add metadata for this domain if matches in some way: matching_domains_list+=($domain) matching_drives_list+=("${drives_list[*]}") matching_imgpaths_list+=("${imgpaths_list[*]}") fi # ----------------------------------------------------------------------------- # Add existing image paths to a global list (used for further comparation): existing_imgpaths_list+=("${imgpaths_list[*]}") done # Fill all unset boolean variables about existing metadata to false: [[ -z $dest_domain_exists ]] && dest_domain_exists=false [[ -z $dest_id_exists ]] && dest_id_exists=false [[ $dest_domain_exists == false && $dest_id_exists == false && -z ${matching_domains_list[@]} ]] && no_matching_domains=true # ----------------------------------------------------------------------------- # Show available actions about how to restore this backup: # ----------------------------------------------------------------------------- screen_header "-" echo -e "Available actions to perform with backup of domain '$src_domain'" screen_header "-" # Number of options present in the below dialog: menu_option=1 # Applies to all scenarios (default option): echo -e "$menu_option. Total or partial restoration with a custom domain name" if [[ $no_matching_domains == true ]]; then # Applies when no existing domain, uuid or image path(s), match with chosen backup: ((menu_option++)) echo -e "$menu_option. Total or partial restoration as new domain '$src_domain' (no matches found)" # When no matches, set as default option instead: default_option=$menu_option else # It's a match! for domain in ${full_path_match_domains_list[@]}; do # Applies when at least, all image path(s) coincide (showing which elements match with backup): ((menu_option++)) if [[ $dest_domain_exists == true && $src_domain == $dest_id_domain ]]; then matching_message="name, uuid &" # When full match, set as default option instead: default_option=$menu_option elif [[ $dest_domain_exists == true && $dest_id_exists == false ]]; then matching_message="name &" elif [[ $dest_domain_exists == false && $dest_id_exists == true ]]; then matching_message="uuid &" fi echo -e "$menu_option. Total or partial restoration onto existing domain '$domain' ($matching_message all disk images match)" done for domain in ${partial_path_match_domains_list[@]}; do # Applies when at least, one image path coincide (showing which elements match with backup): ((menu_option++)) if [[ $dest_domain_exists == true && $src_domain == $dest_id_domain ]]; then matching_message="name, uuid &" elif [[ $dest_domain_exists == true && $dest_id_exists == false ]]; then matching_message="name &" elif [[ $dest_domain_exists == false && $dest_id_exists == true ]]; then matching_message="uuid &" fi echo -e "$menu_option. Partial or custom restoration onto existing domain '$domain' ($matching_message some disk images match)" done fi echo "" user_choice=$(choose_number "Choose an action" "1" "$menu_option" "$default_option") if [[ $user_choice -eq 1 ]]; then # ----------------------------------------------------------------------------- # Ask the user for a name for the new domain dest_domain: # ----------------------------------------------------------------------------- read -p "Insert a name for the new domain, which will be created from backup of domain '$src_domain': " dest_domain # TODO: Add Qemu/KVM domain name constrains: while [[ $dest_domain == $(grep -o -e "$dest_domain" <<< "${existing_domains_list[@]}") ]]; do # Ask for a different name if the domain already exists on the list read -p "Domain '$dest_domain' already exists or is invalid. Choose a different name: " dest_domain done # Domain name was chosen by the user domain_is_custom=true else # Domain name was chosen by available options: domain_is_custom=false fi if [[ -z ${matching_domains_list[@]} ]]; then # ----------------------------------------------------------------------------- # No matches. dest_domain is same src_domain: # ----------------------------------------------------------------------------- # Gets dest_domain (when not already set), drives and paths from source metadata: [[ -z $dest_domain ]] && dest_domain=$src_domain possible_drives_list=(${src_drives_list[@]}) possible_imgpaths_list=(${src_imgpaths_list[@]}) else # ----------------------------------------------------------------------------- # Total or partial match(es). Choose the domain from array matching_domains_list, # at user_choice minus minimal possible option (2): # ----------------------------------------------------------------------------- user_choice=$(($user_choice - 2)) # Gets dest_domain (when not already set), drives and paths from matching metadata: [[ -z $dest_domain ]] && dest_domain=${matching_domains_list[$user_choice]} possible_drives_list=(${matching_drives_list[$user_choice]}) possible_imgpaths_list=(${matching_imgpaths_list[$user_choice]}) fi # Unique possible destination paths (existing and auto-generated) and disk image names (without paths) are needed for the last menu: for path in ${possible_imgpaths_list[@]}; do # This result determines if automatic path is possible or not: [[ -n $(grep -o -e "${path//$src_domain/$dest_domain}" <<< "${existing_imgpaths_list[@]}" ) ]] \ && autopath_is_possible=false # Get the unique dir paths available to restore: [[ -z $(grep -o -e "$(dirname $path)" <<< ${possible_unique_paths_list[@]}) ]] \ && possible_unique_paths_list+=($(dirname $path)) # And the list of disk images, without paths: possible_imgs_list+=($(basename $path)) done if [[ $domain_is_custom == true || $no_matching_domains == true ]]; then # Domain needs to be defined: # ----------------------------------------------------------------------------- domain_definition_needed=true if [[ $autopath_is_possible != false ]]; then # Automatic path for non-defined domain is possible, since disk images to be restored don't match with any other defined domain: autopath_is_possible=true # Generate proposed_unique_paths_list from domain name substitutions based upon possible_unique_paths_list: for path in ${possible_unique_paths_list[@]}; do [[ $path != ${path//$src_domain/$dest_domain} \ && -z $(grep -o -e "${path//$src_domain/$dest_domain}" <<< "${proposed_unique_paths_list[@]}" ) ]] \ && proposed_unique_paths_list+=(${path//$src_domain/$dest_domain}) done fi # Custom message: restoration_message="restored and defined as a new" else # Domain exists. # Custom message: restoration_message="restored onto the already existing" fi echo -e "\nBackup of domain '$src_domain' selected to be $restoration_message domain with name: '$dest_domain'\n" # ----------------------------------------------------------------------------- # Show available options about specific drive(s) and path(s) how to restore this backup: # ----------------------------------------------------------------------------- echo -e "Retrieving info about restorable drives and disk image paths..." screen_header "-" printf "%5s %s\n" "Drive" "Current destination path" screen_header "-" for ((i=0; i<${#possible_drives_list[@]}; i++)); do # List all drives and respective disk image files: printf "%5s %s\n" "${possible_drives_list[$i]}" "${possible_imgpaths_list[$i]}" done echo -e "\nAvailable actions for restorable device(s):" screen_header "-" # Number of options present in the below dialog: menu_option=0 # Also reset default_option: default_option="" if [[ $domain_definition_needed == true ]]; then # Custom path options: # ----------------------------------------------------------------------------- ((menu_option++)) echo "$menu_option. Restore '${possible_imgs_list[@]}' to a custom path" # Save values for further selection: selectable_dest_path_list+=("") # Intentionally blank for being defined by user. selectable_drives_list+=("${possible_drives_list[*]}") selectable_imgs_list+=("${possible_imgs_list[*]}") if [[ ${#possible_drives_list[@]} -gt 1 ]]; then # When multiple drives, show custom path options for individual restoration: for ((i=0; i<${#possible_drives_list[@]}; i++)); do ((menu_option++)) echo "$menu_option. Only restore '${possible_imgs_list[$i]}' to a custom path" # Save values for further selection: selectable_dest_path_list+=("") # Intentionally blank for being defined by user. selectable_drives_list+=(${possible_drives_list[$i]}) selectable_imgs_list+=(${possible_imgs_list[$i]}) done fi # Auto-generated path options: # ----------------------------------------------------------------------------- for ((i=0; i<${#proposed_unique_paths_list[@]}; i++)); do ((menu_option++)) echo "$menu_option. Restore '${possible_imgs_list[@]}' automatically onto path '${proposed_unique_paths_list[$i]}'" # When an automatic path has been generated, set the first option as default: [[ -z $default_option ]] && default_option=$menu_option # Save values for further selection: selectable_dest_path_list+=(${proposed_unique_paths_list[$i]}) selectable_drives_list+=("${possible_drives_list[*]}") selectable_imgs_list+=("${possible_imgs_list[*]}") if [[ ${#possible_drives_list[@]} -gt 1 ]]; then # When multiple drives, show defined options for individual restoration: for ((j=0; j<${#possible_drives_list[@]}; j++)); do ((menu_option++)) echo "$menu_option. Only restore '${possible_imgs_list[$j]}' automatically onto path '${proposed_unique_paths_list[$i]}'" # Save values for further selection: selectable_dest_path_list+=(${proposed_unique_paths_list[$i]}) selectable_drives_list+=(${possible_drives_list[$j]}) selectable_imgs_list+=(${possible_imgs_list[$j]}) done fi done fi # Defined path(s) options: # ----------------------------------------------------------------------------- for ((i=0; i<${#possible_unique_paths_list[@]}; i++)); do ((menu_option++)) echo "$menu_option. Restore '${possible_imgs_list[@]}' onto existing path '${possible_unique_paths_list[$i]}'" # Save values for further selection: selectable_dest_path_list+=(${possible_unique_paths_list[$i]}) selectable_drives_list+=("${possible_drives_list[*]}") selectable_imgs_list+=("${possible_imgs_list[*]}") # When no default_optionhas been set, first path concurrence option for all devices becomes the default option: [[ -z $default_option ]] && default_option=$menu_option if [[ ${#possible_drives_list[@]} -gt 1 ]]; then # When multiple drives, show defined options for individual restoration: for ((j=0; j<${#possible_drives_list[@]}; j++)); do ((menu_option++)) echo "$menu_option. Only restore '${possible_imgs_list[$j]}' onto existing path '${possible_unique_paths_list[$i]}'" # Save values for further selection: selectable_dest_path_list+=(${possible_unique_paths_list[$i]}) selectable_drives_list+=(${possible_drives_list[$j]}) selectable_imgs_list+=(${possible_imgs_list[$j]}) done fi done echo "" user_choice=$(($(choose_number "Choose an action" "1" "$menu_option" "$default_option") - 1)) # Set dest_imgs_list from user choice: # ----------------------------------------------------------------------------- dest_drives_list=(${selectable_drives_list[$user_choice]}) if [[ ${#src_dates_list[@]} -gt ${#dest_drives_list[@]} && ${#dest_drives_list[@]} -eq 1 ]]; then # Restoration is either partial (one drive only) # ----------------------------------------------------------------------------- restoration_is_partial=true elif [[ ${#src_drives_list[@]} -gt ${#possible_drives_list[@]} ]]; then # Drives at backup are more than in destination: # ----------------------------------------------------------------------------- restoration_has_more_drives=true fi # Set dest_imgs_list from user choice: # ----------------------------------------------------------------------------- dest_imgs_list=(${selectable_imgs_list[$user_choice]}) if [[ -z ${selectable_dest_path_list[$user_choice]} ]]; then # ----------------------------------------------------------------------------- # Ask the user for a custom path for drives: # ----------------------------------------------------------------------------- custom_path_message="Insert an absolute path to restore '${dest_imgs_list[*]}': " until [[ $dest_path_is_custom == true ]]; do # Begin the iteration with the last message set: read -p "$custom_path_message: " dest_path if [[ -z $dest_path ]]; then custom_path_message="Path '$dest_path' is empty. Insert a valid path" elif [[ $dest_path != /* ]]; then custom_path_message="Path '$dest_path' is relative. Insert an absolute path" else for image in ${dest_imgs_list[@]}; do [[ -n $(grep -o -e "$dest_path/$image" <<< ${existing_imgpaths_list[@]} ) ]] \ && { existing_image=$image; break; } \ || existing_image="" done if [[ -n $existing_image ]]; then custom_path_message="Disk image '$existing_image' is already defined by another domain at '$dest_path'. Please choose a different path" else # A correct dest_path was defined by the user. This ends the iteration: # ----------------------------------------------------------------------------- dest_path_is_custom=true fi fi done else # Set dest_path from user choice: # ----------------------------------------------------------------------------- dest_path=${selectable_dest_path_list[$user_choice]} fi # Determine if dest_path has changed for at least one disk image: # ----------------------------------------------------------------------------- for ((i=0; i<${#src_imgpaths_list[@]}; i++)); do if [[ ${src_imgpaths_list[$i]} != $dest_path/${dest_imgs_list[$i]} ]]; then # At least one destination path is different from backup: # ----------------------------------------------------------------------------- dest_path_has_changed=true # Stop searching: break fi done echo -e "\nDisk image(s) '${dest_imgs_list[@]}' selected to be restored onto path '$dest_path'\n" # ----------------------------------------------------------------------------- # Properly set virtnbdrestore remaining optional arguments, based on results: # ----------------------------------------------------------------------------- # Set to only restore the selected drive: # ----------------------------------------------------------------------------- [[ $restoration_is_partial == true ]] && drive_option="--disk ${dest_drives_list[@]}" if [[ $domain_definition_needed == true ]]; then # Set to define the domain (with the user selected name): # ----------------------------------------------------------------------------- define_domain_option="--define" domain_name_option="--name $dest_domain" # Set to adjust config new paths of disk images into config (as well deleting current uuid and raw devices): # ----------------------------------------------------------------------------- [[ $dest_path_is_custom || $dest_path_has_changed == true ]] \ && adjust_config_option="--adjust-config" fi # ----------------------------------------------------------------------------- # TODO: Show summary: # ----------------------------------------------------------------------------- if [[ $dest_domain == $(grep -o -e "$dest_domain" <<< "${existing_domains_list[@]}") ]]; then # When dest_domain exists, get the state of such domain: dest_domain_state=$(domain_state $dest_domain) # Notify in advance when dest_domain is not shut off: [[ $dest_domain_state != "shut off" ]] \ && start_restoration_message="Domain '$dest_domain' is currently $dest_domain_state. Are you agree with shut down this domain automatically to start the restoration process?" fi [[ -z $start_restoration_message ]] \ && start_restoration_message="Everything is ready to begin! Are you agree to start with the restoration process?" # ----------------------------------------------------------------------------- # Ask the user to start or not the restoration process: # ----------------------------------------------------------------------------- screen_header "=" start_restoration=$(yes_no "$start_restoration_message" "no") if [[ $start_restoration == yes ]]; then if [[ -n $dest_domain_state && $dest_domain_state != "shut off" ]]; then # Shutdown existing dest_domain that is not shut off: # ----------------------------------------------------------------------------- domain_shutdown $dest_domain --wait $WAIT_TIME domain_shutdown_exit_code=$? echo "" fi if [[ -z $domain_shutdown_exit_code || $domain_shutdown_exit_code -eq 0 ]]; then # Look for existing dest_imgs_list in dest_path # ----------------------------------------------------------------------------- for ((i=0; i<${#dest_imgs_list[@]}; i++)); do if [[ -f $dest_path/${dest_imgs_list[$i]} ]]; then # If found, rename with timestamp, and save the full path: # ----------------------------------------------------------------------------- saved_imgpaths_list+=($dest_path/${dest_imgs_list[$i]}.$(last_modified_time $dest_path/${dest_imgs_list[$i]})) mv $dest_path/${dest_imgs_list[$i]} ${saved_imgpaths_list[$i]} echo -e "INFO: Existing disk image file '${dest_imgs_list[$i]}' at path '$dest_path' renamed to '$(basename ${saved_imgpaths_list[$i]})'" fi done # ----------------------------------------------------------------------------- # Run virtnbdrestore, with all arguments gathered during the process: # ----------------------------------------------------------------------------- screen_header "-" virtnbdrestore --input $src_path --output $dest_path $until_checkpoint_option $drive_option $adjust_config_option $define_domain_option $domain_name_option screen_header "-" virtnbdrestore_exit_code=$? if [[ $virtnbdrestore_exit_code -eq 0 ]]; then # Success. if [[ $domain_definition_needed != true ]]; then # Existing domains require to be pruned of any existing checkpoints metadata: echo -e "Pruning any existing checkpoints metadata from domain '$dest_domain'..." domain_delete_checkpoints $dest_domain --all --metadata # On certain scenarios, user is notified about to perform adjustments in order to complete the restoration process: [[ $dest_path_has_changed == true || $restoration_has_more_drives ]] \ && final_message="\n(Check domain '$dest_domain' settings and perform all necessary adjustments to reflect restored disk images [${dest_imgs_list[@]}] at path '$dest_path')" fi echo -e "Backup restoration of domain '$dest_domain' ended successfully!$final_message\n" if [[ -n ${saved_imgpaths_list[@]} ]]; then delete_leftovers=$(yes_no "Delete disk images that were found and renamed prior to the restoration from '$dest_path'?: " "no") [[ $delete_leftovers == yes ]] && rm -fv ${saved_imgpaths_list[@]} fi # Delete virtnbdrestore definitions left at dest_path: [[ -f $dest_path/vmconfig.xml ]] && rm -f $dest_path/vmconfig.xml # Return success status: vm_restore_exit_code=0 else # It Failed! echo -e "ERROR: Backup restoration of domain '$dest_domain' failed! (virtnbdrestore exit code: $virtnbdrestore_exit_code)\n" # Undo any renaming of existing disk images at $dest_path: for ((i=0; i<${saved_imgpaths_list[@]}; i++)); do mv -f ${saved_imgpaths_list[$i]} $dest_path/${dest_imgs_list[$i]} echo "INFO: Reverted '$(basename ${saved_imgpaths_list[$i]})' back to '${dest_imgs_list[$i]}'" done # Delete virtnbdrestore definitions left at dest_path: [[ -f $dest_path/vmconfig.xml ]] && rm -f $dest_path/vmconfig.xml vm_restore_exit_code=1 fi else echo -e "ERROR: Could not safely start the restoration process, because domain '$dest_domain' current state ($(domain_state $dest_domain)) disallows it!\n" fi else echo -e "\n$(basename $0): Restoration cancelled by user.\n" vm_restore_exit_code=2 fi else vm_restore_exit_code=1 echo -e "ERROR: No backups found at path '$main_backups_path'\n\nUSAGE: [LOCAL_BACKUP_PATH=...] $(basename $0) [ -h | --help ] [-s | --source ]\n" fi fi # End program with resulting exit code: exit $vm_restore_exit_code