Code » Backup
These are the scripts I use to backup my laptop. They live in the directory /live
on an external hard drive, along with backup directories with names like 20090903-195223
. Each contains a directory called data
(mirroring the system's /
directory) and an rsync.log
. I run push.sh
every day or so.
The code is inspired by Mike Rubel's snapshot research page. Essentially, the script works by creating a new backup directory, then running rsync with the --link-dest
option. This very cool feature will use the next oldest backup directory as the baseline for determining whether a file has been modified. If a file in the rsync source is newer, it will be copied over, otherwise it is hardlinked to the link-dest's copy. This prevents unchanged files from taking up room in each new backup.
Setup
- Copy the provided code into a directory on your external drive.
- Personalize the path
/media/ExternalDrive/live
to match your setup. - Enter the backup directory and run
./init.sh
. - Modify the last few lines of update.sh to reflect your system's partitions. (One rsync per filesystem.)
I have this command in a sidebar launcher to add a backup:
gksudo "gnome-terminal --execute /media/ExternalDrive/live/push.sh"
Code
init.sh
#!/bin/bash # Update the most recent backup location with the contents of the system. set -o errexit set -o nounset ## Fake link to previous ln -s ./DELETE_ME-previous ./previous ## Fake current backup mkdir ./DELETE_ME-current touch ./DELETE_ME-current/rsync.log mkdir ./DELETE_ME-current/data ln -s ./DELETE_ME-current ./current echo 'Created fake backup directory and necessary symlinks.' echo 'Once there is at least one real backup, you may "rm -r ./DELETE_ME-current"'
push.sh
#!/bin/bash # Push a new backup location that is identical to the last one, then update it. set -o errexit set -o nounset ## Environment cd /media/ExternalDrive/live set -o verbose ## Create the new backup location STAMP=`date '+%Y%m%d-%H%M%S'` mkdir "./$STAMP" ## Roll the symlinks unlink "./previous" mv "./current" "./previous" ln -s "./$STAMP" "./current" ## Update the current backup ./update.sh ## Wait for approval before closing read -n 1 -p "Press any key to continue . . ." echo
update.sh
#!/bin/bash # Update the most recent backup location with the contents of the system. set -o errexit set -o nounset ## Environment cd /media/ExternalDrive/live set -o verbose re_BASE='^/((\.\.[^/]|\.[^./]|[^./])[^/]*/)*$' ## do_rsync $BASE $EXCLUDES ## BASE: absolute path ending in a slash ## EXCLUDES: space-delimited series of --exclude=/foo function do_rsync { BASE=$1 EXCLUDES=$2 # Using a regex instead of realpath because we don't want to expand symbolic links if [[ ! ( "$BASE" =~ $re_BASE ) ]] then echo "BASE must be a fully absolute, normalized path to a directory. (Must start and end with a slash, contain no '.' or '..' components, and contain no double slashes.)" exit 1 fi # Make "../"**(number of slashes in BASE + 1) BACK=../`echo "$BASE" | perl -ne 'while(/\//g){$count.="../"}; print "$count\n"'` rsync -avEHAXx --numeric-ids --delete ${EXCLUDES} --link-dest=${BACK}previous/data${BASE} ${BASE} current/data${BASE} 2>&1 | tee -a current/rsync.log } ## Ensure log file (we can now use append on the first `tee`) touch current/rsync.log ## Sync each filesystem, descending the tree ## Each BASE must begin and end with a slash. do_rsync / '--exclude=/proc --exclude=/sys --exclude=/dev --exclude=/mnt --exclude=/media --exclude=/tmp' do_rsync /home/ '--exclude=/*/.gvfs'