Created
June 9, 2025 12:08
-
-
Save KpuCko/b872c8980cb9e17f27ea7bd2dc7c075c to your computer and use it in GitHub Desktop.
Incus Zvol backup script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
``` | |
#!/bin/bash | |
# Custom ZFS snapshot + sync + prune script | |
# By KpuCko (updated by ChatGPT) | |
# CONFIGURATION | |
ZVOLS=( | |
"zpool-nvme-01/.ix-virt/custom/default_vm-100-disk-0" | |
"zpool-nvme-01/.ix-virt/custom/default_vm-100-disk-1" | |
"zpool-nvme-01/.ix-virt/custom/default_vm-101-disk-0" | |
"zpool-nvme-01/.ix-virt/custom/default_vm-106-disk-0" | |
"zpool-nvme-01/.ix-virt/custom/default_vm-109-disk-0" | |
"zpool-nvme-01/.ix-virt/custom/default_vm-111-disk-0" | |
"zpool-nvme-01/.ix-virt/custom/default_vm-111-disk-1" | |
"zpool-nvme-01/.ix-virt/custom/default_vm-117-disk-0" | |
"zpool-nvme-01/.ix-virt/custom/default_vm-117-disk-1" | |
"zpool-nvme-01/.ix-virt/custom/default_vm-118-disk-0" | |
) | |
DEST_POOL="zpool-hdd-01" | |
SNAP_PREFIX="custom" | |
RETENTION=7 | |
# FUNCTIONS | |
log() { | |
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | |
} | |
# MAIN LOOP | |
for ZVOL in "${ZVOLS[@]}"; do | |
# Extract components | |
SRC_POOL="${ZVOL%%/*}" | |
REL_PATH="${ZVOL#*/}" | |
DEST_ZVOL="${DEST_POOL}/${REL_PATH}" | |
SRC="${SRC_POOL}/${REL_PATH}" | |
DST="${DEST_POOL}/${REL_PATH}" | |
# Create snapshot | |
SNAP_NAME="${SNAP_PREFIX}-$(date '+%Y%m%d%H%M%S')" | |
SRC_SNAP="${ZVOL}@${SNAP_NAME}" | |
log "Creating snapshot: $SRC_SNAP" | |
zfs snapshot "$SRC_SNAP" | |
# Ensure destination ZVOL exists | |
if ! zfs list "$DEST_ZVOL" &>/dev/null; then | |
log "Destination ZVOL $DEST_ZVOL does not exist, creating it..." | |
VOLSIZE=$(zfs get -H -o value volsize "$ZVOL") | |
zfs create -V "$VOLSIZE" "$DEST_ZVOL" | |
fi | |
# Find latest common snapshot | |
SRC_SNAPS=$(zfs list -H -t snapshot -o name -s creation | grep "^${ZVOL}@${SNAP_PREFIX}-" | awk -F@ '{print $2}') | |
DST_SNAPS=$(zfs list -H -t snapshot -o name -s creation | grep "^${DEST_ZVOL}@${SNAP_PREFIX}-" | awk -F@ '{print $2}') | |
COMMON_SNAP="" | |
for SNAP in $(echo "$SRC_SNAPS" | tac); do | |
if echo "$DST_SNAPS" | grep -qx "$SNAP"; then | |
COMMON_SNAP="$SNAP" | |
break | |
fi | |
done | |
# Perform send | |
if [[ -n "$COMMON_SNAP" && "$COMMON_SNAP" != "$SNAP_NAME" ]]; then | |
log "Incremental send from snapshot ${ZVOL}@${COMMON_SNAP} to ${ZVOL}@${SNAP_NAME} → destination ${DEST_ZVOL}" | |
zfs send -c -i "${ZVOL}@${COMMON_SNAP}" "${ZVOL}@${SNAP_NAME}" | zfs receive -F "$DEST_ZVOL" | |
else | |
log "No common snapshot found or identical name, performing full send: ${ZVOL}@${SNAP_NAME} → destination ${DEST_ZVOL}" | |
zfs send -c "${ZVOL}@${SNAP_NAME}" | zfs receive -F "$DEST_ZVOL" | |
fi | |
# Prune old snapshots on source | |
log "Pruning old snapshots on source: $SRC" | |
SRC_SNAPS_LIST=$(zfs list -H -t snapshot -o name -s creation | grep "^${SRC}@${SNAP_PREFIX}-") | |
SNAP_COUNT=$(echo "$SRC_SNAPS_LIST" | grep -c .) | |
if [ "$SNAP_COUNT" -gt "$RETENTION" ]; then | |
echo "$SRC_SNAPS_LIST" | head -n -"$RETENTION" | while read -r OLD_SNAP; do | |
log "Destroying old source snapshot: $OLD_SNAP" | |
zfs destroy "$OLD_SNAP" | |
done | |
fi | |
# Prune old snapshots on destination | |
log "Pruning old snapshots on destination: $DST" | |
DST_SNAPS_LIST=$(zfs list -H -t snapshot -o name -s creation | grep "^${DST}@${SNAP_PREFIX}-") | |
SNAP_COUNT=$(echo "$DST_SNAPS_LIST" | grep -c .) | |
if [ "$SNAP_COUNT" -gt "$RETENTION" ]; then | |
echo "$DST_SNAPS_LIST" | head -n -"$RETENTION" | while read -r OLD_SNAP; do | |
log "Destroying old destination snapshot: $OLD_SNAP" | |
zfs destroy "$OLD_SNAP" | |
done | |
fi | |
done | |
log "All done." | |
``` |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment