aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCUMENTATION41
-rw-r--r--INSTALL10
-rw-r--r--etc/squashfu62
-rwxr-xr-xsquashfu141
4 files changed, 254 insertions, 0 deletions
diff --git a/DOCUMENTATION b/DOCUMENTATION
new file mode 100644
index 0000000..af175b7
--- /dev/null
+++ b/DOCUMENTATION
@@ -0,0 +1,41 @@
+SquashFu - an alternative backup solution
+ Inspired by http://forums.gentoo.org/viewtopic-p-4732709.html
+
+Requirements: aufs, aufs2-util, squashfs-tools, rsync
+
+Goal: To create a backup solution which provides incremental backups and compression,
+ and which also provides an easy way to roll back.
+
+Design:
+ A directory structure is created as follows:
+ backup_root/
+ |- seed.sfs
+ |- ro/
+ |- rw/
+ |- bins/
+ |-1/ <-- Monday incremental
+ |-2/ <-- Tuesday incremental
+ |-3/ <-- Wednesday incremental
+ |-4/ <-- Thursday incremental
+ |-5/ <-- Friday incremental
+ |-6/ <-- Saturday incremental
+ |-7/ <-- Sunday incremental
+
+ seed.sfs is created from an initial backup and compressed using SquashFS. It
+ is then mounted, using a loopback device on ro/. Using aufs2, a union mount is
+ formed by ro/ and each of the numbered bins, each corresponding to a day.
+
+ At the time of the backup, the day is determined in order to prepare the union. If
+ today is Thursday, `date +%u` returns 4. In order for rsync to properly create our
+ incremental, we need to compare current data with the seed plus incrementals leading
+ up to Thursday, so we create our Aufs mount with the branches bins/4, bins/3, bins/2,
+ bins/1, and ro/ (in that order). When rsync now writes to the resulting union, aufs
+ happily receives the data into the first available (writable) branch of the union,
+ which is bins/4. The next day, the union mount is reformed adding the next bin...
+
+ If and when you want to roll back, the process is simple. Mount the seed plus
+ the necessary bins to increment the seed up to the day of interest.
+
+
+
+
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..34f383a
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,10 @@
+WARNING: EXTREMELY ALPHA. MAY SET YOUR CAT ON FIRE.
+
+If you don't care about your cat...
+-Put etc/squashfu in /etc/squashfu
+-Make a backup directory somewhere where you want your files
+-Read over /etc/squashfu and set it the way you want
+-Make a cron job to run this bad boy at the time you want
+-Hang onto ya nuts.
+
+Some day this will be a real program.
diff --git a/etc/squashfu b/etc/squashfu
new file mode 100644
index 0000000..54c5706
--- /dev/null
+++ b/etc/squashfu
@@ -0,0 +1,62 @@
+# Config file for Super Deluxe SquashFu Backup Express
+
+# The base directory where backups will go.
+# This is the only directory that absolutely needs to be
+# created by the user. All others will be created as needed
+# by the actual backup (and inside this root).
+BKUP_ROOT="/mnt/Gluttony/squashup"
+
+# Filename for the seed generated. You shouldn't need
+# to change this
+SEED="${BKUP_ROOT}/$HOSTNAME-seed.sfs"
+
+# This is an optional offset for determining what bin is used
+# for a nightly backup. If you run your backups in the early
+# morning for the previous day's work, you'll want to set this
+# to -1. Values other than 0 or -1 will cause bad things to
+# happen.
+# TODO: This can also be overridden with the --modifier option
+# at runtime
+MODIFIER=0
+
+# These are the options that are passed directly to rsync.
+# The -u flag is a necessity, or else incrementals will
+# not be created properly. See 'man rsync' for more info.
+RSYNC_OPTS=("-Rua" "--delete" "--stats")
+
+# This option determines whether the previous week's seed
+# and bins are kept when creating a fresh seed for the next
+# week. If this value is anything besides 1, the seed and bins
+# will be discarded. If you have room for a second seed,
+# keep this enabled until you are sure everything is working
+# correctly.
+KEEP_LAST_WEEK=1
+
+# Set this to the day of the week you want a resquash to occur.
+# Days are numbered according to the output of 'date +%u' -- 1
+# through 7, with 1 being Monday, 7 being Sunday. If this number
+# is out of bounds or this day is missed, you MUST create the
+# new squash manually using --resquash. This is again compared
+# to the output of 'date +%u' but does NOT take into account
+# the MODIFIER above.
+RESQUASH_DAY=7
+
+
+# The following defines what will and won't be backed up. All lines
+# MUST be commented, followed by a + for include, or a - for exclude.
+# All other lines will be ignored (or things will break).
+
+#+/boot
+#+/etc
+#+/home
+#+/root
+#+/usr/share
+#+/var/lib/pacman
+
+#-/boot/lost+found
+#-/home/*/.cache
+#-/home/*/.thumbnails
+#-/home/*/.lyrics
+#-/home/*/.gnome2
+#-/home/*/dev/git/*/.git
+#-/home/lost+found
diff --git a/squashfu b/squashfu
new file mode 100755
index 0000000..7b42d46
--- /dev/null
+++ b/squashfu
@@ -0,0 +1,141 @@
+#!/bin/bash
+
+# Declare base options, overwrite with user specs as necessary
+# MAKE SURE TO CHANGE THIS BEFORE PUSHING PUBLIC
+CONFIG=/home/haruko/dev/git/squashfu/etc/squashfu
+source $CONFIG
+if [[ $? -gt 0 ]]; then
+ echo "FATAL: Error in config file. Please check your syntax"
+ exit 1
+fi
+
+# Informational output w/ happy colors
+debug () {
+ echo -e '\033[1;33m??\033[1;m ' $*
+}
+
+die () {
+ echo -e '\033[1;31m!!\033[1;m ' $*
+ exit 1
+}
+mount_aufs_by_num () {
+ # Check for the union already being mounted
+ grep "${BKUP_ROOT}/rw" /proc/mounts >/dev/null && umount "$BKUP_ROOT/rw"
+
+ # build branch string
+ branches="br="
+ for i in `seq $1 -1 1`; do
+ branches="${branches}${BKUP_ROOT}/bins/${i}:"
+ done
+ branches="${branches}${BKUP_ROOT}/ro"
+
+ # build and execute mount command
+ mount -t aufs none $BKUP_ROOT/rw -o udba=reval,$branches
+}
+
+create_new_seed () {
+ # For our very first seed, we're writing directly to disk, so
+ # Delete the data after the squashed seed has been created
+ [[ "$1" == "--initial" ]] && run_rsync
+
+ # Create a new squashfs based on the contents of the union
+ 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" || {
+ echo FATAL: Error mounting $SEED;
+ exit 1;
+ }
+}
+
+mount_aufs_by_day() {
+ # convert DoW to a number
+ mount_aufs_by_num `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
+ echo Rsync executing with:
+ echo Options: ${RSYNC_OPTS[@]}
+ echo Includes: ${INCLUDES[@]}
+ echo 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
+ umount "${BKUP_ROOT}/rw"
+ umount "$SEED"
+}
+
+# Sanity checks
+# - Are we root?
+[[ $UID -eq 0 ]] || { echo "Must be root!"; exit 1; }
+
+# - is our BKUP_ROOT valid? (FAIL)
+[[ -w "${BKUP_ROOT}" ]] ||
+ { echo "FATAL: Backup root '$BKUP_ROOT' is not a valid location!";
+ echo "Please check the setting in /etc/squashfu"; exit 1; }
+
+# Blindly unmount all just in case
+unmount_all
+
+# - 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
+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)
+[[ -f "$SEED" ]] || {
+ echo "No seed found -- creating a new one...";
+ create_new_seed "--initial";
+}
+# mount seed if it exists and is not already mounted
+grep "${BKUP_ROOT}/ro" /proc/mounts >/dev/null || mount_seed
+
+# Prepare union mount with proper bins
+mount_aufs_by_num $(( $(date +%u) + $MODIFIER ))
+
+# Ready for backup!
+run_rsync
+
+# 5) 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
+}
+
+unmount_all
+
+# 6) Optional behavior
+# --seed-initial Create new seed
+# --rollback $1 $2 Rollback to the day specified by $1, mounting at $2
+# --resquash Create a new seed
+#