#!/bin/bash show_help() { cat << end_of_help USAGE: $(basename $0) [-h | --help] [--run-schedule] [--create-new] [domain1 domain2...] $(basename $0) -- Runs virtnbdbackup to create/update backup chains of domains to LOCAL_BACKUP_PATH, from either: - A given list of domains passed as arguments, - All domains currently scheduled for backups, if '--run-schedule' is passed as the only argument, or - Creates new backup chains if '--create-new ' is passed as first argument, and then all domains as further arguments (forcing retention policy appliance in the process) end_of_help } # Load common functions: source /usr/local/bin/vm-functions case $1 in -h|--help) # Show help and exit: show_help exit ;; --create-new) # Creates new backup chains of VMs in need of it (passed as arguments), # during container start or when a new VM is detected, appending/exporting VMs to their correspondent lists, # depending on the result: backup_action="Initial" # Forces to read from the 2nd argument, where the (first) VM is expected to be declared: shift # Virtnbdbackup mode is set to 'full': backup_mode="full" # Populates the backups list from VMs passed as (remaining) arguments: backups_list=($@) # This string is appended to some Unraid messages: unraid_logcheck_message=". Check container logs, or $(basename $LOGFILE_PATH) for details." ;; --run-schedule) # Performs backups from SCHEDULED_BACKUPS_LIST when called via cron, # updating/exporting correspondent lists, depending on the results (included deleted VMs): backup_action="Scheduled" # Processes initial backups list from SCHEDULED_BACKUPS_LIST in external variables file: source $external_vars backups_list=($SCHEDULED_BACKUPS_LIST) # Virtnbdbackup mode is set to 'auto': backup_mode="auto" # This string is appended to some Unraid messages: unraid_logcheck_message=". Check $(basename $SCHEDULE_LOGFILE_PATH) for details." # On Scheduled mode, force '--no-color' for more clarity in log files: [[ -z $(grep -o -e "--nocolor" <<< "$VIRTNBDBACKUP_ARGS") ]] \ && VIRTNBDBACKUP_ARGS="$VIRTNBDBACKUP_ARGS --nocolor" # Display initial message to be appended into log file: screen_header "#" echo "$backup_action Backup started at: $(date "+%Y-%m-%d %H:%M:%S") ($(cat /etc/timezone))" screen_header "#" ;; *) # Without flags, the script performs 'on-demand' backups of the VMs passed as arguments. No lists are updated or exported. # Is mostly for test/debugging purposes, so use with care: # TODO: Add user confirmation action. backup_action="On-demand" # Virtnbdbackup mode is set to 'auto': backup_mode="auto" # Processes initial backups list from VMs (as arguments): backups_list=($@) ;; esac if [[ -n ${backups_list[@]} ]]; then # Notify via Unraid about the start of a scheduled process: [[ $(os_is_unraid) == true ]] \ && unraid_notify "normal" "VM-Babysitter" "$backup_action Backup" "In progress for domain(s): ${backups_list[@]}. Avoid to stop/restart this container until further notice" for domain in ${backups_list[@]}; do screen_header "-" if [[ $(domain_exists $domain) == yes ]]; then # When backup action is to create a new backup chain, force the application of retention policy, prior to perform the backup operation: [[ $backup_action == "Initial" ]] && force_retention_policy="--force" vm-retention-policy $force_retention_policy $domain if [[ ! -d $LOCAL_BACKUP_PATH/$domain || -z $(ls -A $LOCAL_BACKUP_PATH/$domain) ]] \ && [[ $(domain_state $domain) == "shut off" && -z $(grep -o -e "-S" -e "--start-domain" <<< "$VIRTNBDBACKUP_ARGS") ]]; then # Under specific scenario of new backup chain and powered off VMs, # force --start-server to turn on the VM in paused mode: start_domain="--start-domain" elif [[ $(domain_state $domain) == running ]]; then echo "$domain: Checking QEMU guest agent..." agent_is_connected=$(domain_agent_connected $domain $VM_WAIT_TIME) [[ $agent_is_connected == true ]] \ && echo "$domain: Success" \ || echo "WARNING: Timed out awaiting for domain '$domain' QEMU guest agent. Backup may not be concurrent (not installed onto guest OS?)" fi # Perform the backup operation: echo "" virtnbdbackup $start_domain -d $domain -l $backup_mode -o $LOCAL_BACKUP_PATH/$domain $VIRTNBDBACKUP_ARGS # Get last status for virtnbdbackup: virtnbdbackup_exit_code=$? if [[ $virtnbdbackup_exit_code -eq 0 ]]; then # Backup chain creation was successful! backup_success_list+=($domain) else # Failed to create a new backup chain: backup_failed_list+=($domain) echo "WARNING: $backup_action Backup of $domain finished with errors (Virtnbdbackup exit code: $virtnbdbackup_exit_code)" fi else # Create a separated list of 'lost' VMs, and prompt about the unexpected error backup_skipped_list+=($domain) echo "WARNING: There is no such domain '$domain' to backup" fi done if [[ -n $RSYNC_BACKUP_PATH && -z $RSYNC_SCHEDULE && -n ${backup_success_list[@]} ]]; then # When Rsync is enabled and a schedule was not set, activate rsync mode: on_demand_rsync=true for domain in ${backup_success_list[@]}; do screen_header "-" # Attempts to transfer changes via rsync: vm-rsync $domain # Get last status for rsync: rsync_exit_code=$? if [[ $rsync_exit_code -eq 0 ]]; then # Rsync exited normally: rsync_success_list+=($domain) else # Rsync failed: rsync_failed_list+=($domain) fi done fi case $backup_action in "On-demand") # Display that the script has finished: echo -e "\n$(basename $0): Finished\n" ;; *) # All other backup scenarios: # Reload SCHEDULED_BACKUPS_LIST and FAILED_VMS_LIST again, since other VMs could have been processed during this run: source $external_vars # Grab current SCHEDULED_BACKUPS_LIST as array: new_schedule_backups_list=($SCHEDULED_BACKUPS_LIST) for domain in ${backup_success_list[@]}; do # Add any successfully backed up VM into SCHEDULED_BACKUPS_LIST that is not currently present into this list: [[ -z $(item_position $domain "new_schedule_backups_list") ]] && new_schedule_backups_list+=($domain) done for domain in ${backup_failed_list[@]} ${backup_skipped_list[@]}; do failed_domain_index=$(item_position $domain "new_schedule_backups_list") if [[ -n $failed_domain_index ]]; then # Delete each failed or skipped domain found into new_schedule_backups_list: unset new_schedule_backups_list[$failed_domain_index] # Rebuild the array upon each iteration: new_schedule_backups_list=(${new_schedule_backups_list[@]}) fi done # Append failed failed to its correspondent list to be exported: FAILED_VMS_LIST+=" ${backup_failed_list[@]}" # And update SCHEDULED_BACKUPS_LIST with changes, if any: SCHEDULED_BACKUPS_LIST=" ${new_schedule_backups_list[@]}" # Export the variables: sed -i \ -e "s/FAILED_VMS_LIST=.*/FAILED_VMS_LIST=\"$FAILED_VMS_LIST\"/" \ -e "s/SCHEDULED_BACKUPS_LIST=.*/SCHEDULED_BACKUPS_LIST=\"$SCHEDULED_BACKUPS_LIST\"/" \ $external_vars # Show a final summary: screen_header "-" echo -e "$backup_action Backup Summary:\n" echo "Successful backups: ${backup_success_list[@]:-"None"}" echo "Failed backups: ${backup_failed_list[@]:-"None"}" echo "Missing VMs: ${backup_skipped_list[@]:-"None"}" if [[ $on_demand_rsync == true ]]; then # Only show remote stats when remote endppoint is set: screen_header "-" echo -e "$backup_action Rsync Summary:\n" echo "Successful syncs: ${rsync_success_list[@]:-"None"}" echo "Failed syncs: ${rsync_failed_list[@]:-"None"}" fi if [[ $(os_is_unraid) == true ]]; then # Notifies via Unraid about the results: if [[ -z ${backup_failed_list[@]} && -z ${rsync_failed_list[@]} && -z ${backup_skipped_list[@]} ]]; then unraid_notify "normal" "VM-Babysitter" "$backup_action Backup" "Finished Successfully" else [[ -n ${backup_skipped_list[@]} ]] \ && unraid_notify "warning" "VM-Babysitter" "$backup_action Backup" "Unable to find domain(s): ${backup_skipped_list[@]} and therefore removed from schedule (maybe deleted by the user?)" [[ -n ${backup_failed_list[@]} ]] \ && unraid_notify "warning" "VM-Babysitter" "$backup_action Backup finished with errors" "Could not backup domain(s): ${backup_failed_list[@]}$unraid_logcheck_message" [[ -n ${rsync_failed_list[@]} ]] \ && unraid_notify "warning" "VM-Babysitter" "$backup_action Rsync finished with errors" "Could not sync backup of domain(s): ${rsync_failed_list[@]}$unraid_logcheck_message" fi fi # Append the ending date and time into log file: echo -e "\n$backup_action Backup ended at: $(date "+%Y-%m-%d %H:%M:%S") ($(cat /etc/timezone))\n" ;; esac else echo -e "No domain(s) for $backup_action Backup to process\n\nUSAGE: $(basename $0) [--run-schedule] [--create-new] [domain1 domain2...]\n" fi