#!/bin/bash # Informational output w/ happy colors DEBUG=true debug () { [[ $DEBUG ]] && echo -e '\033[1;33mDEBUG ::\033[1;m ' $* } info () { echo -e '\033[1;34m::\033[1;m ' $* } die () { echo -e '\033[1;31mERROR ::\033[1;m ' $* exit 1 } CONFIG=/etc/squashfu source $CONFIG [[ $? -gt 0 ]] && die "Error in config file. Please check your syntax" mount_union_ro () { # build branch string branches="br=" for i in `seq $1 -1 1`; do branches="${branches}${BKUP_ROOT}/bins/${i}=ro:" done branches="${branches}${BKUP_ROOT}/ro=ro" # build and execute mount command debug "Mounting union as entirely read only" mount -t aufs none $BKUP_ROOT/rw -o udba=reval,$branches } mount_union_branch_rw () { debug "Remount branch $i as read-write" mount -o remount,mod:bins/$1=rw "${BKUP_ROOT}/rw" } create_new_seed () { # Create a new squashfs based on the contents of the union # It's okay if we make an empty squash, we'll tell the user # about it at the end and suggest --resquash debug "Making new squash seed $(basename $SEED)" mksquashfs "${BKUP_ROOT}/rw" "$SEED" -b 65536 # Delete the rsync source since its now squashed #[[ "$1" == "--initial" ]] && rm -rf "${BKUP_ROOT}/rw/*" } move_old_tree () { storage="${BKUP_ROOT}/bkup-$(date +%Y-%m-%d)" mkdir "$storage" cd "$BKUP_ROOT" && mv {$SEED,bins/} "$storage" } mount_seed () { debug "Mounting seed" # Mount the squashed seed, failing if we can't mount -o loop,ro "${SEED}" "${BKUP_ROOT}/ro" || { die FATAL: Error mounting $SEED; } } mount_aufs_by_day() { # convert DoW to a number mount_union_ro `date --date=$1 +%u` } run_rsync() { # Gather includes and excludes from config file # No error checking here -- user better not have # effed up the config INCLUDES=($(grep ^#+ $CONFIG | cut -d+ -f2-)) EXCLUDES=($(grep ^#- $CONFIG | cut -d- -f2-)) # rsync source to $BKUP_ROOT/rw debug "Rsync executing with:" debug " Options: ${RSYNC_OPTS[@]}" debug " Includes: ${INCLUDES[@]}" debug " Excludes: ${EXCLUDES[@]}" rsync ${RSYNC_OPTS[@]} ${INCLUDES[@]} ${EXCLUDES[@]} ${BKUP_ROOT}/rw || return 1 } # Unmount union and squash unmount_all () { #Union must be unmounted first, or bad things happen grep "${BKUP_ROOT}/rw" /proc/mounts && { debug Unmounting union...; umount "$BKUP_ROOT/rw"; } grep "${SEED}" /proc/mounts && { debug Unmounting squash...; umount "${SEED}"; } } finish_routine () { # Do another sanity check -- check sizes of bins versus squash. bin_size=$(du -s ${BKUP_ROOT}/bins | awk '{print $1}') sfs_size=$(du -s $SEED | awk '{print $1}') if [[ $bin_size -gt $sfs_size ]]; then info "Your incrementals are larger than your seed! You might consider resquashing your backup with $0 --resquash" fi unmount_all } usage () { info "This is what happens when you can't read" exit } # The meat and potatoes of the script. do-backup () { ################################ # Sanity checks ################################ # - Are we root? # - is our BKUP_ROOT valid? (FAIL) # - Check for pre-existing mounts just in case (and unmount them) # - do we have a proper (expected) directory structure in place? # (Use cd to BKUP_ROOT to avoid issues with brace expansion in a quoted path) [[ $UID -eq 0 ]] || die Must be root! [[ -w "${BKUP_ROOT}" ]] || die "Backup root is not accessible. Please check your setting in /etc/squashfu" unmount_all cd "$BKUP_ROOT" && mkdir -p {rw,ro,bins/{1,2,3,4,5,6,7}} ################################ # Prep work ################################ # - does seed exist? (if not, our backup is creating the seed) # - Prepare union mount with proper bins [[ -f "$SEED" ]] || { debug "No seed found -- creating a new one..."; create_new_seed; } mount_union_ro $(( $(date +%u) + $MODIFIER )) mount_union_branch_rw $(( $(date +%u) + $MODIFIER )) ################################ # Ready for backup! ################################ run_rsync ################################ # Cleanup ################################ # - Is this resquash day? If so, we need a new squash # - If new squash creation fails, we're in trouble. (by default, keep previous week) [[ $(date +%u) -eq $RESQUASH_DAY ]] && { create_new_seed # Set aside last week's tree if user opted to, else delete it all if [[ $KEEP_LAST_WEEK -eq 1 ]]; then move_old_tree else find "${BKUP_ROOT}/bins/" -type f -delete rm $SEED fi } finish_routine } #Option parsing / Decision making [[ $# -eq 0 ]] && usage while [[ $# -gt 0 ]]; do case $1 in "-c") ;; *) usage ;; esac shift done