diff --git a/bashclub-zfs b/bashclub-zfs index 78ad736..80aed7f 100644 --- a/bashclub-zfs +++ b/bashclub-zfs @@ -63,216 +63,220 @@ ZFS() { fi } -### -### defaults -### -tag="$prog" -dateopts="+%F_%T" -keep=5 -verbose=false -quiet=false -tossh=false -fromssh=false -port=22 -sshcompress="" -reinit="" -intermediate="-i " -### -### parse options -### -while getopts "hvqk:p:t:d:srCIRg:" opt ; do - case $opt in - h) usage 0 ;; - v) - verbose=true - send_opts="-v" - recv_opts="-v" - ;; - q) quiet=true ;; - k) keep=$OPTARG ;; - p) port=$OPTARG ;; - t) tag=$OPTARG ;; - d) dateopts=$OPTARG ;; - s) tossh=true ;; - r) fromssh=true ;; - C) sshcompress="-C " ;; - R) reinit="-R " ;; - I) intermediate="-I " ;; - g) gpgid="$OPTARG" ;; - *) usage 1 ;; - esac -done -shift $((OPTIND-1)) -date="$(date $dateopts)" -$tossh && $fromssh && die 1 "-s and -r are mutually exclusive" -if ! $tossh && [[ -n $gpgid ]] ; then - die 1 "-g can only be used with -s" -fi +( + flock -n 9 || exit 1 + ### + ### defaults + ### + tag="$prog" + dateopts="+%F_%T" + keep=5 + verbose=false + quiet=false + tossh=false + fromssh=false + port=22 + sshcompress="" + reinit="" + intermediate="-i " + ### + ### parse options + ### + while getopts "hvqk:p:t:d:srCIRg:" opt ; do + case $opt in + h) usage 0 ;; + v) + verbose=true + send_opts="-v" + recv_opts="-v" + ;; + q) quiet=true ;; + k) keep=$OPTARG ;; + p) port=$OPTARG ;; + t) tag=$OPTARG ;; + d) dateopts=$OPTARG ;; + s) tossh=true ;; + r) fromssh=true ;; + C) sshcompress="-C " ;; + R) reinit="-R " ;; + I) intermediate="-I " ;; + g) gpgid="$OPTARG" ;; + *) usage 1 ;; + esac + done + shift $((OPTIND-1)) + date="$(date $dateopts)" + $tossh && $fromssh && die 1 "-s and -r are mutually exclusive" + if ! $tossh && [[ -n $gpgid ]] ; then + die 1 "-g can only be used with -s" + fi -### -### 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 + ### + ### 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" + # fail if src or dest isn't specified + if [[ -z $1 || -z $2 ]] ; then + usage 1 + fi + src="$1" + dest="$2" -### -### ssh mode - output snaps from local fs to ssh or read snaps from ssh to local fs -if $tossh ; then - log "sending from local zfs filesystem to SSH server" + ### + ### ssh mode - output snaps from local fs to ssh or read snaps from ssh to local fs + if $tossh ; then + log "sending from local zfs filesystem to SSH server" - # make sure src exists - if [[ $src =~ : ]] ; then - die 1 "$src must be a local zfs filesystem" - elif [[ $(ZFS "" list -H -o name "$src" 2>/dev/null) != $src ]] ; then - die 1 "$src must be a local zfs filesystem" - fi + # make sure src exists + if [[ $src =~ : ]] ; then + die 1 "$src must be a local zfs filesystem" + elif [[ $(ZFS "" list -H -o name "$src" 2>/dev/null) != $src ]] ; then + die 1 "$src must be a local zfs filesystem" + fi - # split dest to components - if [[ $dest =~ : ]] ; then - desthost="${dest%:*}" - destpath="${dest#*:}" - else - die 1 "$dest must be ssh host:path" - fi + # split dest to components + if [[ $dest =~ : ]] ; then + desthost="${dest%:*}" + destpath="${dest#*:}" + else + die 1 "$dest must be ssh host:path" + fi - # get the last src component - srcbase="${src##*/}" + # get the last src component + srcbase="${src##*/}" - ### - ### create new snapshot on src - ### - snap="${tag}_$date" - cur="$src@$snap" - ZFS "$srchost" snapshot -r "$cur" || die $? "zfs snapshot failed" + ### + ### create new snapshot on src + ### + snap="${tag}_$date" + cur="$src@$snap" + 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@)" - last="$(ssh "$desthost" zfslast)" + ### + ### 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@)" + last="$(ssh "$desthost" zfslast)" - ### - ### send - ### - # refuse to send without a valid .last maker - if [[ -z $last ]] ; then - die 1 "ssh path contains no .last file" - # special case: tagged snapshots exist on dest, but src has rotated through all - elif ! ZFS "$srchost" list $src@$last &>/dev/null ; then - die 1 "no incremental path from from $src to $dest" - # normal case: send incremental - else - log "sending $([[ -n $gpgid ]] && echo "encrypted ")incremental snapshot from $src to $dest (${last#${tag}_}..${cur#*@${tag}_})" - #ZFS "$srchost" send $send_opts -R $intermediate "$last" "$cur" | ZFS "$desthost" receive $recv_opts -Fue "$destfs" || die $? "zfs incremental send failed" - if [[ -n $gpgid ]] ; then - ZFS "$srchost" send $send_opts -R $intermediate "$last" "$cur" \ - | gpg --trust-model always --encrypt --recipient "$gpgid" \ - | ssh "$desthost" zfswrite "${tag}_$date.zfssnap.gpg" \ - || die $? "zfs incremental send failed" - ssh "$desthost" zfslast "$snap" - else - ZFS "$srchost" send $send_opts -R $intermediate "$last" "$cur" \ - | ssh "$desthost" zfswrite "${tag}_$date.zfssnap" \ - || die $? "zfs incremental send failed" - ssh "$desthost" zfslast "$snap" - fi - fi + ### + ### send + ### + # refuse to send without a valid .last maker + if [[ -z $last ]] ; then + die 1 "ssh path contains no .last file" + # special case: tagged snapshots exist on dest, but src has rotated through all + elif ! ZFS "$srchost" list $src@$last &>/dev/null ; then + die 1 "no incremental path from from $src to $dest" + # normal case: send incremental + else + log "sending $([[ -n $gpgid ]] && echo "encrypted ")incremental snapshot from $src to $dest (${last#${tag}_}..${cur#*@${tag}_})" + #ZFS "$srchost" send $send_opts -R $intermediate "$last" "$cur" | ZFS "$desthost" receive $recv_opts -Fue "$destfs" || die $? "zfs incremental send failed" + if [[ -n $gpgid ]] ; then + ZFS "$srchost" send $send_opts -R $intermediate "$last" "$cur" \ + | gpg --trust-model always --encrypt --recipient "$gpgid" \ + | ssh "$desthost" zfswrite "${tag}_$date.zfssnap.gpg" \ + || die $? "zfs incremental send failed" + ssh "$desthost" zfslast "$snap" + else + ZFS "$srchost" send $send_opts -R $intermediate "$last" "$cur" \ + | ssh "$desthost" zfswrite "${tag}_$date.zfssnap" \ + || die $? "zfs incremental send failed" + ssh "$desthost" zfslast "$snap" + fi + fi - exit -elif $fromssh ; then - log "receving from SSH server to local zfs filesystem" + exit + elif $fromssh ; then + log "receving from SSH server to local zfs filesystem" - # make sure dest exists - if [[ $dest =~ : ]] ; then - die 1 "$dest must be a local zfs filesystem" - elif [[ $(ZFS "" list -H -o name "$dest" 2>/dev/null) != $dest ]] ; then - die 1 "$dest must be a local zfs filesystem" - fi + # make sure dest exists + if [[ $dest =~ : ]] ; then + die 1 "$dest must be a local zfs filesystem" + elif [[ $(ZFS "" list -H -o name "$dest" 2>/dev/null) != $dest ]] ; then + die 1 "$dest must be a local zfs filesystem" + fi - # split src into components - if [[ $src =~ : ]] ; then - srchost="${src%:*}" - srcpath="${src#*:}" - else - die 1 "$src must be ssh host:path" - fi + # split src into components + if [[ $src =~ : ]] ; then + srchost="${src%:*}" + srcpath="${src#*:}" + else + die 1 "$src must be ssh host:path" + fi - ### - ### receive - ### - log "receiving incremental snapshot from $src to $dest" - #ZFS "$srchost" send $send_opts -R $intermediate "$last" "$cur" | ZFS "$desthost" receive $recv_opts -Fue "$destfs" || die $? "zfs incremental send failed" - for file in $(ssh "$srchost" zfsfind | sort) ; do - log "receiving $file from $srchost" - if [[ $file =~ \.gpg$ ]] ; then - ssh "$srchost" zfsget "$file" | gpg | ZFS "$desthost" receive $recv_opts -Fue "$dest" \ - && ssh "$srchost" rm "$file" - else - ssh "$srchost" zfsget "$file" | ZFS "$desthost" receive $recv_opts -Fue "$dest" \ - && ssh "$srchost" rm "$file" - fi - done + ### + ### receive + ### + log "receiving incremental snapshot from $src to $dest" + #ZFS "$srchost" send $send_opts -R $intermediate "$last" "$cur" | ZFS "$desthost" receive $recv_opts -Fue "$destfs" || die $? "zfs incremental send failed" + for file in $(ssh "$srchost" zfsfind | sort) ; do + log "receiving $file from $srchost" + if [[ $file =~ \.gpg$ ]] ; then + ssh "$srchost" zfsget "$file" | gpg | ZFS "$desthost" receive $recv_opts -Fue "$dest" \ + && ssh "$srchost" rm "$file" + else + ssh "$srchost" zfsget "$file" | ZFS "$desthost" receive $recv_opts -Fue "$dest" \ + && ssh "$srchost" rm "$file" + fi + done - exit -fi + exit + fi -# discard anything before a colon to get the fs -srcfs="${src#*:}" -destfs="${dest#*:}" + # 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%:*}" + # 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##*/}" + # 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 + # 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" + ### + ### 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@)" + ### + ### 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 $send_opts $reinit "$cur" | ZFS "$desthost" receive $recv_opts -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 $send_opts -R $intermediate "$last" "$cur" | ZFS "$desthost" receive $recv_opts -Fue "$destfs" || die $? "zfs incremental send failed" -fi + ### + ### send & receive + ### + # 1st time: send full snapshot + if [[ -z $last ]] ; then + log "sending full recursive snapshot from $src to $dest" + ZFS "$srchost" send $send_opts $reinit "$cur" | ZFS "$desthost" receive $recv_opts -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 $send_opts -R $intermediate "$last" "$cur" | ZFS "$desthost" receive $recv_opts -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 + ### + ### 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 + +) 9>/var/lock/bashclub-zfs.lock