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 1); 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=$2)
local MINUTES0=$3)
# 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=$4)
local MINUTES1=$5)
# Log disk space after
local space_after=$(du -sb “${VM_DIR}/${vm}” | cut -f1)
local space_saved=$6)
# 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: $7) MB (Before: $8) MB, After: $9) 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 10); 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)”