#!/bin/bash : Usage: zfs-backup [filesystem] [destination_pool] tmpdir="/export/home/tmp" lockdir="/tmp/zfs-admin-lock" if ! mkdir "$lockdir" >/dev/null 2>&1; then echo >&2 "ZFS admin lock directory is present." exit 1 fi cleanup() { rm -rf "$lockdir"; } trap cleanup EXIT fs=$1 sourcepool=${1%%/*} fsname=${1#*/} destinationpool=$2 if ! zpool list -H "$sourcepool" >/dev/null 2>&1; then echo >&2 "The source pool, '$sourcepool' doesn't seem to exist." exit 1 fi if ! zpool list -H "$destinationpool" >/dev/null 2>&1; then echo >&2 "The destination pool, '$destinationpool' doesn't seem to exist." exit 1 fi if ! zfs list -H "$fs" >/dev/null 2>&1; then echo >&2 "The source filesytem, '$fs' doesn't seem to exist." exit 1 fi printsnaps() { sed 's,.*/,,' | awk '{print $1}' } zfs list -rH -t snapshot $sourcepool/$fsname | printsnaps > /tmp/source-list zfs list -rH -t snapshot $destinationpool/$fsname | printsnaps > /tmp/destination-list diff -u /tmp/source-list /tmp/destination-list | grep -v '^+++' | awk '/^\+/ {print}' | sed "s,^\+,$destinationpool/," > /tmp/obsolete-snapshots rm -f /tmp/source-list /tmp/destination-list echo >&2 "Removing obsolete backups from the destination pool" for snapshot in $(cat /tmp/obsolete-snapshots); do echo >&2 "Removing '$snapshot' from destination." zfs destroy "$snapshot" done echo >&2 "Rolling back to the most recent snapshot on the destination." zfs rollback $(zfs list -rH -t snapshot $destinationpool/$fsname | awk '{snap=$1} END {print snap}') echo >&2 "Calculating the most recent common snapshot between the two filesystems." common="" if zfs list -H "$destinationpool/$fsname" >/dev/null 2>&1; then for snap in $(zfs list -rH -t snapshot "$destinationpool/$fsname" | sed 's,.*@,,' | awk '{print$1}'); do if zfs list -rH -t snapshot "$fs" | sed 's,.*@,,' | awk '{print$1}' | grep "^${snap}$" >/dev/null 2>&1; then common=$snap fi done fi base=$common foundcommon=false if [ -z "$common" ]; then foundcommon=true fi for snap in $(zfs list -rH -t snapshot "$fs" | sed 's,.*@,,' | awk '{print$1}'); do if [ "$snap" = "$common" ]; then foundcommon=true continue fi if $foundcommon; then if [ -z "$base" ]; then echo >&2 "Sending '$1/$snap'" zfs send "$1@$snap" > $tmpdir/zfs.part mv $tmpdir/zfs.part $tmpdir/zfs.data zfs recv "$destinationpool/$fsname" < $tmpdir/zfs.data rm -f $tmpdir/zfs.data zfs set readonly=on "$destinationpool" zfs set atime=off "$destinationpool" zfs set sharenfs=off "$destinationpool" zfs set mountpoint=legacy "$destinationpool" zfs unmount "$destinationpool/$fsname" zfs rollback "$destinationpool/$fsname@$snap" else echo >&2 "Sending '$1@$base' -> '$1/$snap'" zfs send -i "$1@$base" "$1@$snap" > $tmpdir/zfs.part mv $tmpdir/zfs.part $tmpdir/zfs.data zfs recv "$destinationpool/$fsname" < $tmpdir/zfs.data rm -f $tmpdir/zfs.data # zfs unmount "$destinationpool/$fsname" # zfs rollback "$destinationpool/$fsname@$snap" fi base=$snap fi done true