#!/bin/sh

# @(#) $Id: backupsnap-zfs.sh 1217 2014-07-23 08:37:08Z matthew $

# Copyright (c) 2011 Matthew Seaman. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#    1.  Redistributions of source code must retain the above
#        copyright notice, this list of conditions and the following
#        disclaimer.
#
#    2.  Redistributions in binary form must reproduce the above
#        copyright notice, this list of conditions and the following
#        disclaimer in the documentation and/or other materials
#        provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.

# Create snapshots of the filesystems: these will automount under
# ${mountpoint}/.zfs/snapshot/ Back these up remotely using
# tarsnap(1), then dispose of the snapshots.  Run this out of root's
# cron for a daily backup.
#
# Trim the backups kept on-line -- keep only the last 30 for each
# filesystem.

PATH=/usr/bin:/bin:/usr/local/bin:/usr/sbin:/sbin:/usr/local/sbin ; export PATH
IFS='	 
' ; export IFS
umask 022

# Configuration foo

# Command args: 'xroot /tmp /var/empty ...' zpool to backup all
# filesystems from, with list of exceptions.  The first named zvol is
# assumed to be the parent, and a recursive snapshot of it is created.
# Second and subsequent args are fs glob patterns to *exclude* from
# backing up.

pool=${1:-'zroot'}
shift
exceptions=${@:-'/tmp
		 /usr/ports*
		 /usr/obj
		 /usr/src 
		 /var/empty
		 /var/tmp
		 /jail*
		 /ROOT*
		 /poudriere*
		 /usr/local/poudriere*
		 /mnt'}

now=$( date +%Y%m%d-%H%M )
lock=/var/run/${0##*/}.lock
logger="logger -t ${0##*/}[$$] -s -p daemon.notice"
keep_count=30
fslist=''

set -o noglob
for fs in $( mount -p -t zfs | awk '{ print $2 }' ) ; do
    match=
    for exc in $exceptions ; do
	case $fs in
	    */.zfs/snapshot/*)
		match=1
	        break		# Automounted snapshots. Do not want.
		;;
	    $exc)
		match=1
		break
		;;
	esac
    done
    if [ -z $match ]; then
	fslist="$fslist $fs"
    fi
done 

# There can be only one.
lockf -t 0 ${lock} nice -n 10 /bin/sh <<EOF
    echo "Acquired lock on ${lock}"				   | ${logger};

    # Cleanup on exit
    cleanup () {
    	zfs destroy -r ${pool}@${now}				   && \
    	    echo "Destroyed snapshots ${pool}@${now} and children" | ${logger};
    }
    trap cleanup EXIT QUIT KILL INT TERM;

    # Create the snapshots:
    zfs snapshot -r ${pool}@${now}				   && \
	echo "Created snapshots ${pool}@${now} and children"	   | ${logger};

    # Backup each of the snapshots
    for fs in ${fslist} ; do
	echo "Backing up \${fs}"				   | ${logger};
	# Do the backup.
	tarsnap -cf \${fs}@${now} --snaptime ${lock}		      \
	    -C \${fs}/.zfs/snapshot/${now} . 2>&1		   | ${logger};
    done
EOF

# Cleanup will happen when the subshell above exits.

# Delete the oldest backups from the store
archives=$( tarsnap --list-archives )
dates=$( echo "$archives" | sed -e 's,^.*@,,' | \
    sort -ru | sed -e 1,${keep_count}d ) 

for date in ${dates}; do
    echo "Deleting backups from ${date}"                           | ${logger};
    for del in $archives ; do
	case ${del} in
	    *@${date})
	    {
		tarsnap -d -f $del 2>&1				   && \
		    echo "Deleted old backup ${del}" ;
	    }							   | ${logger};
	    ;;
	    *)			# Do nothing
		;;
	esac
    done
done

echo "All finished"						   | ${logger};
exit 0

#
# That's All Folks!
#
