Skip to content

Instantly share code, notes, and snippets.

@KpuCko
Created June 9, 2025 12:08
Show Gist options
  • Save KpuCko/b872c8980cb9e17f27ea7bd2dc7c075c to your computer and use it in GitHub Desktop.
Save KpuCko/b872c8980cb9e17f27ea7bd2dc7c075c to your computer and use it in GitHub Desktop.
Incus Zvol backup script
```
#!/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