#!/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