#! /bin/bash #this script contributed by Matthias Meyer #note that if your $Topdir has been changed, the script will ask you #the new location. # # Significant modifications by Robin Lee Powell, aka # rlpowell AT digitalkingdom DOT org, all of which are placed into the public domain. # usage="\ Usage: $0 -c [-d -b [-f] [-n]] | [-l] Delete specified backups. Attention! If a full backup is deleted, all incremental backups that depends on it will also be deleted. -c - client machine for which the backup was made -d - backup number to delete; if this is a full, deletes all dependent incrementals. Conflicts with -b -b - delete all backups before this date (YYYY-MM-DD); will only remove fulls if all dependent incrementals are gone. Conflicts with -d -f - run Backuppc_nightly afterwards to clean up the pool -l - list all backups for -n | --dry-run - Don't actually do anything, just say what would be done -h - this help Example: list backups of $0 -c -l remove backup #3 from $0 -c -d 3 remove all backups before 2007-07-02 from $0 -c -b 2007-07-02 " typeset -i len while test $# -gt 0; do case "$1" in -c | --client ) shift; client=$1; shift;; -b | --before ) shift; bDate=$1; shift;; -d | --delete ) shift; bNumber=$1; shift;; -f | --force ) nightly="true"; shift;; -n | --dry-run ) dryRun="true"; shift;; -l | --list ) list="true"; shift;; * | -h | --help) echo "$usage" exit 0 ;; esac done if [ -z "$client" ] || [ -z $list ] && [ -z $bNumber ] && [ -z $bDate ] then echo "$usage" exit 0 fi if [ "$bNumber" -a "$bDate" ] then echo "Please use either a specific number or a date, not both." echo "$usage" exit 0 fi if [ -e /engineyard/etc/backuppc/config.pl ] then TopDir=`grep $Conf{TopDir} /engineyard/etc/backuppc/config.pl | awk '{print $3}'` len=${#TopDir}-3 TopDir=${TopDir:1:len} else echo "/engineyard/etc/backuppc/config.pl not found" exit 1 fi ls $TopDir/pc > /dev/null 2>&1 while [ $? != 0 ] do read -p "examined $TopDir seems wrong. What is TopDir ? " TopDir ls $TopDir/pc > /dev/null 2>&1 done ls $TopDir/pc/$client > /dev/null 2>&1 if [ $? != 0 ] then echo "$client have no backups" exit 1 fi if [ ! -z $list ] then while read CLine do BackupNumber=`echo $CLine | awk '{print $1}'` BackupType=`echo $CLine | awk '{print $2}'` BackupTime=$(date -d @$(echo $CLine | awk '{ print $4 }')) echo "BackupNumber $BackupNumber - $BackupType-Backup from $BackupTime" done < $TopDir/pc/$client/backups exit 0 fi if [ ! -z $bNumber ] && [ ! -e $TopDir/pc/$client/$bNumber ] then echo "Backup Number $bNumber does not exist for client $client" exit 1 fi LogDir=`grep $Conf{LogDir} /engineyard/etc/backuppc/config.pl | awk '{print $3}'` len=${#LogDir}-3 LogDir=${LogDir:1:len} rm -f $TopDir/pc/$client/backups.new > /dev/null 2>&1 #********************************************************** # Two Processes # # Deleting a single backup is very different from deleting # everything before a date. # # If the user specifies a backup number, and the backup is a full, # well, the user said to delete it, so delete it and everything that # depends on it. This means walking the list forwards deleting # everything until we get to the next full. # # On the other hand, if the user asks to delete everything before a # particular date, and that date comes just after a full, deleting # the full and all the incrementals is not the expected behaviour at # all. # # As an example: If the first backup is a full on the 5th, and an # incremental for every day from then on, and it's the 30th, and the # user says to delete everything older than the 6th, deleting the # full *and all the incrementals* up to today (i.e. all the # backups!!) is probably not what they had in mind. # # This means that for -b we walk backwards in time so we know if the # fulls are still needed. # # So the two versions actually walk the backup list in opposite # directions. # # -_- # # My (Robin Lee Powell) apologies for the resulting code # duplication. It's hard to abstract a lot of things in bash. # #********************************************************** delete_dir() { dir=$1 if [ "$dryRun" ] then echo "not actually removing $dir, in dry run mode" else echo "remove $dir" echo "`date +\"%Y-%m-%d %T\"` BackupPC_deleteBackup delete $dir" >> $LogDir/LOG rm -fr $dir > /dev/null 2>&1 echo "`date +\"%Y-%m-%d %T\"` BackupPC_deleteBackup $dir deleted" >> $LogDir/LOG fi } swap_backups_file() { if [ "$dryRun" ] then echo "Not updating the backups list; in dry run mode. Compare $TopDir/pc/$client/backups.new to $TopDir/pc/$client/backups to see what would have changed." else echo "Updating $TopDir/pc/$client/backups" cp --backup=t $TopDir/pc/$client/backups $TopDir/pc/$client/backups.old # Need to un-revers if doing date-based if [ "$bDate" ] then sort -n $TopDir/pc/$client/backups.new > $TopDir/pc/$client/backups rm $TopDir/pc/$client/backups.new else mv $TopDir/pc/$client/backups.new $TopDir/pc/$client/backups fi echo "`date +\"%Y-%m-%d %T\"` BackupPC_deleteBackup $TopDir/pc/$client/backups updated" >> $LogDir/LOG fi } #************************* # -d / forwards / delete a full and all its friends handling #************************* if [ "$bNumber" ] then delete2full="false" while read CLine do BackupNumber=`echo $CLine | awk '{print $1}'` BackupTime=$(echo $CLine | awk '{ print $4 }') BackupType=`echo $CLine | awk '{print $2}'` if [ $BackupType == "full" ] then delete2full="false" fi if [ $BackupNumber == "$bNumber" ] || [ $delete2full == "true" ] then if [ $BackupType == "full" ] then if [ $delete2full == "false" ] then delete2full="true" else delete2full="false" fi fi bNumber=$BackupNumber delete_dir $TopDir/pc/$client/$bNumber fi if [ "$BackupNumber" != "$bNumber" ] then echo "$CLine" >> $TopDir/pc/$client/backups.new fi done < $TopDir/pc/$client/backups swap_backups_file fi #************************* # -b / backwards / delete a full only if all dependents are gone handling #************************* # What we do here is walk the list of backups backwards in time. If we skip # over an incremental, we mark a flag that says we need the corresponding full. # When we hit a full, we clear that flag. if [ "$bDate" ] then needFull="" sort -rn $TopDir/pc/$client/backups | while read CLine do BackupNumber=`echo $CLine | awk '{print $1}'` BackupTime=$(echo $CLine | awk '{ print $4 }') BackupType=`echo $CLine | awk '{print $2}'` testTime=$(date -d "$bDate" +%s) removed="" if [ "$BackupTime" -lt "$testTime" ] then if [ $BackupType == "full" -a "$needFull" ] then echo "Not deleting backup number $BackupNumber ; it is a full and there still exist incrementals that depend on it." else removed="true" delete_dir $TopDir/pc/$client/$BackupNumber fi else if [ $BackupType != "full" ] then needFull="true" fi fi # Clear the flag whether the backup was selected or not if [ $BackupType == "full" ] then needFull="" fi if [ ! "$removed" ] then echo "$CLine" >> $TopDir/pc/$client/backups.new fi done swap_backups_file fi #************************* # Run nightly cleanup, if requested #************************* if [ ! -z $nightly ] then if [ "$dryRun" ] then echo "Not doing a nightly run; in dry run mode." else path=${0%/BackupPC*} su backuppc -c "$path/BackupPC_serverMesg BackupPC_nightly run" su backuppc -c "$path/BackupPC_serverMesg log I honestly apologize for the inconvenience" echo `date "+%Y-%m-%d %T"` BackupPC_deleteBackup BackupPC_nightly politely scheduled via daemon >> $LogDir/LOG fi fi exit $?