#!/bin/bash # Informational output w/ happy colors debug () { [[ $DEBUG ]] && echo -e '\033[1;33mDEBUG ::\033[1;m ' $* } info () { echo -e '\033[1;34m::\033[1;m ' $* } die () { echo -e '\033[1;31mFATAL ::\033[1;m ' $* exit 1 } # Source default config, and bail on bad syntax CONFIG=/etc/squashfu.conf source $CONFIG [[ $? -gt 0 ]] && die "Syntax error in config file." 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 } # The meat and potatoes of this bad agent. 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 } } 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_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_up_to_day () { # convert DoW to a number mount_union_ro `date --date=$1 +%u` } mount_union_branch_rw () { debug "Remount branch $i as read-write" mount -o remount,mod:bins/$1=rw "${BKUP_ROOT}/rw" } move_old_tree () { storage="${BKUP_ROOT}/bkup-$(date +%Y-%m-%d)" mkdir "$storage" cd "$BKUP_ROOT" && mv {$SEED,bins/} "$storage" } query_usage () { # Do another sanity check -- check sizes of bins versus squash. bin_size=$(du -s ${BKUP_ROOT}/bins 2>/dev/null | awk '{print $1}') sfs_size=$(stat -c %s $SEED 2>/dev/null) info "Usage report (in mb):\n" printf "%30s %10.2f\n" "Seed size:" "`echo "scale=2;$sfs_size / 1024 / 1024" | bc`" printf "%30s %10.2f\n" "Total incremental size:" "`echo "scale=2;$bin_size / 1024 / 1024" | bc`" 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 } 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}"; } } usage () { info "SquashFu: a backup solution hewn out of boredom" cat < [options] OPERATIONS -B Runs a regular backup, using the config file at /etc/squashfu, unless otherwise specified with the -c option. -Q Displays the size of the seed, the incrementals, and the actual backup. -R [mount] Rollback to the day described by the following argument. 'day' needs to be a day of the week between the last resquash and the current day. An alternate mount point can be specified for the resulting rolled back union. If unspecified, the union will be mounted at $BKUP_ROOT/rw. OPTIONS --resquash To be used with the -B operation. This forces a new squashed seed to be created when the backup finishes. The original seed and incrementals are moved to the directory specified in the config file. Ensure that you have sufficient space for this operation, or bad things will happen. You can use the -Q operation to estimate how much free space you will need. --resquash-destroy-old Similar to --resquash except the old seed and incrementals are discarded after the new seed is created. HELP exit } #Option parsing / Decision making [[ $# -eq 0 ]] && usage while [[ $# -gt 0 ]]; do case $1 in "-B") do_backup ;; "-Q") query_usage ;; "-c") ;; "--resquash") ;; "--check-size") ;; *) usage ;; esac shift done