mirror of
https://github.com/bashclub/bashclub-zfs-push-pull.git
synced 2024-12-24 13:40:13 +01:00
cf353fafd4
Previously we looked for the specific tagged snapshot, but that doesn't actually work properly. Now simply find the most recent snapshot on dest and compare to src. It must exist on src.
152 lines
3.6 KiB
Bash
Executable File
152 lines
3.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# backup-zfs: use zfs send/recv to push/pull snapshots
|
|
prog="$(basename "$0")"
|
|
|
|
usage() {
|
|
cat >&2 <<-EOF
|
|
usage: $prog [-hvq] [-t tag] [-k keep] [-d dateopts] src dest
|
|
use zfs send/recv to push/pull snapshots
|
|
|
|
src the source fs, specified as [host:]pool/path/to/fs
|
|
dest the destination fs parent, specified as [host:]pool/path/to/fs
|
|
(the final path component of src will be appended to dest)
|
|
|
|
-h help
|
|
-v verbose mode
|
|
-q quiet mode
|
|
-t tag tag to use for naming snapshots (default: backup-zfs)
|
|
-k keep number of snapshots to keep on src (default: 5)
|
|
-d dateopts options for date(1) - used to name the snapshots (default: +%F_%T)
|
|
EOF
|
|
exit $1
|
|
}
|
|
|
|
# log to syslog; if verbose or on a tty, also to stdout
|
|
# usage: log msg
|
|
log() {
|
|
logger -t "$prog" -- "$@"
|
|
if ! $quiet && [[ -t 1 ]] || $verbose ; then
|
|
echo "$@" >&2
|
|
fi
|
|
}
|
|
|
|
# exit with a code & message
|
|
# usage: die $exitcode msg
|
|
die() {
|
|
code="$1"
|
|
shift
|
|
if [[ $code -ne 0 ]] ; then
|
|
verbose=true log "FATAL: $@"
|
|
else
|
|
log "$@"
|
|
fi
|
|
exit $code
|
|
}
|
|
|
|
# run zfs(1) command either locally or via ssh
|
|
# usage: ZFS "$host" command args...
|
|
ZFS() {
|
|
host="$1"
|
|
shift
|
|
if [[ -n $host ]] ; then
|
|
log "remote ($host): zfs $@"
|
|
ssh "$host" sudo zfs "$@"
|
|
else
|
|
log "local: zfs $@"
|
|
sudo zfs "$@"
|
|
fi
|
|
}
|
|
|
|
###
|
|
### defaults
|
|
###
|
|
tag="$prog"
|
|
dateopts="+%F_%T"
|
|
keep=5
|
|
verbose=false
|
|
quiet=false
|
|
|
|
###
|
|
### parse options
|
|
###
|
|
while getopts "hvqk:t:d:" opt ; do
|
|
case $opt in
|
|
h) usage 0 ;;
|
|
v) verbose=true ;;
|
|
q) quiet=true ;;
|
|
k) keep=$OPTARG ;;
|
|
t) tag=$OPTARG ;;
|
|
d) dateopts=$OPTARG ;;
|
|
*) usage 1 ;;
|
|
esac
|
|
done
|
|
shift $((OPTIND-1))
|
|
date="$(date $dateopts)"
|
|
|
|
###
|
|
### parse src & dest host/fs info
|
|
###
|
|
# fail if there's ever >1 colon
|
|
if [[ $1 =~ :.*: || $2 =~ :.*: ]] ; then
|
|
die 1 "invalid fsspec: '$1' or '$2'"
|
|
fi
|
|
|
|
# fail if src or dest isn't specified
|
|
if [[ -z $1 || -z $2 ]] ; then
|
|
usage 1
|
|
fi
|
|
src="$1"
|
|
dest="$2"
|
|
|
|
# discard anything before a colon to get the fs
|
|
srcfs="${src#*:}"
|
|
destfs="${dest#*:}"
|
|
|
|
# iff there is a colon, discard everything after it to get the host
|
|
[[ $src =~ : ]] && srchost="${src%:*}"
|
|
[[ $dest =~ : ]] && desthost="${dest%:*}"
|
|
|
|
# get the last src component
|
|
srcbase="${srcfs##*/}"
|
|
|
|
# ensure the destination fs exists before proceeding
|
|
if [[ $(ZFS "$desthost" list -H -o name "$destfs" 2>/dev/null) != $destfs ]] ; then
|
|
die 1 "destination fs '$destfs' doesn't exist"
|
|
fi
|
|
|
|
###
|
|
### create new snapshot on src
|
|
###
|
|
cur="$srcfs@${tag}_$date"
|
|
ZFS "$srchost" snapshot -r "$cur" || die $? "zfs snapshot failed"
|
|
|
|
###
|
|
### get newest snapshot on dest - it must exist on src
|
|
###
|
|
last="$(ZFS "$desthost" list -d 1 -t snapshot -H -S creation -o name $destfs/$srcbase | head -n1 | cut -f2 -d@)"
|
|
|
|
###
|
|
### send & receive
|
|
###
|
|
# 1st time: send full snapshot
|
|
if [[ -z $last ]] ; then
|
|
log "sending full recursive snapshot from $src to $dest"
|
|
ZFS "$srchost" send -R "$cur" | ZFS "$desthost" receive -Fue "$destfs" || die $? "zfs full send failed"
|
|
# special case: tagged snapshots exist on dest, but src has rotated through all
|
|
elif ! ZFS "$srchost" list $srcfs@$last &>/dev/null ; then
|
|
die 1 "no incremental path from from $src to $dest"
|
|
# normal case: send incremental
|
|
else
|
|
log "sending incremental snapshot from $src to $dest (${last#${tag}_}..${cur#*@${tag}_})"
|
|
ZFS "$srchost" send -R -I "$last" "$cur" | ZFS "$desthost" receive -Fue "$destfs" || die $? "zfs incremental send failed"
|
|
fi
|
|
|
|
###
|
|
### clean up old snapshots
|
|
###
|
|
for snap in $(ZFS "$srchost" list -d 1 -t snapshot -H -S creation -o name $srcfs \
|
|
| grep -F "@${tag}_" | cut -f2 -d@ | tail -n+$((keep+1)) ) ;
|
|
do
|
|
ZFS "$srchost" destroy -r $srcfs@$snap
|
|
done
|