Skip to content

Instantly share code, notes, and snippets.

@wrouesnel
Created June 21, 2014 20:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save wrouesnel/f2e3b3f4d25dbcca8a19 to your computer and use it in GitHub Desktop.
Save wrouesnel/f2e3b3f4d25dbcca8a19 to your computer and use it in GitHub Desktop.
A script for doing routine backups of one's home directory with bup and ZFS
#!/bin/bash
# Script to do sensible home directory backups. Expects to receive the
# target user as it's first argument, and then proceeds to do a home
# directory backup in a ZFS-aware way. The second arg should be the name
# for the backup. hostname is used if it's blank.
# The ZFS backup is done by snapshotting every file system underneath the
# home directory and then indexing them all with appropriate graft options.
# The backup destination is determined by the user-name, and expects to find
# a system user called $USER-backup. The home directory of this user is the
# bup repository.
# Non-stdout echo command
echoerr ()
{
echo $@ 1>&2
}
while getopts ":vpn" option; do
case $option in
v)
VERBOSEBUPSAVE="-v -v"
VERBOSEBUPINDEX="-v -v"
;;
p)
PROFILE="--profile"
;;
n)
DRYRUN="1"
;;
:) echoerr "Error: -$option requires an argument."; exit 1 ;;
?) echoerr "Error: -$option unknown option."; exit 1 ;;
esac
done
# Access remaining options (should be just 1)
shift $(($OPTIND - 1))
if [[ $# > 2 ]]; then
echoerr "Too many arguments. Should be max 2."
exit 1
fi
# Target user for the backup
TARGETUSER=$1
BACKUPUSER=$TARGETUSER-backup
LOGFILE=
if [ ! -z $2 ]; then
BACKUPNAME="$2-home"
else
BACKUPNAME="$(hostname)-home"
fi
# Script pid file
PIDFILE=/run/$TARGETUSER-home-directory-backup.pid
# Excludes to pass to bup index
# Note: don't put spaces in front of the \
EXCLUDES=( "--exclude-logical" "/.cache"\
"--exclude-logical" "/.thumbnails"\
"--exclude-logical" "/.unison/backup"\
"--exclude-logical" "/.Trash-1000"\
"--exclude-logical" "/.Trash-0"\
"--exclude-logical" "/tmp/.Trash-1000/"\
"--exclude-logical" "/tmp/"\
"--exclude-logical" "/.local/share/Steam/SteamApps"\
"--exclude-logical" "/pbuilder-repo"\
"--exclude-logical" "/Downloads"\
"--exclude-logical" "/VirtualBox VMs")
# Automatically invoke sudo with the correct command line
if [[ $EUID -ne 0 ]]; then
echo "Root permissions required."
sudo $(readlink -f $0) "$@"
exit $?
fi
# unique identifer for session
UNIQID=bup-$$-$(date +%s)
# list of snapshots to cleanup
FSCLEANUP=()
trap "caughtsig" SIGINT SIGHUP SIGQUIT SIGABRT SIGTERM
# Do a clean shutdown.
caughtsig ()
{
cleanup
echoerr "Exiting on signal..."
notify "Backup Stopping" "Exiting on signal."
exit 1
}
cleanup ()
{
echoerr "Killing jobs..."
kill $(jobs -p)
echoerr "Destroying snapshots..."
for SNAPSHOT in "${FSCLEANUP[@]}"; do
zfs destroy "${SNAPSHOT}"
done
echoerr "Removing pidfile..."
rm -f ${PIDFILE}
# Reset ownership on backup directory if possible
if [ ! -z $BACKUPUSER ]; then
echoerr "Granting backup ownership to $BACKUPUSER..."
chown -R $BACKUPUSER "$BUP_DIR"
fi
# Reset ownership on indexes for the real user.
if [ ! -z $TARGETUSER ]; then
echoerr "Granting index ownership to $TARGETUSER..."
chown $TARGETUSER "$BUP_DIR/bupindex"
chown $TARGETUSER "$BUP_DIR/bupindex.meta"
chown $TARGETUSER "$BUP_DIR/bupindex.hlink"
fi
}
notify ()
{
DISPLAY=:0; sudo -u $TARGETUSER notify-send "$@"
}
# CHECKS
# User exists?
id "${TARGETUSER}" > /dev/null
if [[ $? != 0 ]]; then
echoerr "Non-existent user"
cleanup
exit 1
fi
id "${BACKUPUSER}" > /dev/null
if [[ $? != 0 ]]; then
echoerr "Non-existent backup user"
cleanup
exit 1
fi
# Find the user's home directory
USERHOME="$(sudo -u ${TARGETUSER} -H -s eval 'echo $HOME')"
# Set backup home
export BUP_DIR="$(sudo -u ${BACKUPUSER} -H -s eval 'echo $HOME')"
if [ -e "${PIDFILE}" ]; then
kill -s 0 $(cat ${PIDFILE})
if [[ $? == 0 ]]; then
echoerr "Backup already in progress for user."
exit 0
fi
fi
# Write a pidfile before starting work
echo $$ > "${PIDFILE}"
# List of snapshot paths we will index
SNAPPATHS=()
# grafted names in bup archive
GPATHS=()
# Find and snapshot the ZFS file systems.
while read LINE; do
FSPATH=$(echo ${LINE} | cut -d' ' -f1)
FS=$(echo ${LINE} | cut -d' ' -f2)
# we are fairly likely to accidentally get the backup user (i.e. bup path)
# avoid it.
if [[ $FSPATH == $BUP_DIR ]]; then
continue
fi
zfs snapshot ${FS}@${UNIQID}
# Did we succeed?
if [[ $? != 0 ]]; then
echoerr "Failed creating snapshot ${FS}@${UNIQID}"
notify "Backup Failed" "Failed creating snapshot ${FS}@${UNIQID}"
cleanup
exit 1
fi
# Make a note for snapshots to clean up
FSCLEANUP+=("$FS@$UNIQID")
# Add to the list of snapshot paths
SNAPPATHS+=("${FSPATH}/.zfs/snapshot/${UNIQID}")
# Add to same index the fspath sans the home directory path.
GPATHS+=("/${FSPATH#$USERHOME}")
done < <(zfs list -H -o mountpoint,name | grep "^${USERHOME}")
# Send notification since we're about to start hammering the disk
notify "Backup Starting" "Backing up $USERHOME"
# Index each snapshot to the appropriate logical path
for IND in "${!SNAPPATHS[@]}"; do
bup index -u $VERBOSEBUPINDEX --graft "${SNAPPATHS[$IND]}"="${GPATHS[$IND]}" \
"${EXCLUDES[@]}" "${SNAPPATHS[$IND]}"
if [[ $? != 0 ]]; then
echoerr "Failed indexing ${SNAPPATH[$IND]}"
notify "Backup Failed" "failed while indexing ${SNAPPATH[$IND]}"
cleanup
exit 1
fi
done
# Save the entire index
if [ ! -z $DRYRUN ]; then
bup save $VERBOSEBUPSAVE -n "$BACKUPNAME" /
if [[ $? != 0 ]]; then
notify "Backup successful."
fi
else
notify "Backup dry-run successful."
fi
cleanup
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment