#!/bin/bash # Backs up a Subversion repository using incremental dumps relative to the # previous one, or if there aren't any, a full backup made by 'svnadmin # hotcopy'. # Version 1.0 # # This script is designed to be run by a daily (or more often) incremental # backup script. It doesn't compress anything as the backup software (or tape # drives) will hopefully do that anyway. # # Note that currently, this script doesn't create the full backups. They # should be created manually or by a cron job using svn-hot-backup (part of the # 'subversion-tools' package in Debian) a.k.a. hot-backup.py. Don't forget to # set the SVN_HOTBACKUP_BACKUPS_NUMBER environment variable or use the # --num-backups option, so that old backups are removed. # # directory structure is as follows: # backupdir/ # full/ # repo-6/ # repo-7/ # repo-8/ # repo-8-1/ # repo-9/ # incr-10-14.svndump # incr-15.svndump # incr-16-19.svndump # incr-20.svndump # old_incrementals/ # incr-5.svndump # incr-6-9.svndump # # TO-DO: # option to delete old incrementals instead of moving them # (or just check for presence of old_incrementals/) # warn if there is a dump (e.g. 7-12) that spans a hot backup (e.g. test-11) # (can only happen if svn-hot-backup is run simultaneously with this script) self=${0##*/} declare -i verbose=1 incr_latest_rev() { local name_sans_ext=${1%.svndump} # remove everything up to and including the last hyphen, # e.g. incr-10-14 becomes 14 echo ${name_sans_ext##*-} } show_old_incr() { echo $1 is old } move_old_incr() { mv `if [ $verbose -ge 1 ] ; then echo -v ; fi` $1 $backupdir/old_incrementals } # *** MAINLINE *** # == Command-line handling == if [ $# -lt 2 ]; then echo "Usage: $self " exit 1 fi repodir=$1 backupdir=$2 # == Sanity checking == full_backup_name=`ls -v $backupdir/full | tail -n 1` if [ -z $full_backup_name ] ; then echo "$self: Error: no full backups present" 2>&1 exit 3 fi declare -i full_backup_rev=`svnlook youngest $backupdir/full/$full_backup_name` if [ -z $full_backup_rev ] ; then echo "$self: Error: $full_backup_name doesn't appear to be a hotcopy" 2>&1 exit 4 fi declare -i youngest_rev=`svnlook youngest $repodir` if [ -z $youngest_rev ] ; then echo "$self: Error: $repodir doesn't appear to be a repository" 2>&1 exit 5 fi # == Preparation == handle_old_incr=show_old_incr if [ -d $backupdir/old_incrementals ] ; then handle_old_incr=move_old_incr fi # == Processing == if [ $youngest_rev -gt $full_backup_rev ] ; then # incremental backup possibly required if [ $verbose -ge 2 ] ; then echo $self: checking existing incrementals ; fi # find the most recent incremental # (uses GNU ls' version sorting to remove the need for zero padding in names) newest=`ls -v $backupdir | grep '^incr-.*\.svndump$' | tail -n 1` if [ -z $newest ] ; then if [ $verbose -ge 1 ] ; then echo $self: no existing incrementals ; fi incr_present=n declare -i first_dump_rev=$((full_backup_rev + 1)) declare -i last_dump_rev=$youngest_rev backup=y else incr_present=y declare -i latest_incr_rev=`incr_latest_rev $newest` if [ $verbose -ge 2 ] ; then echo $self: latest incremental rev is $latest_incr_rev ; fi if [ $youngest_rev -gt $latest_incr_rev ] ; then declare -i first_dump_rev=$((latest_incr_rev + 1)) declare -i last_dump_rev=$youngest_rev backup=y else backup=n echo "$self: Notice: nothing to back up (youngest rev is $youngest_rev and newest incremental is $newest)" fi fi # a backup is only needed if there is not already an incremental that # covers $youngest_rev (this was established by checking $latest_incr_rev) if [ $backup = y ] ; then if [ $verbose -ge 2 ] ; then echo $self: revs to dump are $first_dump_rev-$last_dump_rev ; fi if [ $first_dump_rev -eq $last_dump_rev ] ; then revparam=$first_dump_rev dumpfile_name=incr-$first_dump_rev.svndump else revparam=$first_dump_rev:$last_dump_rev dumpfile_name=incr-$first_dump_rev-$last_dump_rev.svndump fi # svnadmin dump puts its informational messages to stderr to redirect them # to stdout # (doesn't affect the actual output due to the fact that shell redirects # actually copy file descriptors) if ! svnadmin dump --incremental -r $revparam $repodir 2>&1 > $backupdir/$dumpfile_name then echo "$self: Error: \"svnadmin dump\" failed with error code $?" 2>&1 ## exit 6 fi fi # move old incrementals even if there was no backup done if [ $incr_present = y ] ; then for incr in $backupdir/incr-*.svndump ; do declare -i current_incr_rev=`incr_latest_rev ${incr##*/}` if [ $current_incr_rev -le $full_backup_rev ] ; then $handle_old_incr $incr fi done fi else echo "$self: Notice: nothing to back up (youngest rev is $youngest_rev and full backup is $full_backup_name)" fi