#!/usr/bin/env bash set -u # no unset vars ######################## # Configuration ######################## SECONDS_WAIT=60 DESTINATION_DIR="/mnt/unprotected/tmp/VM/backup/corellia" VMHOME="/vm/ssd0/" # base dir for VM data; adapt as needed DAYS_TO_KEEP_VM_BACKUP=60 MAIL_TO="vplesnila@gmail.com" MAIL_FROM="plesnila.valeriu@orange.fr" MAIL_SUBJECT_OK="VM backup success" MAIL_SUBJECT_FAIL="VM backup failure" LOG_DIR="/root/tmp" LOGFILE="${LOG_DIR}/vm_backup_$(date +%F_%H%M%S).log" ######################## # Logging setup ######################## mkdir -p "$LOG_DIR" "$DESTINATION_DIR" # Log everything to file and stdout exec > >(tee -a "$LOGFILE") 2>&1 echo "===== VM backup started at $(date) =====" FAILED=0 fail() { echo "ERROR: $*" >&2 FAILED=1 } send_mail() { local subject="$1" { echo "From: ${MAIL_FROM}" echo "To: ${MAIL_TO}" echo "Subject: ${subject}" echo cat "$LOGFILE" } | msmtp "$MAIL_TO" } ######################## # Helper functions ######################## list_all_vms() { virsh list --all --name | sed '/^$/d' } list_running_vms() { virsh list --name | sed '/^$/d' } wait_and_check_offline() { local wait_seconds="$1" echo "Waiting ${wait_seconds}s..." sleep "$wait_seconds" local still_running still_running=$(list_running_vms || true) if [[ -n "$still_running" ]]; then echo "Still running VMs:" echo "$still_running" return 1 fi echo "All VMs are offline." return 0 } backup_vm() { local vm="$1" local date_str date_str="$(date +%F)" local backup_file="${DESTINATION_DIR}/${vm}_${date_str}.tar.gz" local xml_file="${DESTINATION_DIR}/${vm}_${date_str}.xml" echo "----- Backing up VM: ${vm} -----" # 1. Export XML definition echo "Exporting XML definition to ${xml_file}" if ! virsh dumpxml "$vm" > "$xml_file"; then fail "Failed to dump XML for VM '$vm'" return 1 fi # 2. Backup VM storage if [[ ! -d "$VMHOME" ]]; then fail "VMHOME directory '$VMHOME' does not exist for VM '$vm'" return 1 fi cd "$VMHOME" || { fail "Cannot cd to $VMHOME for VM '$vm'"; return 1; } echo "Creating compressed archive: $backup_file" if ! tar -cvf - ${vm} | pigz > "$backup_file"; then fail "Backup failed for VM '$vm'" return 1 fi # 3. Cleanup old backups echo "Cleaning old backups for VM '$vm' older than ${DAYS_TO_KEEP_VM_BACKUP} days" if ! find "$DESTINATION_DIR" -maxdepth 1 -type f \ \( -name "${vm}_*.tar.gz" -o -name "${vm}_*.xml" \) \ -mtime +"$DAYS_TO_KEEP_VM_BACKUP" -print -delete; then fail "Failed to clean old backups for VM '$vm'" return 1 fi echo "Backup completed for VM '$vm'" return 0 } start_all_vms() { echo "===== Starting ALL defined VMs =====" local all_vms all_vms=$(virsh list --all --name | sed '/^$/d') if [[ -z "$all_vms" ]]; then echo "No VMs defined on this host; nothing to start." return 0 fi while read -r vm; do [[ -z "$vm" ]] && continue echo "Starting VM: $vm" if ! virsh start "$vm"; then fail "Failed to start VM '$vm'" fi done <<< "$all_vms" echo "===== VM startup phase completed =====" } ######################## # Main logic ######################## ALL_VMS=$(list_all_vms) if [[ -z "$ALL_VMS" ]]; then echo "No VMs defined on this host." fi echo "All VMs:" echo "$ALL_VMS" RUNNING_VMS=$(list_running_vms) echo "Running VMs:" echo "${RUNNING_VMS:-}" # Graceful shutdown if [[ -n "$RUNNING_VMS" ]]; then echo "Shutting down VMs gracefully..." while read -r vm; do [[ -z "$vm" ]] && continue echo "virsh shutdown $vm" if ! virsh shutdown "$vm"; then fail "Graceful shutdown failed for VM '$vm'" fi done <<< "$RUNNING_VMS" else echo "No running VMs to shut down gracefully." fi # Wait and check if ! wait_and_check_offline "$SECONDS_WAIT"; then echo "Some VMs still running after graceful shutdown; forcing shutdown..." STILL_RUNNING=$(list_running_vms) while read -r vm; do [[ -z "$vm" ]] && continue echo "virsh destroy $vm" if ! virsh destroy "$vm"; then fail "Force shutdown failed for VM '$vm'" fi done <<< "$STILL_RUNNING" fi # If any failure so far, send failure mail and exit if [[ "$FAILED" -ne 0 ]]; then echo "Failures detected before backup phase." send_mail "$MAIL_SUBJECT_FAIL" echo "===== VM backup finished with FAILURE at $(date) =====" exit 1 fi # Backup each VM for vm in $ALL_VMS; do [[ -z "$vm" ]] && continue backup_vm "$vm" || FAILED=1 done echo "Starting all VMs..." start_all_vms # Final status and mail if [[ "$FAILED" -ne 0 ]]; then echo "One or more VM backups failed." send_mail "$MAIL_SUBJECT_FAIL" echo "===== VM backup finished with FAILURE at $(date) =====" exit 1 else echo "All VM backups completed successfully." send_mail "$MAIL_SUBJECT_OK" echo "===== VM backup finished with SUCCESS at $(date) =====" exit 0 fi