#!/bin/sh
#
# snapshot-tar-backup.sh - FreeBSD tar backup of filesystem(s) using snapshots
#
# Copyright (c) 2010 EPIPE Communications
#
# Permission to use, copy, modify, and/or distribute this software
# for any purpose with or without fee is hereby granted, provided
# that the above copyright notice and this permission notice appear
# in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
#
# Version: 1.0
#
# Version history:
#
# - 1.0: initial release (2010-08-31)
#
# Download location:
#
# http://dist.epipe.com/freebsd/
#
# Description:
#
# Takes a tar(1) backup of filesystem(s) by using ufs file system
# snapshots. The script takes consistent backups of live file systems.
# For example live database files can be backed up this way ("hot backup").
# Normally this is possible only with dump(8) with -L option but not
# with tar(1). Easy to use for both local and remote backups with a
# simple front-end script or from command line. See below for examples.
#
# Depedencies:
#
# /usr/ports/sysutils/freebsd-snapshot
#
# See http://people.freebsd.org/~rse/snapshot/ for additional information
# about this useful tool for managing FreeBSD file system snapshots.
#
# freebsd-snapshot can be used to make scheduled snapshots from cron(8)
# and to provide user access to them through amd(8), but these features
# do not need to be enabled to use this script. Only the script
# /usr/local/sbin/snapshot is required.
#
# Usage examples:
#
# Make a snapshot of / /usr /var and /home partitions and save a
# tar backup of that in /my/backups/backup.tar:
#
# snapshot-tar-backup.sh / /usr /var /home > /my/backups/backup.tar
#
# Make a snapshot of /home and /data on remote system host2.example.org
# and take a gzipped tar backup of that to a local file
# /some/dir/host2-home-data.tar.gz:
#
# ssh host2.example.org -l root \
# "/usr/local/etc/snapshot-tar-backup.sh /home /data" \
# | gzip > /some/dir/host2-home-data.tar.gz
#
# The above assumes that this script has been installed as
# /usr/local/etc/snapshot-tar-backup.sh in the remote host.
# You could also alter the command to run the compression
# process at the other end.
#
# Caveats:
#
# - The backup source locations must be ufs2 file system roots, not
# arbitrary directories within a file system.
#
# - If several filesystems are specified, they must be listed in
# order so that the higher level mount points are listed first.
#
# The following is correct:
# snapshot-tar-backup.sh / /usr /var /var/db/mysql
# snapshot-tar-backup.sh /var /var/db/mysql
#
# The following is INCORRECT:
# snapshot-tar-backup.sh /usr /
# snapshot-tar-backup.sh /var/db/mysql /var
#
# - Does not work with ZFS because the script is looking for ".snap"
# directories at the filesystem roots and expects the snapshots
# to be created there. This should be trivial to change.
#
# - If the system crashes or the script is otherwise interrupted in
# the middle, snapshots are not removed until the script is run for
# the next time.
#
# - When this script is used to take remote backups, the script must
# be installed on the remote host(s).
#
# - Must be run as root.
#
# Author contact information:
#
# Janne Snabb
# http://epipe.com/
#
# check that we have some arguments
if [ $# -lt 1 ] ; then
echo 1>&2 "usage: $0 /fs /fs2 .."
exit 2
fi
# list of file systems to back up from the arguments
fss="$*"
# check that sysutils/freebsd-snapshot port is installed
if [ ! -x /usr/local/sbin/snapshot ] ; then
echo 1>&2 "$0: required port sysutils/freebsd-snapshot is not installed"
exit 2
fi
# check that we are running as root (effecive uid)
if [ `/usr/bin/id -u` != "0" ] ; then
echo 1>&2 "$0: must be run as root"
exit 2
fi
# check that the arguments point to file systems with .snap directory
for fs in ${fss} ; do
if [ -z "${fs}" -o ! -d ${fs}/.snap ] ; then
echo 1>&2 "$0: ${fs}/.snap missing, is ${fs} an ufs2 root?"
exit 2
fi
done
# function which returns its arguments in reverse order by using recursion
reverse() {
if [ $# -gt 0 ] ; then
local arg="$1"
shift
reverse "$@"
echo "$arg "
fi
}
# make a temporary mount point for snapshots (you might want to exclude
# this locataion from updatedb by putting it in /etc/locate.rc)
mountpoint=`/usr/bin/mktemp -d /mnt-backup-XXXXXX`
if [ $? -ne 0 ] ; then
echo 1>&2 "$0: unable to create temporary mount point"
exit 1
fi
# make snapshots of all filesystems
for fs in ${fss} ; do
/usr/local/sbin/snapshot make ${fs}:backup
done
# mount the snapshots at our mount point
for fs in ${fss} ; do
/usr/local/sbin/snapshot mount -o ro ${fs}:backup ${mountpoint}/${fs}
done
# output a tar archive of our snapshot tree to STDOUT
/usr/bin/tar -c -f - -C ${mountpoint} .
# capture tar return value
tarstatus=$?
if [ $? -ne 0 ] ; then
echo 1>&2 "$0: tar returned failure: $?"
# continuing anyway to destroy the snapshots
fi
# umount the snapshots (in reverse order to avoid problems)
for fs in `reverse ${fss}` ; do
/usr/local/sbin/snapshot umount ${mountpoint}/${fs}
done
# remove the snapshots as they are no longer needed
for fs in ${fss} ; do
/bin/rm -f ${fs}/.snap/backup.*
done
# remove the mount point directory
/bin/rm -rf ${mountpoint}
# exit with tar status
exit $tarstatus
# eof