Created
June 21, 2014 20:53
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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