backup2l-1.5/0000755000175000017500000000000011311237013012730 5ustar gundolfgundolfbackup2l-1.5/backup2l0000755000175000017500000011517011311236337014376 0ustar gundolfgundolf#!/bin/bash # backup2l --- low-maintenance backup tool # Copyright (c) 2001-2009 Gundolf Kiefer # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc.; 59 Temple Place, Suite 330; # Boston, MA 02111-1307, USA. ################################################## # Set global variables VER=1.5 # The following variable defines the commonly required tools. Additional tools # may be required by special functions, e. g. md5sum by the check functions. # Tool requirements are check in the do_* and show_* functions. COMMON_TOOLS="date find grep gzip gunzip sed awk mount umount" # The following variables define the format of *.list files. Modify at your own risk. FORMAT="%8s %TD %.8TT %04U.%04G %04m %p" # format for *.list files FILTER_NAME="sed 's#^\([-:. 0-9]*/\)\{3\}#/#'" # sed command for extracting names from .list, .new, ... files # (removes everything up to the 3rd "/"; two are contained in the time field) FILTER_CHOWN="sed 's#^\( *[^ ]\+\)\{3\} *0\{0,3\}\([0-9]\+\)\.0\{0,3\}\([0-9]\+\) \+\([0-9]*\)\{4\} \+/\(.*\)\$#\2:\3 \"\5\"#'" # sed command for extracting ownerships and names from .list files FILTER_CHMOD="sed 's#^\( *[^ ]\+\)\{4\} *\([0-9]\{4\}\) \+/\(.*\)\$#\2 \"\3\"#'" # sed command for extracting permissions and names from .list files #FILTER_UNIFY_NAME="sed 's#\\\\[0-7]\{3\}#?#g; s#[^a-zA-Z0-9_ .$%:~/=+\#\-]#?#g'" FILTER_UNIFY_NAME="sed 's#\\\\[0-7]\{3\}#?#g; s#[^a-zA-Z0-9_ .$%:~/=+\#\-]#?#g'" # replaces special and escaped characters by '?'; # only used when checking TOCs of fresh backup archives in order to avoid false alarms # The following alternative (donated by Christian Ludwig ) is slightly more precise, # but requires perl to be installed: #FILTER_UNIFY_NAME="perl -n -e 's{\\\\(\d{3})}{chr(oct(\$1))}eg; print;'" # On systems without GNU sed >= 3.0.2 such as Mac OS X, try the following alternatives... # ... settings (donated by Joe Auricchio ). #FILTER_CHOWN="perl -pe 's#^( *[^ ]+){3} *0{0,3}([0-9]+).0{0,3}([0-9]+) +([0-9]*){4} +/(.*)\$#\$2.\$3 \"\$5\"#'" #FILTER_CHMOD="perl -pe 's#^( *[^ ]+){4} *([0-9]{4}) +/(.*)\$#\$2 \"\$3\"#'" #COMMON_TOOLS="$COMMON_TOOLS perl" ################################################## # Misc. helpers require_tools () { local NOT_AVAIL="" for TOOL in $@; do if [ "`which $TOOL 2> /dev/null`" == "" ]; then NOT_AVAIL="$NOT_AVAIL $TOOL"; fi done if [[ "$NOT_AVAIL" != "" ]]; then echo "ERROR: The following required tool(s) cannot be found: $NOT_AVAIL" exit 3 fi } get_bid_of_name () { local SUFFIX=${1#$VOLNAME.} echo ${SUFFIX%%.*} } get_last_bid () { if [ -f $VOLNAME.1.list.gz ]; then REV_ARCH_LIST=(`ls $VOLNAME.*.list.gz | sort -r`) get_bid_of_name ${REV_ARCH_LIST[0]} else echo "0" # default if no archives exist yet fi } get_base_bid () { local BID=$(( $1 - 1 )) echo ${BID%%+(0)} } expand_bid_list () { local SUFFIX="$1" shift local ARCH_LIST="" local BID ARCH for BID in "$@"; do ARCH_LIST="$ARCH_LIST `ls -1 $BACKUP_DIR/$VOLNAME.$BID.$SUFFIX 2> /dev/null`" done local BID_LIST="" local ARCH for ARCH in $ARCH_LIST; do BID_LIST="$BID_LIST `get_bid_of_name ${ARCH#$BACKUP_DIR/}`" done echo $BID_LIST } do_symlink () { # tries to symlink & copies if not possible (e. g. on FAT32/Samba file systems) ln -s $@ &> /dev/null || cp -af $@ } readable_bytes_sum () { awk -v UNIT=$1 ' { B += $1 } END { KB=B / 1024.0; MB=KB / 1024.0; GB=MB / 1024.0; if ((GB>=1.0 && UNIT=="") || UNIT=="G" || UNIT=="g") print sprintf("%.1fG", GB); else if ((MB>=1.0 && UNIT=="") || UNIT=="M" || UNIT=="m") print sprintf("%.1fM", MB); else if ((KB>=1.0 && UNIT=="") || UNIT=="K" || UNIT=="k") print sprintf("%.0fK", KB); else print sprintf("%i ", B); } ' } ################################################## # Archive drivers & helpers BUILTIN_DRIVER_LIST="DRIVER_TAR DRIVER_TAR_GZ DRIVER_TAR_BZ2 DRIVER_AFIOZ" DRIVER_TAR () { case $1 in -test) require_tools tar echo "ok" ;; -suffix) echo "tar" ;; -create) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file tar cf $3 -T $4 --numeric-owner --no-recursion 2>&1 \ | grep -v 'tar: Removing leading .* from .* names' ;; -toc) # Arguments: $2 = BID, $3 = archive file name tar tf $3 | sed 's#^#/#' ;; -extract) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file tar x --same-permission --same-owner --numeric-owner -f $3 -T $4 2>&1 ;; esac } DRIVER_TAR_GZ () { case $1 in -test) require_tools tar echo "ok" ;; -suffix) echo "tar.gz" ;; -create) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file tar czf $3 -T $4 --no-recursion 2>&1 \ | grep -v 'tar: Removing leading .* from .* names' ;; -toc) # Arguments: $2 = BID, $3 = archive file name tar tzf $3 | sed 's#^#/#' ;; -extract) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file tar zx --same-permission --same-owner -f $3 -T $4 2>&1 ;; esac } DRIVER_TAR_BZ2 () { case $1 in -test) require_tools tar bzip2 echo "ok" ;; -suffix) echo "tar.bz2" ;; -create) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file tar cjf $3 -T $4 --no-recursion 2>&1 \ | grep -v 'tar: Removing leading .* from .* names' ;; -toc) # Arguments: $2 = BID, $3 = archive file name tar tjf $3 | sed 's#^#/#' ;; -extract) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file tar jx --same-permission --same-owner -f $3 -T $4 2>&1 ;; esac } DRIVER_AFIOZ () { case $1 in -test) require_tools afio echo "ok" ;; -suffix) echo "afioz" ;; -create) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file afio -Zo $3 < $4 2>&1 # afio -Zo - < $4 > $3 2>&1 ;; -toc) # Arguments: $2 = BID, $3 = archive file name afio -Zt $3 | sed 's#^#/#' # 's#^#/#;s#\.z$##' # afio -Zt - < $3 | sed 's#^#/#' # 's#^#/#;s#\.z$##' ;; -extract) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file afio -Zinw $4 $3 2>&1 # afio -Zinw $4 - < $3 2>&1 ;; esac } # Helpers require_drivers () { for DRIVER in $@; do local STATUS=`$DRIVER -test` if [ "$STATUS" != "ok" ]; then echo "ERROR: Archive driver not ready: $DRIVER" echo $STATUS exit 3 fi done } get_driver_and_file () # Argument: /. { local DRIVER for DRIVER in $USER_DRIVER_LIST $BUILTIN_DRIVER_LIST; do local SUFFIX=`$DRIVER -suffix` if [ "$SUFFIX" != "" -a -e "$1.$SUFFIX" ]; then echo "$DRIVER:$1.$SUFFIX" break fi done } get_driver () # Argument: { local DRIVER_AND_FILE=`get_driver_and_file $1` echo ${DRIVER_AND_FILE%%:*} } get_archive () # Argument: { local DRIVER_AND_FILE=`get_driver_and_file $1` echo ${DRIVER_AND_FILE##*:} } ################################################## # (Un)mount backup disk and (un)lock volume MUST_BE_UNMOUNTED=0 mount_dev () { if [ ! -d $BACKUP_DIR -a "$BACKUP_DEV" != "" ]; then echo "Mounting $BACKUP_DEV..." echo mount $BACKUP_DEV MUST_BE_UNMOUNTED=1 fi if [ ! -d $BACKUP_DIR ]; then echo "ERROR: $BACKUP_DIR not present (mount failed?)" umount $BACKUP_DEV >& /dev/null exit 3 fi if [ -e $BACKUP_DIR/$VOLNAME.lock ]; then if [ `ps -a | grep -af $BACKUP_DIR/$VOLNAME.lock | grep ${0##*/} | wc -l` -gt 0 ]; then echo "ERROR: Backup volume is locked." echo echo "Another instance is currently running. If you are sure that this is not" echo "the case, then remove the lock file '$BACKUP_DIR/$VOLNAME.lock' manually." exit 3 fi fi echo $$ > $BACKUP_DIR/$VOLNAME.lock } umount_dev () { rm -f $BACKUP_DIR/$VOLNAME.lock if [ $MUST_BE_UNMOUNTED == "1" ]; then echo echo "Unmounting $BACKUP_DEV..." umount $BACKUP_DEV MUST_BE_UNMOUNTED=0 fi } ################################################## # Purging purge () { local LV=${#1} : $[LV-=1] local LASTDIG=${1:LV:1} local PREFIX=${1%?} if [[ $LV == 0 ]]; then local CAND=$VOLNAME.$1*.list.gz else local CAND=$VOLNAME.$PREFIX[$LASTDIG-9]*.list.gz fi local ARCH BASE for ARCH in $CAND; do BASE=${ARCH%.list.gz} echo " removing <$BASE>" rm -fr $BASE.* done } name_cleanup () { local DST=0 local SRC=0 local SUFFIX SRC_FILE DST_FILE CHK while [[ $SRC -lt 9 ]]; do : $[SRC+=1] if [[ -f $VOLNAME.$SRC.list.gz ]]; then : $[DST+=1] if [[ "$SRC" != "$DST" ]]; then for SRC_FILE in $VOLNAME.$SRC* ; do local SUFFIX=${SRC_FILE##$VOLNAME.$SRC} local DST_FILE=$VOLNAME.$DST$SUFFIX if [ ${SUFFIX#*.} == "list.gz" ]; then echo " moving <${SRC_FILE%.list.gz}> to <${DST_FILE%.list.gz}>" fi if [ ${SUFFIX#*.} == "new.gz" -a -h $SRC_FILE ]; then rm -fr $SRC_FILE do_symlink ${DST_FILE%.new.gz}.list.gz $DST_FILE else mv $SRC_FILE $DST_FILE fi done for CHK in $VOLNAME.$DST*.check ; do sed "s/ $VOLNAME.$SRC/ $VOLNAME.$DST/" < $CHK > $TMP.check mv $TMP.check $CHK done fi fi done } do_purge () { if [[ "$1" == "" ]]; then echo "No archive specified - not purging anything!" else mount_dev cd $BACKUP_DIR BID_LIST=`expand_bid_list list.gz "$@"` if [[ "$BID_LIST" == "" ]]; then echo "No archive(s) matching '$@' found - nothing to purge." else echo "Purging <$BID_LIST>..." for BID in $BID_LIST; do purge $BID done name_cleanup fi fi } ################################################## # Checking create_check () { # Parameter: single BID local BID=$1 if [[ "${#BID}" == "1" ]]; then local BASE_FILE="" else local BASE_FILE=$VOLNAME.`get_base_bid $BID`.list.gz fi echo "Creating check file for <$VOLNAME.$BID>..." rm -f $VOLNAME.$BID.check md5sum `find . -follow -path "*/$VOLNAME.$BID.*" -type f` $BASE_FILE > $TMP.check mv $TMP.check $VOLNAME.$BID.check } do_create_check () { require_tools $COMMON_TOOLS md5sum mount_dev cd $BACKUP_DIR rm -fr $TMP.* if [[ "$1" == "" ]]; then BID_LIST=`expand_bid_list list.gz "*"` else BID_LIST=`expand_bid_list list.gz "$@"` fi for BID in $BID_LIST; do if [[ "$1" != "" || ! -f $VOLNAME.$BID.check ]]; then create_check $BID fi done } check_arch () { # Parameter: single BID local BID=$1 echo "Checking archive <$VOLNAME.$BID>..." # Check the plausibility by file existence... local SUFFIX for SUFFIX in list.gz skipped.gz new.gz obsolete.gz error.gz; do if [[ ! -f $VOLNAME.$BID.$SUFFIX ]]; then echo " ERROR: File '$VOLNAME.$BID.$SUFFIX' does not exist." fi done if [[ ${#BID} -gt 1 ]]; then if [[ ! -f $VOLNAME.`get_base_bid $BID`.list.gz ]]; then echo " ERROR: Base archive <$VOLNAME.`get_base_bid $BID`> does not exist." fi fi # Check check sum file... if [[ -f $VOLNAME.$BID.check ]]; then md5sum -c $VOLNAME.$BID.check 2>&1 | sed 's/^/ /' if [[ $(( `grep $VOLNAME.$BID. $VOLNAME.$BID.check | wc -l` )) -lt 6 ]]; then echo " ERROR: Check file seems to be corrupted." fi else echo " Information: no check file" fi } do_check () { require_tools $COMMON_TOOLS md5sum mount_dev cd $BACKUP_DIR rm -fr $TMP.* if [[ "$1" == "" ]]; then BID_LIST=`expand_bid_list list.gz "*"` else BID_LIST=`expand_bid_list list.gz "$@"` fi for BID in $BID_LIST; do check_arch $BID done } ################################################## # Print summary show_summary () { require_tools $COMMON_TOOLS echo "Summary" echo "=======" echo cd $BACKUP_DIR if [[ `echo $VOLNAME.*.list.gz` == "" ]]; then echo "No backup archives present." else echo "Backup Date Time | Size | Skipped Files+D | New Obs. | Err." echo "------------------------------------------------------------------------------" for f in `ls $VOLNAME.*.list.gz` ; do p=${f%%.list.gz} size="`du -sbL $p.* | readable_bytes_sum $SIZE_UNITS`" skipped=$( gunzip -c $p.skipped.gz | wc -l ) total=$( gunzip -c $p.list.gz | wc -l ) new_files=$( gunzip -c $p.new.gz | wc -l ) obsolete=$( gunzip -c $p.obsolete.gz | wc -l ) errors=$( gunzip -c $p.error.gz | grep '<' | wc -l ) printf "%-12s %s %s |%8s |%8i %8i |%5i %5i |%5i\n" \ $p $(date -r $p.list.gz +"%Y-%m-%d %H:%M") "$size" \ $skipped $total $new_files $obsolete $errors done fi echo df -h $BACKUP_DIR } ################################################## # backup compute_level_and_bids () { # Determine level and base BID for new backup... if [ ! -f $VOLNAME.1.list.gz ]; then LEVEL="0"; else if [[ "$1" -gt 0 || "$1" == "0" ]]; then LEVEL=$1 else LEVEL=$MAX_LEVEL fi BASE_BID=`get_last_bid` while [[ "$LEVEL" -gt 0 && "${BASE_BID:$LEVEL:1}" -gt "$[MAX_PER_LEVEL-1]" ]]; do : $[LEVEL-=1] done BASE_BID=${BASE_BID:0:$[LEVEL+1]} BASE_BID=${BASE_BID%%+(0)} fi # Determine new archive's name if [ "$LEVEL" != "0" ]; then NEW_BID=$BASE_BID while [ ${#NEW_BID} -le $LEVEL ]; do NEW_BID=${NEW_BID}"0" done : $[NEW_BID+=1] else NEW_BID="1" while [ -f $VOLNAME.$NEW_BID.list.gz ]; do : $[NEW_BID+=1] done fi } prepare_backup () { # Input: Comment ("Preparing"/"Estimating"), selected level (optional) # Output: $LEVEL, $BASE_BID, $NEW_BID # $TMP.list $TMP.skipped $TMP.new $TMP.obsolete $TMP.files # Determine level, base BID and new BID compute_level_and_bids $2 if [ "$LEVEL" != "0" ]; then echo "$1 differential level-$LEVEL backup <$VOLNAME.$NEW_BID> based on <$VOLNAME.$BASE_BID>..." else echo "$1 full backup <$VOLNAME.$NEW_BID>..." fi # Determine main list & which files are new or obsolete... set -f # pathname expansion off as it may destroy $SKIPCOND OLDIFS=$IFS IFS="" find ${SRCLIST[*]} \( \( ${SKIPCOND[*]} \) \ \( -type d -fprintf $TMP.skipped.dirs "$FORMAT/\n" -o -fprintf $TMP.skipped.files "$FORMAT\n" \) \) \ -o \( -not -type d -printf "$FORMAT\n" -o -printf "$FORMAT/\n" \) \ | sed -e 's# \([0-9]*\..* /.*\)# 00\1#' -e 's# \([0-9]*\..* /\)# 0\1#' \ -e 's#\(\. *\) \([0-9 ]* /\)#\100\2#' -e 's#\. \([0-9 ]* /\)#\.0\1#' \ | sort -k 6 \ > $TMP.list IFS=$OLDIFS cat $TMP.skipped.dirs $TMP.skipped.files \ | sed -e 's# \([0-9]*\..* /.*\)# 00\1#' -e 's# \([0-9]*\..* /\)# 0\1#' \ -e 's#\(\. *\) \([0-9 ]* /\)#\100\2#' -e 's#\. \([0-9 ]* /\)#\.0\1#' \ | sort -k 6 \ > $TMP.skipped # WORKAROUND: The reason for the two 2-line sed's above is a bug in find 4.1.7, where the format # directives %04x do not produce leading 0's. set +f if [ $LEVEL != 0 ]; then gunzip -c $VOLNAME.$BASE_BID.list.gz | sort -k 6 | diff - $TMP.list > $TMP.diff # WORKAROUND: 'sort -k 6' can be removed if sort uses the same options for every user, # which seems to be not the case!! if [[ $(( `grep "<" $TMP.diff | tail -n 1 | sed 's# /.*##' | wc -w` )) == 5 ]]; then echo " file '$VOLNAME.$BASE_BID.list.gz' has an old format - using compatibility mode" sed 's#[0-9]*\.[0-9 ]* /#- /#' < $TMP.list > $TMP.list.old gunzip -c $VOLNAME.$BASE_BID.list.gz | sort -k 5 | diff - $TMP.list.old > $TMP.diff fi grep "<" $TMP.diff | sed 's/^< //' > $TMP.obsolete grep ">" $TMP.diff | sed 's/^> //' > $TMP.new else # by convention, the *.new and *.obsolete files always exist, although redundant for level-0 backups do_symlink $TMP.list $TMP.new touch $TMP.obsolete fi eval "$FILTER_NAME" < $TMP.new | grep -v "/$" > $TMP.files # extract real files # Print statistics... echo " " `wc -l < $TMP.files` / `grep -v '/$' $TMP.list | wc -l` "file(s)," \ `grep '/$' $TMP.new | wc -l` / `grep '/$' $TMP.list | wc -l` "dir(s)," \ `grep -v '/$' $TMP.new | readable_bytes_sum`"B /" \ `grep -v '/$' $TMP.list | readable_bytes_sum`"B (uncompressed)" echo " skipping:" `grep -v '/$' $TMP.skipped | wc -l` "file(s)," \ `grep '/$' $TMP.skipped | wc -l` "dir(s)," \ `grep -v '/$' $TMP.skipped | readable_bytes_sum`"B (uncompressed)" } show_backup_estimates () { require_tools $COMMON_TOOLS mount_dev cd $BACKUP_DIR rm -fr $TMP.* prepare_backup "Estimating" $1 rm -f $TMP.* } do_backup () { require_tools $COMMON_TOOLS require_drivers $CREATE_DRIVER # Print time stamp & mount backup drive... date echo mount_dev # Run pre-backup echo "Running pre-backup procedure..." PRE_BACKUP # Operate in destination directory cd $BACKUP_DIR rm -fr $TMP.* # Remove old backups... echo echo "Removing old backups..." name_cleanup # should not do anything in normal cases compute_level_and_bids $1 SAVED_LEVEL=$LEVEL # Rotate level-0 backups if necessary... TOO_MANY=$(( ${NEW_BID:0:1} - $MAX_FULL )) if [[ $TOO_MANY -gt 0 ]]; then N=0 while [[ $N -lt $TOO_MANY ]]; do : $[N+=1] purge $N done name_cleanup compute_level_and_bids $SAVED_LEVEL fi # Remove old differential backups... if [[ $BASE_BID -gt 0 ]]; then ARCH_LIST=(`ls $VOLNAME.*1.list.gz`) LV=0 while [ $LV -le 10 ]; do : $[LV+=1] MATCHCNT=0 N=${#ARCH_LIST[*]} while [[ $N -gt 0 ]]; do : $[N-=1] BID=`get_bid_of_name ${ARCH_LIST[$N]}` if [[ ${#BID} == $[LV+1] && ${BID:0:$LV} != ${NEW_BID:0:$LV} ]]; then : $[MATCHCNT+=1] if [[ $MATCHCNT -gt $GENERATIONS ]]; then purge $BID fi fi done done fi # Prepare backup... echo prepare_backup "Preparing" $SAVED_LEVEL # Create and verify archive file... echo echo "Creating archive using '"$CREATE_DRIVER"'..." ARCH_SUFFIX=`$CREATE_DRIVER -suffix` $CREATE_DRIVER -create $NEW_BID $TMP.$ARCH_SUFFIX $TMP.files 2>&1 | sed 's/^/ /' if [ "$ARCH_SUFFIX" != "" ]; then echo "Checking TOC of archive file (< real file, > archive entry)..." $CREATE_DRIVER -toc $NEW_BID $TMP.$ARCH_SUFFIX | eval "$FILTER_UNIFY_NAME" > $TMP.toc eval "$FILTER_UNIFY_NAME" < $TMP.files | diff - $TMP.toc | tee $TMP.error | sed 's/^/ /' fi # Move files in place... gzip -9 $TMP.list $TMP.skipped $TMP.obsolete $TMP.error if [ $LEVEL != 0 ]; then gzip -9 $TMP.new else rm -f $TMP.new # Here we don't use the do_symlink function because we need to copy or # symlink different files, depending on wether we copy or symlink ln -s $VOLNAME.$NEW_BID.list.gz $TMP.new.gz &> /dev/null || cp -af $TMP.list.gz $TMP.new.gz fi for SUFFIX in skipped.gz new.gz obsolete.gz error.gz $ARCH_SUFFIX list.gz ; do # *.list.gz has to be the last for transaction safety mv $TMP.$SUFFIX $VOLNAME.$NEW_BID.$SUFFIX done # Create check file if requested... if [[ "$CREATE_CHECK_FILE" == "1" ]]; then create_check $NEW_BID fi # Run post-backup echo echo "Running post-backup procedure..." POST_BACKUP # print summary and finish... rm -f $TMP.* echo date echo echo show_summary } ################################################## # show_availability show_availability () { # parameters: masks require_tools $COMMON_TOOLS mount_dev 1>&2 cd $BACKUP_DIR rm -fr $TMP.* MASK="$@" if [ "$MASK" = "" ]; then MASK="/" fi echo "Listing available files..." 1>&2 for F in $VOLNAME.*.list.gz; do BID=`get_bid_of_name $F` FBID=$BID while [[ ${#FBID} -lt 5 ]]; do FBID=$FBID" " done for X in "$MASK"; do gunzip -c $VOLNAME.$BID.obsolete.gz | grep "$X" | sed "s/^/$VOLNAME.$FBID - /" gunzip -c $VOLNAME.$BID.new.gz | grep "$X" | sed "s/^/$VOLNAME.$FBID + /" done done cd / umount_dev 1>&2 } ################################################## # show_location get_location () { # parameter 1: BID of snapshot # other parameters: masks if [[ $1 == "head" ]]; then BID=`get_last_bid` else BID=$1 fi if [[ ! -f $VOLNAME.$BID.list.gz ]]; then echo "ERROR: Specified backup archive <$VOLNAME.$BID> does not exist!" exit 3 fi shift MASK_LIST="$@" if [ "$MASK_LIST" = "" ]; then MASK_LIST="/" fi # determine active files... for MASK in "$MASK_LIST"; do gunzip -c $VOLNAME.$BID.list.gz | grep "$MASK" | tee $TMP.found | grep '/$' >> $TMP.dirs # dirs go to $TMP.dirs WITH attributes grep -v '/$' $TMP.found | eval "$FILTER_NAME" >> $TMP.left # files go to $TMP.left WITHOUT attributes done echo "Active files in <$VOLNAME.$BID>:" `wc -l < $TMP.left` sort < $TMP.left > $TMP.nowleft mv $TMP.nowleft $TMP.left # generate location list LAST_BID="xxx" touch $TMP.located touch $TMP.archlist touch $TMP.noarch while [ ${#LAST_BID} -gt 1 -a `wc -l < $TMP.left` -gt 0 ]; do gunzip -c $VOLNAME.$BID.new.gz | eval "$FILTER_NAME" | sort | comm -1 -2 - $TMP.left | tee $TMP.found \ | comm -1 -3 - $TMP.left > $TMP.nowleft local FOUND=`wc -l < $TMP.found` printf " found in %-12s%5i (%5i left)\n" \ "$VOLNAME.$BID:" $FOUND `wc -l < $TMP.nowleft` if [ "$FOUND" -gt 0 ]; then sed "s/^/$VOLNAME.$BID: /" < $TMP.found >> $TMP.located mv $TMP.nowleft $TMP.left DRIVER=`get_driver $VOLNAME.$BID` if [ "$DRIVER" = "" ]; then echo $VOLNAME.$BID >> $TMP.noarch else echo $VOLNAME.$BID:$DRIVER >> $TMP.archlist fi fi LAST_BID=$BID BID=`get_base_bid $LAST_BID` done echo # leaves $TMP.left, $TMP.found, $TMP.located, $TMP.dirs, $TMP.archlist, $TMP.noarch } show_location () { # parameter 1: BID of snapshot # other parameters: masks require_tools $COMMON_TOOLS comm mount_dev 1>&2 cd $BACKUP_DIR rm -fr $TMP.* get_location "$@" 1>&2 echo "Listing locations..." 1>&2 cat $TMP.located sed 's#^#NOT FOUND: #' < $TMP.left if [ `wc -l < $TMP.noarch` -gt 0 ]; then echo -e "\nTo restore, the archive files of the following backups are missing and" \ "\nhave to be copied or linked into $BACKUP_DIR:" 1>&2 sed 's#^# #' < $TMP.noarch 1>&2 else echo -e "\nAll required archive files are present in $BACKUP_DIR." 1>&2 fi rm -f $TMP.* cd / umount_dev 1>&2 } ################################################## # do_restore do_restore () { # parameter 1: . of snapshot # other parameters: masks require_tools $COMMON_TOOLS comm mount_dev pushd $BACKUP_DIR > /dev/null rm -fr $TMP.* # determine which file to get from which archive get_location "$@" popd > /dev/null if [ `wc -l < $BACKUP_DIR/$TMP.noarch` -gt 0 ]; then echo "Cannot access archive file(s) of the following backup(s):" sed 's#^# #' < $BACKUP_DIR/$TMP.noarch echo -e "\nNothing has been restored." else # check availability of all required drivers in advance... require_drivers `sed 's#^.*:##' < $BACKUP_DIR/$TMP.archlist` # create directories... DIRS=`wc -l < $BACKUP_DIR/$TMP.dirs` if [ $DIRS -gt 0 ]; then echo "Restoring" $DIRS "directories..." eval "$FILTER_NAME" < $BACKUP_DIR/$TMP.dirs | sed 's#^/\(.*\)$#"\1"#' | xargs -l1 mkdir -p eval "$FILTER_CHMOD" < $BACKUP_DIR/$TMP.dirs | xargs -l1 chmod eval "$FILTER_CHOWN" < $BACKUP_DIR/$TMP.dirs | xargs -l1 chown fi # process all archives... echo "Restoring files..." for ARCH_AND_DRIVER in `cat $BACKUP_DIR/$TMP.archlist`; do ARCH=${ARCH_AND_DRIVER%%:*} BID=${ARCH#$VOLNAME.} DRIVER=${ARCH_AND_DRIVER##*:} SUFFIX=`$DRIVER -suffix` grep "^$ARCH:" $BACKUP_DIR/$TMP.located \ | sed -e "s#^$ARCH: /##" -e 's#\([][*?]\)#\\\1#g' \ > $BACKUP_DIR/$TMP.curlist # The second sed expression escapes special glob(7) characters ([]*?). FILES=`wc -l < $BACKUP_DIR/$TMP.curlist` echo " $ARCH.$SUFFIX:" $FILES "file(s) using '"$DRIVER"'" $DRIVER -extract $BID $BACKUP_DIR/$ARCH.$SUFFIX $BACKUP_DIR/$TMP.curlist | sed "s/^/ /" done fi # Cleanup... rm -f $BACKUP_DIR/$TMP.* } ################################################## # External archiving do_external () { require_tools $COMMON_TOOLS split tail md5sum CTRL_EXPR="$VOLNAME\.[0-9]*\.((list)|(new)|(obsolete)|(skipped)|(error)|(check))" mount_dev CAPACITY=$(( $1 * 1024 )) shift MAXFREE=$(( $1 * 1024 )) shift # determine BID_LIST... BID_LIST=`expand_bid_list list.gz "$@"` # determine distribution... LAST_DISK=1 REMAINING=$CAPACITY rm -f TMP.xdist 2> /dev/null touch TMP.xdist for BID in $BID_LIST; do CTRL_SIZE=`ls -1s $BACKUP_DIR/$VOLNAME.$BID.* | grep -E "$CTRL_EXPR" \ | awk '{ sum += $1 } END { print sum }'` if [ $CTRL_SIZE -gt $REMAINING -a $REMAINING -lt $CAPACITY ]; then : $[ LAST_DISK += 1 ] REMAINING=$CAPACITY fi LAST_DISK_DIR=`printf "data-%02d" $LAST_DISK` ls -1s $BACKUP_DIR/$VOLNAME.$BID.* | grep -E "$CTRL_EXPR" \ | sed "s#$BACKUP_DIR#$LAST_DISK_DIR#" >> TMP.xdist : $[ REMAINING -= $CTRL_SIZE ] for ARCHIVE_PATH in `ls -1d $BACKUP_DIR/$VOLNAME.$BID.* | grep -vE "$CTRL_EXPR"`; do ARCHIVE=${ARCHIVE_PATH##*/} if [ -f $BACKUP_DIR/$ARCHIVE ]; then DATA_SIZE=`ls -1s $BACKUP_DIR/$ARCHIVE | sed 's/ [^ ]*$//'` if [ $DATA_SIZE -gt $REMAINING -a $REMAINING -lt $MAXFREE ]; then : $[ LAST_DISK += 1 ] REMAINING=$CAPACITY fi if [ $DATA_SIZE -le $REMAINING ]; then LAST_DISK_DIR=`printf "data-%02i" $LAST_DISK` ls -1s $BACKUP_DIR/$ARCHIVE | sed "s#$BACKUP_DIR#$LAST_DISK_DIR#" >> TMP.xdist : $[ REMAINING -= $DATA_SIZE ] else SPLIT_NO=1 while [ $DATA_SIZE -gt $REMAINING ]; do LAST_DISK_DIR=`printf "data-%02i" $LAST_DISK` printf "%4i $LAST_DISK_DIR/$ARCHIVE.%02i\n" $REMAINING $SPLIT_NO >> TMP.xdist : $[ DATA_SIZE -= $REMAINING ] : $[ SPLIT_NO += 1 ] : $[ LAST_DISK += 1 ] REMAINING=$CAPACITY done LAST_DISK_DIR=`printf "data-%02i" $LAST_DISK` printf "%4i $LAST_DISK_DIR/$ARCHIVE.%02i\n" $DATA_SIZE $SPLIT_NO >> TMP.xdist : $[ REMAINING -= $DATA_SIZE ] fi else echo "WARNING: Cannot handle directory: $ARCHIVE (skipping)" echo fi done done SPACE=$(( (`grep "\.[0-9]*$" TMP.xdist | awk '{ sum += $1 } END { print sum }'` + 1023) / 1024 )) cat << EOT I am about to split and combine the selected backup archives into directories of equal size, so that they can be stored on a set of removable media (e. g. CDs). If 'cdlabelgen' is installed, I will create CD covers. It is up to you to burn the CDs or store the data in whichever way you like. All files are generated in the current working directory ($PWD). Make sure it is empty! In order to save disk space, only symbolic links will be generated wherever possible. Make sure that they are followed by your CD-burn/storage tool and that the backup device is mounted! You have selected a medium capacity of $(( $CAPACITY / 1024 )) MB with a maximum waste of $(( $MAXFREE /1024 )) MB per medium. The selected BIDs are: $BID_LIST I need about $SPACE MB of disk space in the current directory. You will get $LAST_DISK volume(s). EOT read -p "Do you want to see details? [y/N] " ANSWER if [[ "$ANSWER" == "y" ]]; then echo -e "\nSize Volume/File\n=======================================" cat TMP.xdist fi echo read -p "Do you want to continue? [y/N] " ANSWER if [[ "$ANSWER" != "y" ]]; then rm -f TMP.xdist return fi echo -e "\nCreating links..." rm -fr data-?? # create directories... sed -e 's#^[ 0-9]*##' -e 's#/.*$##' < TMP.xdist | sort -u | xargs -l1 mkdir -p # create links... for FILE in `grep -v '\.[0-9]*$' TMP.xdist | sed 's#^[ 0-9]*##'`; do do_symlink $BACKUP_DIR/${FILE#data-??/} $FILE done echo "Splitting large files..." for FILE in `grep '\.01$' TMP.xdist | sed -e 's#^[ 0-9]*##' -e 's#.01$##'`; do ORG_FILE=${FILE#data-??/} echo " $ORG_FILE" HEAD_SIZE=$(( `grep $FILE TMP.xdist | sed 's/ [^ ]*$//'` * 1024 )) head $BACKUP_DIR/$ORG_FILE -c $HEAD_SIZE > $FILE.01 tail $BACKUP_DIR/$ORG_FILE -c +$(( $HEAD_SIZE + 1 )) | split -b $(( $CAPACITY * 1024 )) - TMP.split. SPLIT_NO=2 for SPLIT in `ls -1 TMP.split.*`; do DST_FILE=`printf "$ORG_FILE.%02i" $SPLIT_NO` DST=`grep $DST_FILE TMP.xdist | sed 's#^[ 0-9]*##'` mv $SPLIT $DST : $[ SPLIT_NO += 1 ]; done done # create self-check files... cat << EOT I can now generate check scripts for each volume that can later be used to verify the integrity of all files. This is e.g. useful if, in a couple of years, you want to know whether your backup media are still readable. Then simply mount your media and type '. check_these_files.sh' inside the media's main directory. EOT read -p "Create self-check scripts? [Y/n] " ANSWER if [[ "$ANSWER" != "n" ]]; then echo echo "Creating self-check scripts..." for DISK in data-??; do echo " $DISK" cd $DISK DST_FILE="check_these_files.sh" rm -f $DST_FILE FILES=`find . -follow -type f` echo "#!/bin/sh" > $DST_FILE echo >> $DST_FILE echo "echo \"This script has been auto-generated by backup2l v$VER.\"" >> $DST_FILE echo "echo \"Verifying file(s) using md5sum(1)...\"" >> $DST_FILE echo >> $DST_FILE echo "md5sum -v -c << EOF" >> $DST_FILE md5sum $FILES >> $DST_FILE echo "EOF" >> $DST_FILE cd .. done fi # create CD labels if which cdlabelgen > /dev/null; then echo echo "Creating CD labels..." read -p " Enter CD title [Backup]: " ANSWER if [[ "$ANSWER" == "" ]]; then ANSWER="Backup" fi if [ -r /usr/share/cdlabelgen/penguin.eps ]; then TRAYPIC="-e /usr/share/cdlabelgen/penguin.eps -S 0.5" else TRAYPIC="" fi for DISK in data-??; do DISK_NO=${DISK#data-} echo -e "\nContents\n========\n" > contents-$DISK_NO.txt grep $DISK TMP.xdist | sed -e 's#^.*/##' -e 's#\.list\.gz# - control files#' \ | grep -vE "$CTRL_EXPR" >> contents-$DISK_NO.txt cdlabelgen -c "$ANSWER" -s "${DISK_NO#0} of $LAST_DISK" -d `date -I` \ -f contents-$DISK_NO.txt $TRAYPIC -o cd-cover-$DISK_NO.ps done fi # clean up rm -f TMP.xdist } ################################################## # Usage & banner banner () { echo backup2l v$VER by Gundolf Kiefer echo } usage () { banner cat << EOF Usage: backup2l [-c ] [-t ] Where -c | --conf : specifies configuration file [/etc/backup2l.conf] -t | --time : specifies backup ID as a point-in-time for --locate and --restore : -h | --help : Help -b | --backup [] : Create new backup -e | --estimate [] : Like -b, but nothing is really done -s | --get-summary : Show backup summary -a | --get-available : Show all files in all backups containing in their path names -l | --locate [] : Show most recent backup location of all active files matching -r | --restore [] : Restore active files matching into current directory -p | --purge : Remove the specified backup archive(s) and all depending backups -v | --verify [] : Verify the specified / all backup archive(s) -m | --make-check [] : Create md5 checksum file for the specified archive(s) / wherever missing -x | --extract : Split and collect files to be stored on removable media (e. g. CDs) EOF echo "Built-in archive drivers:" $BUILTIN_DRIVER_LIST if [ "$USER_DRIVER_LIST" != "" ]; then echo "User-defined drivers: " $USER_DRIVER_LIST fi } ################################################## # Main shopt -s nullglob shopt -s extglob unset LANG LC_ALL LC_COLLATE # otherwise: unpredictable sort order in sort, ls # Set defaults... CREATE_DRIVER="DRIVER_TAR_GZ" # works with last stable version 1.01 # Read & validate setup... CONF_FILE="/etc/backup2l.conf" if [ "$1" = "-c" -o "$1" = "--conf" ]; then shift CONF_FILE=$1 shift fi if [ -f "$CONF_FILE" ]; then . $CONF_FILE else echo "Could not open configuration file '$CONF_FILE'. Aborting." exit 3 fi if [ "$UNCONFIGURED" = "1" ]; then banner echo -e "The configuration file '$CONF_FILE' has to be edited before using ${0##*/}.\n" echo -e "For help, look into the comments or read the man page.\n" exit 3 fi if [ "$VOLNAME" = "" -o "$SRCLIST" = "" -o "${SKIPCOND[*]}" = "" -o "$BACKUP_DIR" = "" -o \ "$MAX_LEVEL" = "" -o "$MAX_PER_LEVEL" = "" -o "$MAX_FULL" = "" ]; then echo "ERROR: The configuration file '$CONF_FILE' is missing or incomplete." exit 3 fi if [ "$FOR_VERSION" = "" ]; then FOR_VERSION="0.9" fi if [[ "$FOR_VERSION" < "1.1" || "$FOR_VERSION" > "${VER%-*}" ]]; then banner cat << EOF The configuration file '$CONF_FILE' seems to be written for version $FOR_VERSION and may be incompatible with this version of backup2l. The following variables have been added, removed or their syntax may have changed. Details can be found in first_time.conf and the man page. 1.1 : CREATE_DRIVER, USER_DRIVER_LIST 0.93: POST_BACKUP 0.91: SRCLIST, SKIPCOND, FOR_VERSION If you think your configuration file is correct, please change the value of FOR_VERSION. EOF exit 3 fi TMP="TMP.$VOLNAME" # Read time point if given if [ "$1" = "-t" -o "$1" = "--time" ]; then shift BID=${1#$VOLNAME.} shift else BID="head" fi # Go ahead... case $1 in -h | --help) usage ;; -e | --estimate) banner show_backup_estimates "$2" ;; -b | --backup) banner do_backup "$2" ;; -s | --get-summary) banner mount_dev show_summary ;; -a | --get-available) banner 1>&2 shift show_availability "$@" ;; -l | --locate) banner 1>&2 shift show_location $BID "$@" ;; -r | --restore) banner shift do_restore $BID "$@" ;; -p | --purge) banner shift do_purge "$@" ;; -v | --verify) banner shift do_check "$@" ;; -m | --make-check) banner shift do_create_check "$@" ;; -x | --extract) banner shift if [[ "$3" == "" ]]; then usage else do_external "$@" fi ;; *) if [ "$AUTORUN" = "1" ]; then banner do_backup else usage fi ;; esac # Unmount backup device if it was mounted cd / # avoid "device is busy" during unmount umount_dev backup2l-1.5/first-time.conf0000644000175000017500000003143511311236044015673 0ustar gundolfgundolf################################################## # Configuration file for backup2l # ################################################## # Define the backup2l version for which the configuration file is written. # This way, future versions can automatically warn if the syntax has changed. FOR_VERSION=1.5 ################################################## # Volume identification # This is the prefix for all output files; # multiple volumes can be handled by using different configuration files VOLNAME="all" ################################################## # Source files # List of directories to make backups of. # All paths MUST be absolute and start with a '/'! SRCLIST=(/etc /root /home /var/mail /usr/local) # The following expression specifies the files not to be archived. # See the find(1) man page for further info. It is discouraged to # use anything different from conditions (e. g. actions) as it may have # unforeseeable side effects. # This example skips all files and directories with a path name containing # '.nobackup' and all .o files: SKIPCOND=(-path "*.nobackup*" -o -name "*.o") # Some background on 'SKIPCOND': The method of using a find(1) expression to determine # files to backup or to skip is very powerful. Some of the following examples result from feature # requests by various users who were not always aware that their "feature" was already implemented. ;-) # # If you want to exclude several directories use the following expression: # SKIPCOND=(-path '/path1' -o -path '/path1/*' -o -path '/path2' -o -path '/path2/*') # # If you do not have anything to skip, use: # SKIPCOND=(-false) # "SKIPCOND=()" does not work # # To skip directory trees (for performance reasons) you can add the '-prune' action to your SKIPCOND setting, e.g.: # SKIPCOND=( -name "unimportant_dir" -prune ) # # To prevent backup2l from crossing filesystem boundaries you can add '-xdev' to your SKIPCOND setting. ################################################## # Destination # Mount point of backup device (optional) #BACKUP_DEV="/disk2" # Destination directory for backups; # it must exist and must not be the top-level of BACKUP_DEV BACKUP_DIR="/disk2/backup" ################################################## # Backup parameters # Number of levels of differential backups (1..9) MAX_LEVEL=3 # Maximum number of differential backups per level (1..9) MAX_PER_LEVEL=8 # Maximum number of full backups (1..8) MAX_FULL=2 # For differential backups: number of generations to keep per level; # old backups are removed such that at least GENERATIONS * MAX_PER_LEVEL # recent versions are still available for the respective level GENERATIONS=1 # If the following variable is 1, a check file is automatically generated CREATE_CHECK_FILE=1 ################################################## # Pre-/Post-backup functions # This user-defined bash function is executed before a backup is made PRE_BACKUP () { echo " pre-backup: nothing to do" # e. g., shut down some mail/db servers if their files are to be backup'ed # On a Debian system, the following statements dump a machine-readable list of # all installed packages to a file. #echo " writing dpkg selections to /root/dpkg-selections.log..." #dpkg --get-selections | diff - /root/dpkg-selections.log > /dev/null || dpkg --get-selections > /root/dpkg-selections.log } # This user-defined bash function is executed after a backup is made POST_BACKUP () { # e. g., restart some mail/db server if its files are to be backup'ed echo " post-backup: nothing to do" } ################################################## # Misc. # Create a backup when invoked without arguments? AUTORUN=0 # Size units SIZE_UNITS="" # set to "B", "K", "M" or "G" to obtain unified units in summary list # Remove this line after the setup is finished. UNCONFIGURED=1 # Archive driver for new backups (optional, default = "DRIVER_TAR_GZ") # CREATE_DRIVER="DRIVER_MY_AFIOZ" ################################################## # User-defined archive drivers (optional) # This section demonstrates how user-defined archive drivers can be added. # The example shows a modified version of the "afioz" driver with some additional parameters # one may want to pass to afio in order to tune the speed, archive size etc. . # An archive driver consists of a bash function named # "DRIVER_" implementing the (sometimes simple) operations "-test", "-suffix", # "-create", "-toc", and "-extract". # If you do not want to write your own archive driver, you can remove the remainder of this file. # USER_DRIVER_LIST="DRIVER_MY_AFIOZ" # uncomment to register the driver(s) below (optional) DRIVER_MY_AFIOZ () { case $1 in -test) # This function should check whether all prerequisites are met, especially if all # required tools are installed. This prevents backup2l to fail in inconvenient # situations, e. g. during a backup or restore operation. If everything is ok, the # string "ok" should be returned. Everything else is interpreted as a failure. require_tools afio # The function 'require_tools' checks for the existence of all tools passed as # arguments. If one of the tools is not found by which(1), an error message is # displayed and the function does not return. echo "ok" ;; -suffix) # This function should return the suffix of backup archive files. If the driver #ädoes not create a file (e. g. transfers the backup data immediately to a tape # or network device), an empty string has to be returned. backup2l uses this suffix # to select a driver for unpacking. If a user-configured driver supports the same # suffix as a built-in driver, the user driver is preferred (as in this case). echo "afioz" ;; -create) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file # This function is called to create a backup file. The argument $3 is the full file # name of the archive file including path and suffix. $4 contains an alphabetically # sorted list of files (full pathname) to be backed up. Directories are not contained, # they are handled by backup2l directly without using the driver. # All output to stderr should be directed to stdout ("2>&1"). afio -Zo -G 9 -M 30m -T 2k $3 < $4 2>&1 # This line passes some additional options to afio (see afio(1)): # '-G 9' maximizes the compression by gzip. # '-M 30m' increases the size of the internal file buffer. Larger files have to # be compressed twice. # '-T 2k' prevents the compression of files smaller than 2k in order to save time. ;; -toc) # Arguments: $2 = BID, $3 = archive file name # This function is used to validate the correct generation of an archive file. # The output is compared to the list file passed to the '-create' function. # Any difference is reported as an error. afio -Zt $3 | sed 's#^#/#' # The sed command adds a leading slash to each entry. ;; -extract) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file # This function is called by backup2l's restore procedure for each archive. # It is extremely important that only those files contained in $4 are restored. # Otherwise it may happen that files are overwritten by incorrect (e. g. older) # versions of the same file. afio -Zinw $4 $3 2>&1 ;; esac } ################################################## # More sample archive drivers (optional) # This is an unordered collection of drivers that may be useful for you, # either to use them directly or to derive own drivers. # Here's a version of the standard DRIVER_TAR_GZ driver, # modified to split the output archive file into multiple sections. # (donated by Michael Moedt) DRIVER_TAR_GZ_SPLIT () { case $1 in -test) require_tools tar split cat echo "ok" ;; -suffix) echo "tgz_split" ;; -create) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file mkdir -p ${3} tar cz -T $4 --no-recursion | split --bytes=725100100 - ${3}/part_ ;; -toc) # Arguments: $2 = BID, $3 = archive file name cat ${3}/part_* | tar tz | sed 's#^#/#' ;; -extract) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file cat ${3}/part_* | tar xz --same-permission --same-owner -T $4 2>&1 ;; esac } # This driver uses afio and bzip2, where bzip2 is invoked by afio. # (donated by Carl Staelin) DRIVER_MY_AFIOBZ2 () { case $1 in -test) require_tools afio bzip2 echo "ok" ;; -suffix) echo "afio-bz2" ;; -create) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file afio -z -1 m -P bzip2 -Q -9 -Z -M 50m -T 1k -o $3 <$4 2>&1 # This line passes some additional options to afio (see afio(1)): # '-P bzip2' utilizes bzip2 as an external compressor # '-Q 9' maximizes the compression by bzip2. # '-M 50m' increases the size of the internal file buffer. Larger files have to # be compressed twice. # '-T 1k' prevents the compression of files smaller than 1k in order to save time. ;; -toc) # Arguments: $2 = BID, $3 = archive file name afio -t -Z -P bzip2 -Q -d - <$3 | sed 's#^#/#' # The sed command adds a leading slash to each entry. ;; -extract) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file afio -Zinw $4 -P bzip2 -Q -d - <$3 2>&1 ;; esac } # This driver uses afio and bzip2, such that the I/O stream is piped through bzip2. # (donated by Carl Staelin) DRIVER_MY_AFIO_BZ2 () { case $1 in -test) require_tools afio bzip2 echo "ok" ;; -suffix) echo "afio.bz2" ;; -create) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file afio -o - < $4 | bzip2 --best > $3 2>&1 ;; -toc) # Arguments: $2 = BID, $3 = archive file name bzip2 -d < $3 | afio -t - | sed 's#^#/#' # The sed command adds a leading slash to each entry. ;; -extract) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file bzip2 -d < $3 | afio -inw $4 - 2>&1 ;; esac } # This driver uses the Info-ZIP tools to generate zip files. Unfourtunately unzip # expects all file names to be on the command line. So unless there is a work- # around it's not possible to use the "-extract" command. # (donated by Georg Lutz) DRIVER_ZIP () { case $1 in -test) require_tools zip echo "ok" ;; -suffix) echo "zip" ;; -create) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file cat $4| zip -qy $3 -@ ;; -toc) # Arguments: $2 = BID, $3 = archive file name zipinfo -1 $3| sed 's#^#/#' ;; -extract) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file echo "Not implemented yet! Sorry." #unzip $3 ;; esac } # This driver uses tar and pipes the output trough gnupg. You can specifiy # the passphrase in a file (/etc/backup2l.pass in the example). You have to # invoke gpg at least one time before backup because gnupg has to initiate # first thing in the home directory. DRIVER_TAR_GPG () { case $1 in -test) require_tools tar gpg echo "ok" ;; -suffix) echo "tar.pgp" ;; -create) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file tar -c -T $4 --no-recursion | /usr/bin/gpg --batch --no-tty -q --passphrase-fd 3 3 $3 ;; -toc) # Arguments: $2 = BID, $3 = archive file name /usr/bin/gpg --batch --no-tty -q --passphrase-fd 3 3/dev/null | tar t | sed 's#^#/#' ;; -extract) # Arguments: $2 = BID, $3 = archive file name, $4 = file list file /usr/bin/gpg --batch --no-tty -q --passphrase-fd 3 3/dev/null | tar -x --same-permission --same-owner -f $3 -T $4 2>&1 ;; esac } backup2l-1.5/backup2l.80000644000175000017500000003730211311223275014536 0ustar gundolfgundolf.TH BACKUP2L 8 "Dec 2009" "backup2l v1.5" .SH NAME backup2l \- low-maintenance backup/restore tool .SH SYNOPSIS .B backup2l [ .BI "-c " conffile ] [ .BI "-t " "backup-ID" ] .I command .SH DESCRIPTION \fBbackup2l\fP is a tool for generating, maintaining and restoring backups on a mountable file system (e. g. hard disk). The main design goals are low maintenance effort, efficiency, transparency and robustness. It features differential backups at multiple hierarchical levels and provides rollback functionality. All control files are stored together with the archives on the backup device, and their contents are mostly self-explaining. Hence, in the case of an emergency, a user does not only have to rely on the restore functionality of \fBbackup2l\fP, but can - if necessary - browse the files and extract archives manually. An open driver architecture allows to use virtually any archiving program as a backend. Built-in drivers support \fB.tar.gz\fP, \fB.tar.bz2\fP, or \fB.afioz\fP files. Further drivers can be added by the user. When restoring data, an appropriate driver is selected automatically for each archive depending on the suffix of the archive file. The method of hierarchical differential backups is a generalization to the concept of the "daily", "weekly" and "monthly" backups. Each backup has a level and a serial number. Maximum-level backups are comparable with daily differential backups, level-0 backups are full backups. For example, let \fBMAX_LEVEL\fP be 4 and \fBMAX_PER_LEVEL\fP be 5. After 5 level-4 backups (e. g. after 5 days), a new level-3 backup is made. After 5 level-3 backups (and 5*5 at level-4), a new level-2 backup is made, and so on. Each differential backup contains the changes towards the previous backup of the same or a lower level. This scheme allows to efficiently generate small incremental backups at short intervals. At the same time, the total number of archives that have to be stored (or processed in the case of a restore) only increases logarithmically with the number of backups since the last full backup. Time-consuming full backups are only sparsely needed. In the example above, a new full backup is only necessary after 780 (=5^4+5^3+5*5+5) days, while only at most 20 (=4*5) archives have to be processed. For \fBbackup2l\fP, each backup archive is identified by its \fBbackup ID (BID)\fP. The number of digits determines the level. Level-0 (full) backups have a 1-digit BID, level-n backups have a BID of n+1 digits. The last digit is a serial number, the prefix identifies the lower-level backups on which a given backup is based on. For example, the archive 235 contains the differences towards archive 234, and to restore the file system state of the time it was generated, the full backup 2, the level-1 backups 21, 22, 23 and the level-2 backups 231, ..., 235 have to be processed. All serial numbers are between 1 and 9, a zero in the BID indicates that no archive of the respective level is contained in the chain. For example, the level-3 backup 1201 is immediately based on the level-1 backup 12. For deciding whether a file is new or modified, \fBbackup2l\fP looks at its name, modification time, size, ownership and permissions. Unlike other backup tools, the i-node is not considered in order to avoid problems with non-Unix file systems like FAT32. .SH OPTIONS .TP .BI "\-c, --conf " conffile This argument specifies the configuration file (default: \fBetc/backup2l.conf\fP). .TP .BI "\-t, --time " "BID" If present, this option selects a certain backup for the \fB--locate\fP and \fB--restore\fP commands. E. g., the latter will restore files and directories exactly as they were on the system at the time when the specified backup was made. If not present, the latest available backup is selected. .SH COMMANDS .TP .BI "\-h, --help" Display the usage info. .TP .BI "\-e, --estimate " "\fR[\fP level \fR]\fP" Prints the number of files, estimated amount of data and other information on the backup that would be generated next. No backup archives are actually created or removed. If specified, the parameter \fBlevel\fP overrides the \fBMAX_LEVEL\fP setting. .TP .BI "\-b, --backup " "\fR[\fP level \fR]\fP" Creates a new backup and removes old archives based on the given configuration file. If specified, the parameter \fBlevel\fP overrides the \fBMAX_LEVEL\fP setting. This is useful e. g. shortly before or after major changes are performed with the file system. In this case, a lower level should be specified in order to avoid that a large number of files are backed up multiple times again. .TP .BI "\-s, --get-summary" Shows a table describing each backup (date, size, files, ...) and the file system usage of the backup device. .TP .BI "\-a, --get-available " "\fR[\fP pattern list \fR]\fP" Shows all files removed and added for all backups. A '+' in the output indicates that the file is new and thus contained in the archive file. A '-' indicates that the file has been removed (or replaced). If one or several patterns are supplied, \fBgrep(1)\fP is used to filter the list. All status messages go to \fBstderr\fP, so that the generated file list can easily be redirected. \fBNote:\fP The search pattern is not just applied to the file names, but to the whole entry in the \fB.list.gz\fP file. This allows you to not only search for file names but also for other attributes like ownership, modification time etc. . In order to apply a search pattern to file names only, preceed it by "/.*". .TP .BI "\-l, --locate " "\fR[\fP pattern list \fR]\fP" Shows most recent backup location for active files. If one or several patterns are supplied, \fBgrep(1)\fP is used to filter the list in the same way as for \fB--get-available\fP (see above) . All status messages go to \fBstderr\fP, so that the generated file list can easily be redirected. Active files are files that have been on the system at the time of the selected backup, which is either the latest backup or the one specified by \fB--time\fP (see above). Files that were removed at that time but are still stored in some later archive will not be shown. Altogether, this command tells you, which files have to be extracted from which archive in order to restore the state of the system at the time of the selected backup. .TP .BI "\-r, --restore " "\fR[\fP pattern list \fR]\fP" Performs the same steps like \fB--locate\fP and then restores the respective files. All files are restored relative to the current directory. They can be restored to their original location by cd'ing into / before, but this is not recommended. .TP .BI "\-p, --purge " "BID list" Removes the specified backup archive(s) and all depending backups. .TP .BI "\-m, --make-check " "\fR[\fP BID list \fR]\fP" Creates (a) check file(s) for the specified archive(s) using \fBmd5sum(1)\fP. If no BID is specified, check files are created wherever missing. .TP .BI "\-v, --verify " "\fR[\fP BID list \fR]\fP" Verifies the specified backup archive(s). If no BID is specified, all existing archives are checked. If a check file exists, this allows a comprehensive test including e. g. media failures. If the check file is missing, only the existence of all files and the immediate base archive are verified. .TP .BI "\-x, --extract " "capacity max-free BID-list" Split and collect files to be stored on removable media (e. g. CDs). \fBcapacity\fP is the medium capacity in MB. \fBmax-free\fP is the maximum amount of empty space on each medium (except for the last one, of course). \fBBID-list\fP specifies the archives and may contain wildcards, e. g.: 1 '2*'. The operation generates enumbered subdirectories representing the media contents. Some more files are generated that may be useful, e. g. to print labels. While guaranteeing a minimum waste of \fBmax-free\fP MB per medium, the collection procedure preserves the ordering of files and keeps all control files of an archive always together on the same medium. Large archive files are split into multiple files with serial numbers appended to their names. The operation is interactive. Just run it and look what it is about to do. If that is not what you want, you can stop it. .SH CONFIGURATION In the configuration file (\fB/etc/backup2l.conf\fP by default), the following variables have to be set, following the \fBbash(1)\fP syntax: .TP .BI "FOR_VERSION=" "version" Defines the \fBbackup2l\fP version for which the configuration file is written. This way, future versions can automatically print a warning if the syntax has changed. .TP .BI "SRCLIST=(" " source list " ")" This is a blank-separated list of all top-level directories to make backups of. Directory names with spaces have to be quoted, e. g.: SRCLIST=("/my dir" /another/dir). The last elements of the list may be options for \fBfind(1)\fP, for example \fB-xdev\fP in order to skip subdirs on other file systems like /dev or /proc. .TP .BI "SKIPCOND=(" " find condition " ")" Files for which this condition is 'true' are not considered for backup. See \fBfind(1)\fP for information on how to formulate possible conditions. Special characters ("(", ")", "!", ...) must be quoted by a leading backslash ("\\(", "\\)", " \\!", ...). An empty condition (i. e. if you do not want any files to be skipped) must be specified as "( -false )". .TP .BI "[ BACKUP_DEV=""" "mount_point" """ ]" If defined, \fBbackup2l\fP mounts the backup device before any operation. Afterwards, it is unmounted unless it was already mounted before. .TP .BI "BACKUP_DIR=""" "backup dir" """" Destination directory for backup files. This must be different from \fBMOUNT_POINT\fP, i. e. a subdirectory on the device. .TP .BI "VOLNAME=""" "volname" """" This is a common prefix for all backup and control files. Multiple backup volumes are possible if for each volume a separate configuration file is written. .TP .BI "MAX_LEVEL=" "max_level" Maximum backup level. Possible values are 1..9. .TP .BI "MAX_PER_LEVEL=" "max_per_level" Number of differential backups per level. Possible values are 1..9. .TP .BI "MAX_FULL=" "max_full" Number of full backups kept. Possible values are 1..8. .TP .BI "GENERATIONS=" "generations" Number of backup generations to keep for each non-zero level. Old backups are automatically removed as long as at least \fBGENERATIONS * MAX_PER_LEVEL\fP backups for the respective level remain. For example, with \fBMAX_LEVEL=3, MAX_PER_LEVEL=5, GENERATIONS=2\fP it is always possible to access the last 10 level-3 (e. g. daily) backups, the last 10 level-2 backups (e. g. 5, 10, 15, ..., 50 days old), and so on. .TP .BI "PRE_BACKUP () { " "do something" " }" This function is called before writing the backup. It can be used to dump some important system information, e. g. the HD's partition table, to a file which is then backed up. .TP .BI "POST_BACKUP () { " "do something" " }" This function is called after writing the backup. Together with \fBPRE_BACKUP\fP it can be used to stop and restart e. g. database or mail services which may frequently alter some files that have to be backed up. .TP .BI "[ AUTORUN=1 ]" If set to 1, \fBbackup2l\fP performs the \fB--backup\fP operation when invoked without arguments. Otherwise, the usage information is shown. .TP .BI "[ SIZE_UNITS= " "B | K | M | G" " ]" Sets the units for archive sizes in summary listings to bytes, KB, MB, or GB. If unset, a user-readable format is chosen automatically. If set, the units are the same for the whole table, which may be even more user-friendly. .TP .BI "[ CREATE_DRIVER=""" "archive driver" """ ]" Selects an archive driver for creating backups. An archive driver is responsible for managing backup files. If unset, the default driver "DRIVER_TAR_GZ" is used. The \fB--help\fP operation lists all available drivers. More drivers can be defined in the configuration file (see below). .TP .BI "[ USER_DRIVER_LIST=""" "user-defined drivers" """ ]" Declares additional, user-defined archive drivers which are implemented in the configuration file. The sample configuration file contains a commented example. Read it in order to learn how to implement your own driver. .SH FILES .TP .B /etc/backup2l.conf Configuration file. .TP .IB "VOLNAME" "." "BID" ".tar.gz, " "VOLNAME" "." "BID" ".afioz, ..." Archive files. .TP .IB "VOLNAME" "." "BID" ".list.gz" List of all active files when the backup was made. Each file is preceeded with its size, modification time, and other information. .TP .IB "VOLNAME" "." "BID" ".new.gz" List of all new ore modified files when the backup was made (pathnames only). Unless an error occured, this list reflects the contents of the archive. .TP .IB "VOLNAME" "." "BID" ".obsolete.gz" List of all obsolete files when the backup was made (pathnames only). .TP .IB "VOLNAME" "." "BID" ".skipped.gz" Complete list of all files that were skipped according to \fBSKIPMASK\fP. .TP .IB "VOLNAME" "." "BID" ".error.gz" This file is generated by comparing the \fB.new.gz\fP file with the actual archive contents using \fBdiff(1)\fP. If the error file is non-empty, something may have gone wrong. .TP .IB "VOLNAME" "." "BID" ".check" MD5 check sums of all files of the present archive and the \fB.list.gz\fP file of the base archive. This file is optional and may be used by the \fB--verify\fP operation. .SH INVOCATION BY CRON \fBbackup2l\fP is designed to be run autonomously as a cron job. If the variable \fBAUTORUN\fP is set, it generates a backup if invoked without any parameters, and you can simply create a symlink, e. g. by: .ce ln -s `which backup2l` /etc/cron.daily/zz-backup2l The "zz-" prefix causes the backup job to be the last one executed, so that other jobs are not delayed if the backup takes somewhat longer. The status output is e-mailed to root by the cron daemon. .SH MANIPULATING FILES AND CONFIGURATIONS \fBbackup2l\fP has been designed to be robust with respect to errors and configuration changes. If the backup process is interrupted, e. g. because of a shutdown while it is running, no serious data corruption can occur. Some temporary files may remain which are cleaned up during the next run. If file is changed during the backup generation, it may not be contained in the current backup. However, it is guaranteed that it is considered modified during the next backup. In order to save disk space, e. g. after some archives have been copied to external media, archive files (.tar.gz or .afioz, for example) can safely be removed from the backup directory. As long as all control files are kept, \fBbackup2l\fP retains full functionality as far as possible. The \fB--restore\fP command prompts for eventually missing archive files for the respective request (and only those). The \fB--extract\fP command completely ignores all backups with missing archive files. The configuration, especially the settings for \fBMAX_LEVEL\fP, \fBMAX_PER_LEVEL\fP, \fBMAX_FULL\fP and the specification of source files, can be arbitrarily changed without having to expect data corruption. \fBbackup2l\fP will gracefully adapt the new settings during the next run. .SH COPYRIGHT Copyright (c) 2001-2004 by Gundolf Kiefer. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. .SH BUGS After a restore operation, the modification time of directories is equal to the restoration time while for files it is equal to the original modification time. .SH SEE ALSO .BR "tar(1), afio(1), find(1), grep(1), md5sum(1)" .SH AUTHOR Gundolf Kiefer backup2l-1.5/debian/0000755000175000017500000000000011311237013014152 5ustar gundolfgundolfbackup2l-1.5/debian/rules0000755000175000017500000000271307722473715015263 0ustar gundolfgundolf#!/usr/bin/make -f # Base on the sample debian/rules file for GNU Hello (1.3) package=backup2l build: clean: $(checkdir) rm -rf *~ debian/tmp debian/*~ debian/files* debian/substvars binary-indep: checkroot build $(checkdir) # There are no architecture-independent files to be uploaded # generated by this package. If there were any they would be # made here. binary-arch: checkroot build $(checkdir) rm -rf debian/tmp install -d debian/tmp/DEBIAN\ -d debian/tmp/usr/share/doc/$(package)\ -d debian/tmp/usr/share/man/man8\ -d debian/tmp/usr/sbin\ -d debian/tmp/etc/cron.daily install -m 755 debian/prerm debian/tmp/DEBIAN install -m 644 debian/conffiles debian/tmp/DEBIAN ./install-sh -fc debian/tmp/usr/share debian/tmp/usr/sbin debian/tmp/etc cp -a debian/copyright debian/tmp/usr/share/doc/$(package)/ cp -a debian/changelog debian/tmp/usr/share/doc/$(package)/changelog cp -a README debian/tmp/usr/share/doc/$(package)/ cp -a first-time.conf debian/tmp/usr/share/doc/$(package)/ cd debian/tmp/usr/share/doc/$(package) && gzip -9 changelog cd debian/tmp/usr/share/doc/$(package) && gzip -9 README dpkg-gencontrol -isp chown -R root.root debian/tmp chmod -R g-ws debian/tmp dpkg --build debian/tmp .. define checkdir test -f install-sh -a -f debian/rules endef # Below here is fairly generic really binary: binary-indep binary-arch checkroot: $(checkdir) test $$(id -u) = 0 .PHONY: binary binary-arch binary-indep clean checkroot build backup2l-1.5/debian/prerm0000644000175000017500000000017607370004303015232 0ustar gundolfgundolf#!/bin/bash set -e if [ \( "$1" = "upgrade" -o "$1" = "remove" \) -a -L /usr/doc/backup2l ]; then rm -f /usr/doc/backup2l fi backup2l-1.5/debian/conffiles0000644000175000017500000000005707370004303016053 0ustar gundolfgundolf/etc/backup2l.conf /etc/cron.daily/zz-backup2l backup2l-1.5/debian/control0000644000175000017500000000273107736606343015606 0ustar gundolfgundolfSource: backup2l Section: admin Priority: optional Maintainer: Gundolf Kiefer Standards-Version: 3.6.1 Package: backup2l Architecture: all Depends: findutils (>= 2.1) Recommends: tar (>= 1.13.17) | afio (>= 2.4.7) Suggests: bzip2 (>= 1.0.2), cdlabelgen Description: low-maintenance backup/restore tool for mountable media backup2l [backup-too-l] is a tool for autonomously generating, maintaining and restoring backups on a mountable file system (e. g. hard disk). In a default installation, backups are created regularly by a cron script. . The main design goals are low maintenance effort, efficiency, transparency and robustness. All control files are stored together with the archives on the backup device, and their contents are mostly self-explaining. Hence, a user can - if necessary - browse the files and extract archives manually. . backup2l features differential backups at multiple hierarchical levels. This allows to generate small incremental backups at short intervals while at the same time, the total number of archives only increases logarithmically with the number of backups since the last full backup. . An open driver architecture allows to use virtually any archiving program as a backend. Built-in drivers support .tar.gz, .tar.bz2, or .afioz files. Further user-defined drivers can be added. . An integrated split-and-collect function allows to comfortably transfer all or selected archives to a set of CDs or other removable media. backup2l-1.5/debian/copyright0000644000175000017500000000154607721705564016140 0ustar gundolfgundolfCopyright (c) 2001-2003 by Gundolf Kiefer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. On Debian systems, the complete text of the GNU General Public License can be found in /usr/share/common-licenses/GPL file. backup2l-1.5/debian/changelog0000644000175000017500000001046311311236324016034 0ustar gundolfgundolfbackup2l (1.5) unstable; urgency=low * Add self-test script to each external volume * changed FORMAT to truncate fractional parts from time stamps in .list.gz files (needed for Debian 5.0/Lenny) * backup2l.conf: added some comments on how to use SKIPCOND for certain features * fixed false errors with special characters, closes #291423, #305792 * changed chown invocation ("g:u" instead of "g.u") -- Gundolf Kiefer Tue, 13 Dec 2009 20:13:05 +0100 backup2l (1.4) unstable; urgency=low * Misc. adaptions to allow directory structures as archives * first-time.conf: added section with donated sample drivers * DRIVER_AFIOZ: accesses archives only via stdin/stdout to allow files > 2GB * Improved POSIX compliance, closes #261950 * Improved support for complex SKIPCOND settings including "(", ")", "!" * Fixed restore of files with glob(7) control characters, closes #235249 * misc. typos in doc, closes #250672, #250671 * adopted documentation to correctly state that MAX_FULL must be <= 8, closes #233962 * some adaptions to Mac OS X with the help of Joe Auricchio * turned SED_* macros into FILTER_* to allow alternative filter program instead of sed * install-sh: autodetects cron/daily directory (compatibility with Mac OS X) * install-sh: fixed error in usage () -- Gundolf Kiefer Tue, 4 Jan 2005 16:10:05 +0100 backup2l (1.3) unstable; urgency=low * Removed "--same-order" option in tar-based drivers (fixes problems during restore) * Improved checking of required drivers before starting a restore operation * Fixed some typos in comments and documentation * Set Debian standards version to 3.6.1 -- Gundolf Kiefer Wed, 8 Oct 2003 19:22:00 +0200 backup2l (1.2) unstable; urgency=low * Fixed false alarms with special characters in file names (closes #763710, #587658) * Added DRIVER_TAR (Philippe "BooK" Bruhat) * Moved Debian adaptions by Chris Davis upstream * Set Debian standards version to 3.6.0 -- Gundolf Kiefer Mon, 2 Sep 2003 20:47:17 +0200 backup2l (1.1-gk.1.1) unstable; urgency=low * Initial upload into Debian (closes: 142857) * Remove unnecessary postinst -- Chris G. Davis Fri, 25 Jul 2003 15:10:34 -0400 backup2l (1.1-gk.1) unstable; urgency=low * New: open driver architecture for different archive formats * fixed bugs with regexps for --restore, --locate, --get-available -- Gundolf Kiefer Sun, 18 May 2003 12:16:07 +0200 backup2l (1.01-gk.1) unstable; urgency=low * fixed incorrect backup list sorting (bug #637066) * removed "bc" usage * stricter filter macros for SED_NAME, SED_CHOWN, SED_CHMOD (should fix various bugs including patch 702979 from Gene Skonicki) * human readable sizes (based on patch by Jarno Elonen) * allow wildcards in argument of ANY operation * support for filesystems not allowing symlinks (patch by Jason Creighton) -- Gundolf Kiefer Sun, 9 Apr 2003 12:59:00 +0200 backup2l (1.00-gk.1) unstable; urgency=low * Fixed problem with special characters in file names (bug #587658) * .tar.gz files can savely be removed * Various fixes/patches -- Gundolf Kiefer Sun, 8 Sep 2002 17:30:00 +0200 backup2l (0.93-gk.1) unstable; urgency=low * Changed: diff is invoked without '-d' (performance issue with large numbers of files) * New: POST_BACKUP * Fixed: compatibility with pre-0.9 .list files * Various fixes/patches -- Gundolf Kiefer Thu, 9 Mar 2002 15:26:00 +0100 backup2l (0.92-gk.1) unstable; urgency=low * Fixed: incorrectly determined base archive resulting in full backup instead of incremental -- Gundolf Kiefer Thu, 17 Jan 2002 11:11:01 +0100 backup2l (0.91-gk.1) unstable; urgency=low * New: command to generate CD-ready data directories * Time in summary display * Workaround for bug in find 4.1.7 (no leading 0's in printf if requested) * Use of shell arrays for SRCLIST * Fixed problems with spaces in file names * Minor bugfixes -- Gundolf Kiefer Tue, 6 Nov 2001 09:32:13 +0100 backup2l (0.9-gk.1) unstable; urgency=low * Initial release -- Gundolf Kiefer Mon, 29 Oct 2001 14:58:47 +0100 Local variables: mode: debian-changelog End: backup2l-1.5/install-sh0000755000175000017500000000661507776013052014763 0ustar gundolfgundolf#!/bin/bash set -e NO_PROMPT=0 TOUCH_CONF=1 UNINST=0 case $1 in -f) NO_PROMPT=1 TOUCH_CONF=0 shift ;; -fc) NO_PROMPT=1 shift ;; -u) NO_PROMPT=1 UNINST=1 TOUCH_CONF=0 shift ;; -uc) NO_PROMPT=1 UNINST=1 shift ;; esac if [[ "$1" != "" ]]; then PREFIX=${1%/} else PREFIX="/usr/local" fi if [[ "$2" != "" ]]; then PREFIX_BIN=${2%/} else PREFIX_BIN="$PREFIX/bin" fi if [[ "$3" != "" ]]; then PREFIX_CONF=${3%/} else PREFIX_CONF="/etc" fi if [[ "$NO_PROMPT" == "0" ]]; then cat << EOF Usage: install-sh [ -f | -fc | -u | -uc ] [ ] [ ] [ ] Where -f: (Re-)Install program -fc: (Re-)Install program and configuration files -u: Uninstall program -uc: Uninstall program and configuration files : Location (default: /usr/local) : Location for binary files (default: /bin) : Location for configuration files (default: /etc) EOF fi CRON_FILE="" if [[ "$TOUCH_CONF" == "1" ]]; then if [ -d $PREFIX_CONF/cron.daily ]; then # standard cron/daily path for some Linux's... CRON_FILE=$PREFIX_CONF/cron.daily/zz-backup2l elif [ -d $PREFIX_CONF/periodic/daily ]; then # standard cron/daily path for Mac OS X... CRON_FILE=$PREFIX_CONF/periodic/daily/zz-backup2l fi fi PROG_FILES="$PREFIX_BIN/backup2l $PREFIX/man/man8/backup2l.8.gz" CONF_FILES="$PREFIX_CONF/backup2l.conf $CRON_FILE" if [[ "$UNINST" == "0" ]]; then # Installation... if [[ "$NO_PROMPT" == "0" ]]; then echo -e "I am about to install the following program file(s):\n $PROG_FILES\n" read -p "Do you want to continue? [y/N] " ANSWER echo if [[ "$ANSWER" != "y" ]]; then echo "Stopping." exit 1 fi fi mkdir -p $PREFIX_BIN $PREFIX/man/man8 cp -af backup2l $PREFIX_BIN gzip -9 -c backup2l.8 > $PREFIX/man/man8/backup2l.8.gz echo "Program files installed." if [[ "$TOUCH_CONF" == "1" && "$NO_PROMPT" == "0" ]]; then echo -e "\nI can install the following configuration file(s):\n $CONF_FILES\n" echo "This is recommended for a first-time installation." echo -e "Warning: Previously existing files will be overwritten!\n" read -p "Do you want to continue? [y/N] " ANSWER echo if [[ "$ANSWER" != "y" ]]; then TOUCH_CONF=0 fi fi if [[ "$TOUCH_CONF" == "1" ]]; then mkdir -p $PREFIX_CONF cp -af first-time.conf $PREFIX_CONF/backup2l.conf echo "Configuration files installed." if [[ "$CRON_FILE" != "" ]]; then cp -af zz-backup2l $CRON_FILE else echo -e "\nNote: No suitable cron directory found - file 'zz-backup2l' not (un-)installed automatically." fi else echo "No configuration files installed." fi else # Un-installation... echo "Removing program file(s): $PROG_FILES" rm -f $PROG_FILES if [[ "$TOUCH_CONF" == "1" ]]; then echo "Removing configuration file(s): $CONF_FILES" rm -f $CONF_FILES if [[ "$CRON_FILE" == "" ]]; then echo -e "\nNote: Cron directory not found - eventually remove 'zz-backup2l' manually." fi else echo "Configuration files NOT removed." fi fi backup2l-1.5/README0000664000175000017500000000627311311236527013633 0ustar gundolfgundolfINSTALLATION (from .tar.gz archive) =================================== Debian Linux: Type 'debuild' to get a binary archive. Others: Run './install-sh' or 'sh install-sh' and follow the instructions. RELEASE NOTES ============= Version 1.4 ----------- With some help from Joe Auricchio , backup2l should now run under Mac OS X 10.3 (but may still be unstable!). The following known restrictions apply: - installing the GNU findutils, e. g. from fink (fink.sf.net), in particular the GNU versions of 'find' and 'xargs'. - If no GNU version of sed is installed, the FILTER_* commands in the beginning of "backup2l" have to be changed to use per for filtering (just uncomment the respective lines there). - In some cases, the printf "%s" command of 'find' always returns 0 instead of the file size. This leads to incorrect size reports. - The date in the summary output is broken unless GNU date is installed. - [to be continued...] Binaries for the four gnu programs which Macintosh users need to install before using backup2l (find, xargs, sed and date) may be found at http://luddite.cst.usyd.edu.au/~jason/macos-gnu-utils-for-backup2l.tgz (Jason Grossman ). Version 1.1 ----------- An new open driver architecture allows to use virtually any archiving program as a backend. Built-in drivers support .tar.gz, .tar.bz2, or .afioz files. Further user-defined drivers can be added as described in the sample configuration file 'first-time.conf'. Due to major code changes, this release may be unstable. The latest stable release is 1.01. Archives created by the default driver ("DRIVER_TAR_GZ") are fully compatible with backup2l 1.01. Version 0.9 & 0.91 ------------------ Some versions of find (e. g. 4.1.7.) seem to have a bug so that printf does not produce leading 0's if requested. As a result, the .list.gz, .new.gz and .obsolete.gz files may contain entries like 4 11/11/01 02:07:17 0. 0 0777 /var/squid/some_file instead of 4 11/11/01 02:07:17 0000.0000 0777 /var/squid/some_file In backup2l 0.91, a workaround is implemented to guarantee the correct (2nd) format. Two issues have to be considered: a) Without leading 0's, a restore operation may fail. b) Backups are always created correctly. However, when switching to 0.91, differential backups may be larger than necessary. It is recommended either to modify incorrect .list.gz, .new.gz and .obsolete.gz files manually or to purge the respective archives. COPYRIGHT ========= (c) 2001-2009 by Gundolf Kiefer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. backup2l-1.5/zz-backup2l0000755000175000017500000000051010166467722015041 0ustar gundolfgundolf#!/bin/bash # The following command invokes 'backup2l' with the default configuration # file (/etc/backup2l.conf). # # (Re)move it or this entire script if you do not want automatic backups. # # Redirect its output if you do not want automatic e-mails after each backup. ! which backup2l > /dev/null || nice -n 19 backup2l -b