This is an old revision of the document!
``` #!/bin/bash # New production VM backup and pruning script # Runs monthly to maintain VM image sizes
# Configuration BACKUP_DIR=“/mnt/warehouse/backups” VM_DIR=“/mnt/vms/production” TEMP_DIR=“/mnt/vms/temps” LOG_DIR=“/root” ALERT_EMAIL=“alerts@haacksnetworking.org” HOSTNAME=$(hostname -f) RETENTION_DAYS=15 MIN_SPACE_REQUIRED=10737418240 # 10GB in bytes MAX_PARALLEL=2 # Maximum parallel VM processes
# VM images array declare -a VM_IMAGES=(
"hackingclub.org.qcow2" "felinefantasy.club.qcow2"
)
# Check if dry-run mode is enabled DRY_RUN=false if "$1" == "--dry-run"; then
DRY_RUN=true echo "Running in dry-run mode - no changes will be made"
fi
# Function to log and email messages log_and_mail() {
local subject="$1" local message="$2" local log_file="$3" echo "$message" | tee -a "$log_file" if ! $DRY_RUN; then echo "$message" | mail -s "$subject" "$ALERT_EMAIL" fi
}
# Cleanup function for interruptions cleanup() {
echo "Script interrupted - cleaning up temporary files..." rm -f "${TEMP_DIR}"/*.bak "${TEMP_DIR}/trimmed2."* exit 1
}
# Trap interrupts trap cleanup INT TERM
# Check available disk space check_space() {
local dir="$1" local available=$(df --output=avail "$dir" | tail -n1) if (( available * 1024 < MIN_SPACE_REQUIRED )); then return 1 fi return 0
}
# Process VM function process_vm() {
local vm="$1" local TIMESTAMP=$(date +"%Y%m%d-%H:%M:%S") local LOG_FILE="${LOG_DIR}/${vm}-loop-b.log" touch "$LOG_FILE"
# Log disk space before local space_before=$(du -sb "${VM_DIR}/${vm}" | cut -f1)
# Check disk space if ! check_space "$VM_DIR"; then log_and_mail "[vm-sane-bu-${vm}-failed]-${HOSTNAME}-$(date)" \ "Insufficient disk space for ${vm} at $(date)." \ "$LOG_FILE" return 1 fi
if ! $DRY_RUN; then # Shutdown VM if ! virsh shutdown "$vm" 2>/dev/null; then log_and_mail "[vm-sane-bu-${vm}-failed]-${HOSTNAME}-$(date)" \ "Failed to initiate shutdown of ${vm} at $(date)." \ "$LOG_FILE" return 1 fi
# Wait for shutdown sleep 30 if ! tail -n 2 "/var/log/libvirt/qemu/${vm}.log" | grep -q "reason=shutdown"; then log_and_mail "[vm-sane-bu-${vm}-failed]-${HOSTNAME}-$(date)" \ "The ${vm} image failed to shutdown properly at $(date)." \ "$LOG_FILE" return 1 fi fi
# Image conversion process cd "$VM_DIR" || return 1 local START0=$(date +%s) if ! $DRY_RUN; then if ! virt-sparsify --in-place "$vm" || \ ! qemu-img convert -O qcow2 "$vm" "trimmed.$vm" || \ ! mv "$vm" "${TEMP_DIR}/${vm}.bak" || \ ! mv "trimmed.$vm" "$vm" || \ ! virsh start "$vm"; then log_and_mail "[vm-sane-bu-${vm}-failed]-${HOSTNAME}-$(date)" \ "Image conversion failed for ${vm} at $(date)." \ "$LOG_FILE" return 1 fi fi
local END0=$(date +%s) local DURATION0=$((END0 - START0)) local MINUTES0=$((DURATION0 / 60))
# Tarballing process cd "$TEMP_DIR" || return 1 local START1=$(date +%s) if ! $DRY_RUN; then if ! qemu-img convert -O qcow2 "${vm}.bak" "trimmed2.$vm" || \ ! cp -ar --sparse=always "trimmed2.$vm" "${BACKUP_DIR}/replicas/${vm}:${TIMESTAMP}.bak" || \ ! bsdtar --use-compress-program=pbzip2 -Scf "${vm}.bak.tar.bz2" "trimmed2.$vm" || \ ! mv "${vm}.bak.tar.bz2" "${BACKUP_DIR}/tarballs/${vm}:${TIMESTAMP}.sane.bak.tar.bz2"; then log_and_mail "[vm-sane-bu-${vm}-failed]-${HOSTNAME}-$(date)" \ "Tarball creation failed for ${vm} at $(date)." \ "$LOG_FILE" return 1 fi
# Cleanup temporary files rm -f "${vm}.bak" "trimmed2.$vm" fi
local END1=$(date +%s) local DURATION1=$((END1 - START1)) local MINUTES1=$((DURATION1 / 60))
# Log disk space after local space_after=$(du -sb "${VM_DIR}/${vm}" | cut -f1) local space_saved=$((space_before - space_after))
# Prune old backups if ! $DRY_RUN; then find "${BACKUP_DIR}/tarballs/" -type f -mtime "+${RETENTION_DAYS}" -delete find "${BACKUP_DIR}/replicas/" -type f -mtime "+${RETENTION_DAYS}" -delete fi
# Success notification local MESSAGE="$(date) Jonathan, the ${vm} image conversion took ${DURATION0} sec. (${MINUTES0} min) "\
“and the tarballing took ${DURATION1} sec. (${MINUTES1} min). “\ “Space saved: $1) MB (Before: $2) MB, After: $3) MB)”
log_and_mail "[vm-sane-bu-${vm}-success]-${HOSTNAME}-$(date)" \ "$MESSAGE" \ "$LOG_FILE"
rm -f "$LOG_FILE" return 0
}
# Main loop with parallel processing pids=() for vm in “${VM_IMAGES[@]}”; do
# Wait if maximum parallel processes reached while (( ${#pids[@]} >= MAX_PARALLEL )); do wait -n # Wait for any process to finish # Clean up completed PIDs new_pids=() for pid in "${pids[@]}"; do if kill -0 "$pid" 2>/dev/null; then new_pids+=("$pid") fi done pids=("${new_pids[@]}") done
# Process VM in background process_vm "$vm" & pids+=($!)
done
# Wait for all processes to complete wait
echo “All VM processing completed at $(date)” ```