Skip to content

Instantly share code, notes, and snippets.

@jpawlowski
Last active November 6, 2024 11:48
Show Gist options
  • Save jpawlowski/c8181d4c456563c569ca239ecbd8d143 to your computer and use it in GitHub Desktop.
Save jpawlowski/c8181d4c456563c569ca239ecbd8d143 to your computer and use it in GitHub Desktop.
Proxmox Hourly Snapshot Rotation Script: This Bash script automates the creation of hourly snapshots for running Proxmox VMs, while automatically deleting older snapshots to maintain a manageable rotation. It uses Proxmox CLI commands (pct and qm) without requiring additional libraries, making it lightweight and efficient. The script includes op…
#!/bin/bash
# Variables
DATE=$(date +"%Y%m%d%H")
DEFAULT_KEEP=24 # Default number of snapshots to keep
DEFAULT_RETAIN_DAYS=7 # Default days to keep LVM archive files
DRY_RUN=false
QUIET=false
KEEP=$DEFAULT_KEEP
RETAIN_DAYS=$DEFAULT_RETAIN_DAYS
# Help function
function show_help {
echo "Usage: $0 [-n | --dry-run for dry-run mode] [-q | --quiet to suppress output] [-k | --keep <number> to set snapshots to keep]"
echo " [--retain-days <number> to set days to keep LVM archives] [-h | --help for help]"
exit 0
}
# Parse parameters
while [[ "$#" -gt 0 ]]; do
case $1 in
-n|--dry-run) DRY_RUN=true ;;
-q|--quiet) QUIET=true ;;
-k|--keep) KEEP="$2"; shift ;;
--retain-days) RETAIN_DAYS="$2"; shift ;;
-h|--help) show_help ;;
-*|--*) echo "Unrecognized option: $1"; show_help ;;
esac
shift
done
# Function to delete old LVM archive files
delete_old_lvm_archives() {
local archive_path="/etc/lvm/archive"
if [ -d "$archive_path" ]; then
$QUIET || echo "Cleaning LVM archive files older than $RETAIN_DAYS days..."
if [ "$DRY_RUN" = true ]; then
find "$archive_path" -type f -mtime +$RETAIN_DAYS -print
else
find "$archive_path" -type f -mtime +$RETAIN_DAYS -exec rm {} +
fi
fi
}
# Apply archive cleanup before snapshot management
delete_old_lvm_archives
# Main snapshot management logic
$QUIET || echo "Fetching VM list from pvesh..."
pvesh get /cluster/resources --type vm --output-format text --human-readable 0 --noborder --noheader | \
awk '{split($1, a, "/"); TYPE=a[1]; VMID=a[2]; STATUS=$(NF-2); print TYPE, VMID, STATUS}' | \
while read -r TYPE VMID STATUS; do
# Skip VMs that are not running
if [ "$STATUS" != "running" ]; then
$QUIET || echo "Skipping VM $VMID ($TYPE) - Status: $STATUS"
continue
fi
# Set the snapshot command based on VM type
if [ "$TYPE" == "lxc" ]; then
SNAP_CMD="pct"
elif [ "$TYPE" == "qemu" ]; then
SNAP_CMD="qm"
else
continue
fi
# Create snapshot in dry-run mode or execute normally
if [ "$DRY_RUN" = true ]; then
$QUIET || echo "[Dry-Run] Would create snapshot: $SNAP_CMD snapshot $VMID auto_$DATE"
else
$QUIET || echo "Creating snapshot: $SNAP_CMD snapshot $VMID auto_$DATE"
$SNAP_CMD snapshot $VMID auto_$DATE
fi
# Retrieve snapshots and delete oldest if exceeding the keep limit
SNAPSHOTS=($($SNAP_CMD listsnapshot $VMID | grep -oP 'auto_\d{10}' | sort -r)) # Sort newest first
SNAP_COUNT=${#SNAPSHOTS[@]}
if [ "$SNAP_COUNT" -gt "$KEEP" ]; then
for (( i=KEEP; i<SNAP_COUNT; i++ )); do
OLD_SNAP="${SNAPSHOTS[$i]}"
if [ "$DRY_RUN" = true ]; then
$QUIET || echo "[Dry-Run] Would delete snapshot: $SNAP_CMD delsnapshot $VMID $OLD_SNAP"
else
$QUIET || echo "Deleting snapshot: $SNAP_CMD delsnapshot $VMID $OLD_SNAP"
$SNAP_CMD delsnapshot $VMID $OLD_SNAP
fi
done
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment