#!/bin/bash
################################################################################
#### Restore and compare rsync BackupPC backups
## Jeff Kosowsky
## v1.0
## June 8, 2020
################################################################################
#Usage:
# BackupPC_restoreTest <host>[:<host_alias>] [<backup_number>] [<share1>] [<share2>] [<share3>]...
# Defaults: <host_alias> = <host> if not set
#           <backup_number> = most recent backup
#           <shareN> = specific shares to be restored and compared
#                      (note shares can be given with/without '%2f' substituting for '/'
#
# NOTE:    'compression' and 'version' come from the $HOST/backups file for each backup number
#          The source directory for comparing each restored share come from the pulled XferLOG file
#          The source file for comparing the restored share is pulled from the XferLOG file
#
# Key variables to set:
#   PC = typically the equivalent of TopDir/pc
#   BASE = base restore directory where the restored directories and logs are to be kept

# Optional variables that can be set as defaults
#   HOST = name of backup machine
#   ALIAS = host alias for backup machine
#   NUM = backup number (to override automatic use of last backup)
#   ALLSHARES = space separated list of shares (to override automatic substitution of all shares)
#   LOG = log level for restore
#
# For each share the following are created:
#   BASE/SHARE-BAKNUM: restore directory
#   BASE/SHARE-BAKNUM.restore: log file from the share restore
#   BASE/SHARE-BAKNUM.restore: diff file from the final rsync compare
################################################################################
shopt -s extglob #Turn on extended globbing
#Run again under sudo if not root... (do this so sudo doesn't timeout on separate ops...)
[ "`id -un`" != "root" ] && exec sudo -u root $0 $@

################################################################################
#####Defaults:
###BackupPC 'pc' location - typically the equivalent of TopDir/pc
PC=/var/lib/backuppc/pc

###BASE: base directory for restore and  logs
BASE=/tmp/baktemp
! sudo mkdir -p ${BASE} && echo "Can't create base directory: ${BASE}" && exit 4

###HOST: name of BackupPC machine
HOST=testmachine #Example of setting manually
###ALIAS: BackupPC alias for HOST
ALIAS= #This is the alias of the machine
[ -n "$1" ] && HOST="${1%:*}" && ALIAS="${1#*:}" && shift
! [ -d "$PC/$HOST" ] && echo "Can't find host: $PC/$HOST" && exit 1
! ping -q -c 1 ${ALIAS} >& /dev/null && echo "Can't ping Alias '${ALIAS}'..." && exit 2

###NUM: Backup number
#NUM=10 #Example of setting manually
NUM=$(cd /var/lib/backuppc/pc/${HOST}/; \ls -d *([0-9]) | sort -n |  tail -1) #Requires shopt extglob
#NUM=$(cd /var/lib/backuppc/pc/${HOST}/; find * -maxdepth 2 -regex "[0-9]+" | sort -n | tail -1)
[[ "$1" =~ [0-9]+ ]] &&  NUM="$1"; shift
! [ -d "$PC/$HOST/$NUM" ] && echo "Can't find backup number '${NUM}' for '$HOST'" && exit 3

###ALLSHARES: White-space separated string of shares to restore and compare
#ALLSHARES="root boot-efi home mythtv scratch" #Example of setting manually
ALLSHARES=($(cd /var/lib/backuppc/pc/${HOST}/${NUM}/; ls -d f*))
ALLSHARES=${ALLSHARES[@]#f};
[ -n "$1" ] && ALLSHARES="$@"

#LOG=1 #Log level for restore
RSYNCBPC=$(which rsync_bpc)

################################################################################
#Functions

function echoexe() { echo "\$ $@" ; "$@" ; } #Echo then execute following command

function restore ()
{
    FILESFROM=
    #NOTE columns of backups as follows: 1=backup number; 15=compression; 20=filled(0)/unfilled(1); 22=version x.y.z
    MERGE=$(sudo cat ${PC}/${HOST}//backups | awk -v num=$NUM '($1 >= num) {printf "%d/%d/%d\n", $1, $15, $22; if($21 ==0)exit}' | sort -rn)
    MERGE=$(echo ${MERGE:-$NUM/$COMPRESS/$VERSION}) #Handle case of a filled backup (no merges) -- i.e. if $21 == 0 above
    MERGE=$(echo ${MERGE// /,}) #Replace spaces with ','
    [ -z "${LOG}" ] && LOG=1

    echo -e "...Restoring: SHARE=$SHARE\tMERGE=$MERGE\tTARGET=${RESTORE}"
    echoexe sudo ${RSYNCBPC:-/usr/bin/rsync_bpc} --bpc-top-dir /var/lib/backuppc --bpc-host-name $HOST --bpc-share-name $SHARE --bpc-bkup-num $NUM --bpc-bkup-comp 3 --bpc-bkup-merge "$MERGE" --bpc-attrib-new --bpc-log-level $LOG -e /usr/bin/sudo\ -h --rsync-path=/usr/bin/rsync --recursive --super --protect-args --numeric-ids --perms --owner --group -D --times --links --hard-links --delete --partial --log-format=log:\ %o\ %i\ %B\ %8U,%8G\ %9l\ %f%L --stats --acls --xattrs ${FILESFROM:+--files-from=$FILESFROM} / localhost:${RESTORE} >| ${RESTORE}.restore 2>&1
}

function compare ()
{
    ! [ -d "${RESTORE}" ] && echo "Compare: can't find RESTORE: ${RESTORE}..." && return 1

    MATCH=$(sudo ~/bin/BackupPC_zcat /var/lib/backuppc/pc/$HOST/XferLOG.${NUM}.z | sed -ne "s|^0\?Running: .*rsync_bpc .* --bpc-share-name ${SHARE} .* --timeout=[0-9]\+ \(.*\) \?${ALIAS}:\(.*\) /|\1 \2|p")

    FILTER=${MATCH% *} #Match before space

    SRC=${MATCH##* } #Match after space
    SRC=${SRC%%+(/)} #Remove 1 or more trailing slashes (Requires: shopt extglob)
    ! [ -d "${SRC}" ] && echo "Compare: can't find SRC: $SRC..." && return 1

    echo "...Comparing: SHARE=$SHARE\tSOURCE=${SRC}/\tTARGET=${RESTORE}"
    echoexe sudo ${RSYNC:-/usr/bin/rsync} -niv -caxXAH --delete ${FILTER//\\/} ${SRC}/ ${RESTORE}/ >| ${RESTORE}.diffs 2>&1
}
################################################################################

echo -e  "BASE=$BASE\tHOST=$HOST (ALIAS=$ALIAS)\tNUM=$NUM\tSHARES: $ALLSHARES"
echo
for SHARE in $ALLSHARES; do
    echo "## ${SHARE}: "
    if [ -d "${PC}/${HOST}/${NUM}/f${SHARE}" -o -d "${PC}/${HOST}/${NUM}/f${SHARE/\/%2f}}" ]; then
	SHARE=${SHARE//%2f/\/} #Remove f-mangle
	RESTORE=${SHARE%/} #Remove trailing '/'
	RESTORE=${RESTORE##*/}-${NUM} #Remove leading directory path and add NUM suffix
	RESTORE=${BASE}/${RESTORE}	

	#RESTORE:
	restore
	echo -n "     Errors: "
	cat ${RESTORE}.restore | grep -v "^log: send\|^__bpc_progress" | grep -v "^$" | grep -v "^\(\$ sudo\|Number of files:\?\|[Tt]otal\|File list\|Literal data:\|Matched data:\|sent\|Done:\) "  | wc -l

	#COMPARE
	compare
	echo -n "     Errors: "
	cat ${RESTORE}.diffs | grep -v "^\($\|\$ sudo \|sending incremental\|sent \|total size \)" | wc -l
    else
	echo "Can't find share: ${SHARE}... skipping..."
    fi
    echo
done
echo "DONE!"
