From 5c007da2b01306442c7f9a1a6118f4738b4b3be6 Mon Sep 17 00:00:00 2001 From: Rob Pearce Date: Tue, 3 Nov 2020 15:04:57 +1100 Subject: [PATCH] Added restore mode Added colours --- bare.sh | 535 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 471 insertions(+), 64 deletions(-) diff --git a/bare.sh b/bare.sh index 9b2c579..675eacb 100755 --- a/bare.sh +++ b/bare.sh @@ -1,11 +1,12 @@ #!/bin/bash -VALIDCOMMANDS="go ls init repos stats diff forget" +VALIDCOMMANDS="go ls init repos stats diff forget get" RCFILE=${HOME}/.backup/config DEF_AUTHFILE=${HOME}/.backup/auth REPOFILE=${HOME}/.backup/repos STATFILE=${HOME}/.backup/stats LOGFILE=/var/log/backup.log +FULLLOGFILE_BASE=/tmp/full_backup_log RESTIC=/usr/local/bin/restic RCLONE=/usr/local/bin/rclone @@ -16,19 +17,51 @@ USMB=/usr/local/bin/usmb RCLONEOPTS="--progress --buffer-size 10M --cache-chunk-no-memory" SPEED="" +RESTOREDIRBASE="/mnt/restore" CRONMODE=0 CONNECTIONS="" LOG=/dev/stdout DATE=/bin/date DOALL=0 -REPOSTOBACKUP="" +REPO_LIST="" TESTMODE=0 VERBOSE=0 +function cecho() { + local col + col="$1" + shift 1 + echo -e "${col}$*${PLAIN}" +} +function error() { + cecho "$RED" "Error: $*" +} +function action() { + cecho "$CYAN" "$*" +} + function log() { - local now + local now col lowrer now=`${DATE} +%Y/%m/%d-%H:%M:%S` - echo "${now} <${mode}> $*" >>${LOG} + lower=$(echo "$*" | tr '[:upper:]' '[:lower:]') + if [[ $lower == *error* ]]; then + col="$RED" + elif [[ $lower == *fail* ]]; then + col="$RED" + elif [[ $lower == *success* ]]; then + col="$GREEN" + elif [[ $lower == *starting* ]]; then + col="$BOLD" + elif [[ $lower == *created* ]]; then + col="$BOLD" + elif [[ $lower == *creating* ]]; then + col="$BOLD" + elif [[ $lower == *finished* ]]; then + col="$BOLD" + else + col="$CYAN" + fi + echo -e "$CYAN${now} <${mode}> $col$*$PLAIN" >>${LOG} } function updatestats() { @@ -52,9 +85,10 @@ function usage-repo() { echo " Default password file is: $DEF_AUTHFILE" echo "" echo "Backup method tags:" - echo " restic Backup using restic (default repo is Backblaze B2 bucket \$B2_BUCKET_PREFIX-)" - echo " rclone Backup using rclone (default repo is Backblaze B2 bucket \$B2_BUCKET_PREFIX-)" - echo " rsync Backup using rsync (defualt repo is \$DEF_RSYNC_USER@\$DEF_RSYNC_SERVER:\$DEF_RSYNC_DIR/)" + echo " restic Backup using restic (default repo is Backblaze B2 bucket \$B2_BUCKET_PREFIX-)" + echo " rclone Backup using rclone (default repo is Backblaze B2 bucket \$B2_BUCKET_PREFIX-)" + echo " rsync Backup using rsync (default repo is \$DEF_RSYNC_USER@\$DEF_RSYNC_SERVER:\$DEF_RSYNC_DIR/)" + echo " postgres Dump all postgres databases. Instead of local file path, specify database name to backup. If empty, all dbs are backed up." echo "" echo "Pre-backup commands to access local files:" echo " nfs Mount \$DEF_NFS_SERVER:\$DEF_NFS_SERVER_BASE/ to " @@ -78,6 +112,8 @@ function usage-rc() { echo " export RESTIC_KEEPDAYS=31 # Days to keep restic backups for" echo " export RESTICOPTS=\"--one-file-system\" # Extra args to pass to restic" echo "" + echo " export PGUSER=postgres # Postgresql username" + echo "" echo " export DEF_RSYNC_SERVER=xxx # Server used for repos with 'rsync' tag" echo " export DEF_RSYNC_USER=backups # Username for rsync server" echo " export DEF_RSYNC_DIR=/home/backups/backups # Remote directory on rsync server" @@ -94,6 +130,7 @@ function usage() { echo "usage: $0 command reponame" echo "" echo " -a Run on all repos defined as 'auto'" + echo " -d dir Restore mode: specify where to put restored files (default: $RESTOREDIRBASE)" echo " -h Show this usage text" echo " -s num Limit speed to 'num' Mbps (default: unlimited)" echo " -x num Use 'num' simultaneous connections (default: 20)" @@ -320,15 +357,27 @@ function do_mount() { fi fi - if ! [[ -e ${DATAPATH} ]]; then - log "Error: ${DATAPATH} doesn't exist" - return 1 - fi + checktag "$f" postgres # datapath is used for sql filename in pgsql backups + if [[ $? -eq 0 ]]; then + # Make sure the db exists + if ! [[ -z ${DATAPATH} ]]; then + psql -lqt | cut -d \| -f 1 | grep -qw ${DATAPATH} + if [[ $? -ne 0 ]]; then + log "Error: PostgreSQL database '${DATAPATH}' doesn't exist" + return 1 + fi + fi + else + if ! [[ -e ${DATAPATH} ]]; then + log "Error: ${DATAPATH} doesn't exist" + return 1 + fi - count=`ls ${DATAPATH} | wc -l` - if [ $count -le 2 ]; then - log "Error: ${DATAPATH} exists but appears to be empty" - return 1 + count=`ls ${DATAPATH} | wc -l` + if [ $count -le 2 ]; then + log "Error: ${DATAPATH} exists but appears to be empty" + return 1 + fi fi return 0 } @@ -346,14 +395,60 @@ function do_umount() { fi } +function updatedir() { + local temp r tempwd + tempwd=$(echo "$wd" | sed -e 's-/\{1,\}-/-g') + temp=$( ${RESTIC} $AUTHOPTS $CONNECTIONSOPTS -q ls -l $RESTORESNAPID $tempwd 2>/dev/null ) + r=$? + if [[ $r -ne 0 || -z $temp ]]; then + return 1 + fi + wd="$tempwd" + contents=$( echo "$temp" | awk -v wd=$wd '{ sub(wd,""); sub(wd "/", ""); print $0; }') + subdirs=$( echo "${contents}" | grep "^d" | awk '{ print $NF}' | sed -e 's,^/,,') + subfiles=$( echo "${contents}" | awk '{ print $NF}' | sed -e 's,^/,,') + return 0 +} + +function showrestoreinfo() { + cecho "$YELLOW$BOLD" "Repo to restore from:" + cecho "$YELLOW" " $f (snapshot $RESTORESNAPID)" + echo + cecho "$YELLOW$BOLD" "Files to restore:" + if [[ -z $RESTORELIST ]]; then + cecho "$YELLOW" " (none)" + else + cecho "$YELLOW" "$RESTORELIST" | sed -e 's/^ //' | tr ' ' '\n' | sed -e 's/^/ /g' + fi + echo +} + +function restorecmd_help() { + echo + echo "x Abort" + echo "q Abort" + echo "lcd dir Change local restore dir" + echo "ls Show files in current repo dir" + echo "cd Change current repo dir" + echo "dirs Start subdirectories of current repo dir" + echo "go Start restore of marked file" + echo "w List files marked for restore" + echo "a file Mark file to be restored" + echo "d file Un-mark file from restore list" + echo +} + # Handle args -ARGS="acdhs:tvx:" +ARGS="acd:hs:tvx:" while getopts "$ARGS" i; do case "$i" in a) DOALL=1 ;; + d) + RESTOREDIRBASE="$OPTARG"; + ;; h) usage; exit 1 @@ -382,21 +477,33 @@ while getopts "$ARGS" i; do done shift $((OPTIND - 1)) +if [[ $CRONMODE -eq 0 ]]; then + BOLD="\033[1m" + PLAIN="\033[0m" + UNDERLINE="\033[4m" + RED="\033[31m" + GREEN="\033[32m" + YELLOW="\033[33m" + BLUE="\033[34m" + CYAN="\033[36m" + LINK="$BLUE$UNDERLINE" +fi + if ! [ -e ${RCFILE} ]; then - echo "Error - can't find ${RCFILE}" + error "can't find ${RCFILE}" echo "" usage-rc exit 1 fi if ! [ -e ${REPOFILE} ]; then - echo "Error - can't find ${REPOFILE}" + error "can't find ${REPOFILE}" echo "" usage-repo exit 1 fi which bc >/dev/null 2>&1 if [ $? -ne 0 ]; then - echo "Error - can't find bc in path" + error "can't find bc in path" echo "" exit 1 fi @@ -404,7 +511,7 @@ fi . ${RCFILE} if [[ -z $RESTIC_KEEPDAYS ]]; then - echo "Error - \$RESTIC_KEEPDAYS not set." + error "\$RESTIC_KEEPDAYS not set." exit 1 fi @@ -415,6 +522,25 @@ else KSPEED="" fi +CMD="$1" +shift 1 + +# command synonyms +if [[ $CMD == "repolist" ]]; then + CMD="repos" +elif [[ $CMD == "restore" ]]; then + CMD="get" +elif [[ $CMD == "snapshots" ]]; then + CMD="ls" +fi + + +if (echo "$VALIDCOMMANDS" | grep -wq $CMD) ; then + true +else + error "invalid command $CMD. Should one of: $VALIDCOMMANDS" + exit 1 +fi # Get list of defined repos idx=0 @@ -426,11 +552,11 @@ for f in `cat $REPOFILE | grep ";" | grep -v "^#"`; do thisrpath=`getrepopath $f` if [[ ${thisrpath,,} == *b2* ]]; then if [[ -z $B2_APP_ID ]]; then - echo "Error - \$B2_APP_ID not set." + error "\$B2_APP_ID not set." exit 1 fi if [[ -z $B2_APP_KEY ]]; then - echo "Error - \$B2_APP_KEY not set." + error "\$B2_APP_KEY not set." exit 1 fi fi @@ -439,31 +565,50 @@ for f in `cat $REPOFILE | grep ";" | grep -v "^#"`; do if [[ $DOALL -eq 1 ]]; then checktag "$f" auto if [[ $? -eq 0 ]]; then - REPOSTOBACKUP="$REPOSTOBACKUP `echo $f | sed -e 's/\;.*//'`" + REPO_LIST="$REPO_LIST `echo $f | sed -e 's/\;.*//'`" fi fi idx=$((idx + 1)) done -if ! [[ $VALIDCOMMANDS == *"$CMD"* ]]; then - echo "Error - invalid command $CMD. Should one of: $VALIDCOMMANDS" - exit 1 -fi -CMD="$1" -shift 1 - -if [[ -z $REPOSTOBACKUP ]]; then - REPOSTOBACKUP="$*" +if [[ $CMD == "get" ]]; then + # one repo + snapshotid/date + err=0 + if [[ -z $REPO_LIST ]]; then + if [[ $# -ge 1 ]]; then + REPO_LIST=$1 ;shift 1 + else + error "no reponame provided." + exit 1 + fi + if [[ $# -ge 1 ]]; then + RESTORESNAPID=$1 ; shift 1 + fi + RESTORELIST="$*" + else + echo "restore usage: $0 get repo_name (snapshotid|latest)" + echo " or" + echo " $0 get repo_name (snapshotid|latest) file1 file2 etc" + echo + echo "Files will be restored to $RESTOREDIRBASE/repo_name/" + echo + exit 1 + fi +else + # one or more repos + if [[ -z $REPO_LIST ]]; then + REPO_LIST="$*" + fi fi if [[ $CMD == "repos" ]]; then - format="%-10s%-20s%-20s%s\n" - echo "Repos defined in ${RCFILE}:" + format="%-20s%-20s%-30s%s\n" + cecho "${CYAN}" "Repos defined in $BOLD${RCFILE}$PLAIN$CYAN:" echo "" - printf "$format" "Repo" "Path" "Tags" "Repo path" + head=$(printf "$format" "Repo" "Path/DB" "Repo path" "Tags") + cecho "$BOLD$YELLOW" "$head" for x in ${REPODEFS[@]}; do - IFS=';' read -ra tok <<< "$x" thisrepo=${tok[0]} thispath=${tok[1]} @@ -475,19 +620,24 @@ if [[ $CMD == "repos" ]]; then thisrpath=`getrepopath $thisrepo` [[ -z $thisrpath ]] && thisrpath="(default)" - printf "$format" "$thisrepo" "$thispath" "$tags" "$thisrpath" + if [[ $tags == *postgres* && -z $thispath ]]; then + thispath="(all dbs)" + fi + + line=$(printf "$format" "$thisrepo" "$thispath" "$thisrpath" "$tags") + cecho "$YELLOW" "$line" done exit 0 fi -if [[ -z $REPOSTOBACKUP ]]; then +if [[ -z $REPO_LIST ]]; then usage exit 1 fi # Validate repos GOODREPOS="" -for f in $REPOSTOBACKUP; do +for f in $REPO_LIST; do REPO=${f} DATAPATH=$(getdatapath $f) REPOPATH=$(getrepopath $f) @@ -559,12 +709,17 @@ for f in $REPOSTOBACKUP; do fi if [[ -z $DATAPATH ]]; then - log "can't find matching repo for $f - make sure it is listed in ${REPOFILE}" - continue + checktag "$f" postgres + if [[ $? -ne 0 ]]; then + log "error - can't find matching repo for $f - make sure it is listed in ${REPOFILE}" + continue + fi fi if [[ $CMD == "go" ]]; then NEEDMOUNT=1 + elif [[ $CMD == "get" ]]; then + NEEDMOUNT=1 elif [[ $CMD == "diff" ]]; then NEEDMOUNT=1 else @@ -585,15 +740,15 @@ for f in $REPOSTOBACKUP; do fi done -REPOSTOBACKUP="$GOODREPOS" -if [[ -z $REPOSTOBACKUP ]]; then +REPO_LIST="$GOODREPOS" +if [[ -z $REPO_LIST ]]; then log "Error: errors found with all repos. Aborting." exit 1 fi if [[ $TESTMODE -eq 1 ]]; then - echo "Would have run '$CMD' on these repos:" - for f in $REPOSTOBACKUP; do + action "Would have run '$CMD' on these repos:" + for f in $REPO_LIST; do DATAPATH=$(getdatapath $f) echo " $f -> $DATAPATH" if [[ $NEEDMOUNT -eq 1 ]]; then @@ -606,9 +761,10 @@ fi errcount=0 # used for 'check' mode warncount=0 # used for 'check' mode errtext="" -for f in $REPOSTOBACKUP; do +for f in $REPO_LIST; do REPO=${f} DATAPATH=$(getdatapath $REPO) + FULLLOGFILE="${FULLLOGFILE_BASE}_${REPO}.txt" checktag "$f" usmb if [[ $? -eq 0 ]]; then if [[ -z $USMB_PREFIX ]]; then @@ -661,6 +817,8 @@ for f in $REPOSTOBACKUP; do if [[ $CMD == "go" ]]; then NEEDMOUNT=1 + elif [[ $CMD == "get" ]]; then + NEEDMOUNT=1 elif [[ $CMD == "diff" ]]; then NEEDMOUNT=1 else @@ -695,10 +853,32 @@ for f in $REPOSTOBACKUP; do else CONNECTIONSOPTS="-o b2.connections=${CONNECTIONS}" fi + + checktag "$f" postgres + if [[ $? -eq 0 ]]; then + CONNECTIONSOPTS="$OTHEROPTS --tag postgres" + fi + if [[ $VERBOSE -eq 1 ]]; then OTHEROPTS="$OTHEROPTS -v" fi + DBOPTS="" + DBDUMPCMD="" + checktag "$f" postgres + if [[ $? -eq 0 ]]; then + DBOPTS="$DBOPTS --clean" + OTHEROPTS="$OTHEROPTS --stdin" + if [[ -z ${DATAPATH} ]]; then + DBDUMPCMD="pg_dumpall" + DBFILENAME="alldbs.sql" + else + DBDUMPCMD="pg_dump" + DBFILENAME="${DATAPATH}.sql" + DBOPTS="$DBOPTS ${DATAPATH}" # Last dbdump option is database name + fi + fi + export RESTIC_EXCLUDEFILE=`getrepoexcludefile "$f"` if [[ ! -z $RESTIC_EXCLUDEFILE ]]; then OTHEROPTS="$OTHEROPTS --exclude-file=${RESTIC_EXCLUDEFILE}" @@ -718,7 +898,7 @@ for f in $REPOSTOBACKUP; do for xx in $list; do eval val='$'$xx if [[ -z $val ]]; then - echo "Error - \$$xx not set. Please update ${RCFILE}." + error "\$$xx not set. Please update ${RCFILE}." echo "" usage-rc exit 1 @@ -770,34 +950,257 @@ for f in $REPOSTOBACKUP; do oneday=$( echo "24 * 60 * 60" | bc) if [[ $code -eq 0 ]]; then if [[ $age -le $oneday ]]; then - echo "OK: Last backup for '$REPO' succeeded on $humanstamp." + cecho "$GREEN" "OK: Last backup for '$REPO' succeeded on $humanstamp." rv=0 else - echo "CRITICAL: Last backup for '$REPO' succeeded on $humanstamp (age $age not in last 24 hours)." + cecho "$RED" "CRITICAL: Last backup for '$REPO' succeeded on $humanstamp (age $age not in last 24 hours)." errcount=$(( $errcount + 1)) rv=2 fi else - echo "CRITICAL: Last backup for '$REPO' failed on $humanstamp." + cecho "$RED" "CRITICAL: Last backup for '$REPO' failed on $humanstamp." rv=2 errcount=$(( $errcount + 1)) fi else - echo "WARNING: No history found for repo '$REPO'" + cecho "$YELLOW" "WARNING: No history found for repo '$REPO'" rv=1 warncount=$(( $warncount + 1)) fi elif [[ $mode == "restic" ]]; then if [[ $CMD == "go" ]]; then if [[ $CRONMODE -eq 1 ]]; then - ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH 2>&1 | egrep "^(Added|processed|snapshot)" >> ${LOG} - rv=${PIPESTATUS[0]} + if [[ $VERBOSE -eq 1 ]]; then + echo "debug: running: [${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH" >>${LOG} + echo "debug: full log is in ${FULLLOGFILE}" >>${LOG} + checktag "$f" postgres + if [[ $? -eq 0 ]] ; then + $DBDUMPCMD $DBOPTS | ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS --stdin-filename $DBFILENAME 2>&1 >> ${FULLLOGFILE} + rv=$? + else + ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH 2>&1 >> ${FULLLOGFILE} + rv=$? + fi + egrep "^(Added|processed|snapshot)" ${FULLLOGFILE} >>${LOG} + else + checktag "$f" postgres + if [[ $? -eq 0 ]] ; then + ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH 2>&1 | egrep "^(Added|processed|snapshot)" >> ${LOG} + rv=${PIPESTATUS[0]} + else + $DBDUMPCMD $DBOPTS | ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS --stdin-filename $DBFILENAME 2>&1 | egrep "^(Added|processed|snapshot)" >> ${LOG} + rv=${PIPESTATUS[0]} + fi + fi else - [[ $VERBOSE -eq 1 ]] && log "debug: running: [${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH" - ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH 2>&1 >> ${LOG} + checktag "$f" postgres + if [[ $? -eq 0 ]] ; then + [[ $VERBOSE -eq 1 ]] && log "debug: running: [$DBDUMPCMD $DBOPTS | ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS --stdin-filename $DBFILENAME" + $DBDUMPCMD $DBOPTS | ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS --stdin-filename $DBFILENAME 2>&1 >> ${LOG} + rv=$? + else + [[ $VERBOSE -eq 1 ]] && log "debug: running: [${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH" + ${RESTIC} backup $AUTHOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH 2>&1 >> ${LOG} + rv=$? + fi + fi + elif [[ $CMD == "get" ]]; then + RESTOREDIR="$RESTOREDIRBASE/$f/" + snaps=$(${RESTIC} snapshots $AUTHOPTS $CONNECTIONSOPTS 2>&1 | grep -v ID | awk '(NF >= 4) { print }') + checktag "$f" postgres + if [[ $? -ne 0 ]]; then + snaps=$(echo "$snaps" | grep -v postgres) + fi + snapids=$(echo "$snaps" | awk '{ print $1 }') + defsid=$(echo "$snapids" | tail -1) + + if [[ -z $RESTORESNAPID ]]; then + cecho "$BOLD$CYAN" "Found these snapshots:" + echo "$snaps" + RESTORESNAPID="xxxxxx" + while [[ ! $snaps == *$RESTORESNAPID* ]]; do + read -p "snapshot [$defsid]: " RESTORESNAPID + if [[ $RESTORESNAPID == "latest" || -z $RESTORESNAPID ]]; then + RESTORESNAPID=$defsid + fi + done + elif [[ $RESTORESNAPID == "latest" ]]; then + RESTORESNAPID=$defsid + elif [[ ! $snaps == *$RESTORESNAPID* ]]; then + error "snapshot '$RESTORESNAPID' not found." + echo + cecho "$RED" "Valid snapshots for repo $f:" + cecho "$RED" "$snaps" + exit 1 + + fi + if [[ -z $RESTORELIST ]]; then + action "Opening repo for snapshot $BOLD$RESTORESNAPID$PLAIN$CYAN..." + wd=/ + updatedir + c="ls" + doit=0 + while [[ ! $c == "x" && ! c == "q" ]]; do + if [[ $c == "ls" ]]; then + echo "$contents" + elif [[ $c == "go" ]]; then + if [[ -z $RESTORELIST ]]; then + cecho "$RED" "Nothing to restore!" + else + doit=1 + fi + break + elif [[ $c == "x" || $c == "q" ]]; then + doit=0 + break + elif [[ $c == "?" || $c == "h" ]]; then + restorecmd_help + elif [[ -z $c ]]; then + true + elif [[ $c == "dirs" ]]; then + echo "subdirs:" + echo "$subdirs" | sed -e 's/^/ /' + elif [[ $c == w* ]]; then + showrestoreinfo + elif [[ $c == a* || $c == mark* ]]; then + file=$(echo "$c" | awk '{ print $2 }') + # remove leading / + file=$(echo $file | sed -e 's,^/,,') + if [[ -z $file ]]; then + error "no file provided" + else + echo "$RESTORELIST" | tr ' ' '\n' | egrep -q "^$wd$file$" + thisrv=$? + if [[ $thisrv -eq 0 ]]; then + error "'$file' already in restore list" + else + if [[ $file == */* ]]; then + # add without checking + if [[ -z $RESTORELIST ]]; then + RESTORELIST="$wd$file" + else + RESTORELIST="$RESTORELIST $wd$file" + fi + action "Added to restore list: $BOLD$file$PLAIN$CYAN (no verify)" + else + echo "$subfiles" | grep -qw "$file" + rv=$? + if [[ $rv -eq 0 ]]; then + RESTORELIST="$RESTORELIST $wd$file" + action "Added to restore list: $BOLD$file" + else + error "'$file' doesn't exist" + fi + fi + fi + fi + elif [[ $c == d* || $c == unmark* ]]; then + file=$(echo "$c" | awk '{ print $2 }') + file="$wd$file" + echo "$RESTORELIST" | tr ' ' '\n' | egrep -q "^$file$" + if [[ $? -eq 0 ]]; then + #RESTORELIST=$(echo "$RESTORELIST" | sed -e 's/^ //' | tr ' ' '\n' | grep -wv $file | tr '\n' ' ' | egrep -v "^$" | sed -e 's/^/ /') + RESTORELIST=$(echo "$RESTORELIST" | sed -e "s, $file,,") + action "Removed from restore list: $BOLD$file" + else + error "Restore list doesn't contain '$file'" + fi + elif [[ $c == cd* ]]; then + newdir=$(echo "$c" | awk '{ print $2 }') + olddir=$wd + needupdate=1 + if [[ $newdir == "/" ]]; then + wd=$newdir + rv=0 + elif [[ $newdir == /* ]]; then + wd=$newdir/ + updatedir + rv=$? + needupdate=0 + elif [[ $newdir == */* ]]; then + wd=$wd$newdir/ + updatedir + rv=$? + needupdate=0 + elif [[ $newdir == ".." ]]; then + wd=$(dirname $wd) + rv=0 + else + echo "$subdirs" | grep -qw "$newdir" + rv=$? + if [[ $rv -eq 0 ]]; then + wd=$wd$newdir/ + fi + fi + if [[ $rv -eq 0 ]]; then + [[ $needupdate -eq 1 ]] && updatedir + else + error "no such directory '$newdir'" + wd=$olddir + fi + elif [[ $c == lcd* ]]; then + newlocaldir=$(echo "$c" | awk '{ print $2 }') + if [[ -d $newlocaldir ]]; then + RESTOREDIR="$newlocaldir" + elif [[ -e $newlocaldir ]]; then + error "'$newlocaldir' is not a directory" + else + error "'$newlocaldir' does not exist" + fi + else + error "bad command '$c' (? for help)" + fi + echo + action "Restore path: $RESTOREDIR" + echo -en "$CYAN$wd> $PLAIN" + read c + done + else + # restorelist not empty + doit=1 + fi + if [[ $doit -eq 1 ]]; then + showrestoreinfo + action "Performing restore..." + echo + RESTOREOPTS="" + for r in $RESTORELIST; do + RESTOREOPTS="$RESTOREOPTS -i $r" + done + [[ $VERBOSE -eq 1 ]] && log "debug: running: ${RESTIC} restore $AUTHOPTS $CONNECTIONSOPTS -t "$RESTOREDIR" $RESTOREOPTS $RESTORESNAPID" + ${RESTIC} restore $AUTHOPTS $CONNECTIONSOPTS -t "$RESTOREDIR" $RESTOREOPTS $RESTORESNAPID 2>&1 >> ${LOG} rv=$? + + if [[ $rv -eq 0 ]]; then + # check that we got them all + tried=0 + success=0 + failed=0 + for r in $RESTORELIST; do + tried=$((tried + 1)) + if [[ -e $RESTOREDIR/$r ]]; then + success=$((success + 1)) + else + failed=$((failed + 1)) + fi + done + echo + echo -en "${CYAN}${BOLD}Restored $success / $tried items$PLAIN" + [[ $failed -gt 0 ]] && cecho "$RED" " ($failed failed)" || echo + echo + if [[ $success -eq $tried ]]; then + rv=0 + else + rv=1 + fi + fi + else + action "${BOLD}Aborting..." + rv=1 fi elif [[ $CMD == "ls" ]]; then + + [[ $VERBOSE -eq 1 ]] && log "debug: running: ${RESTIC} snapshots $AUTHOPTS $CONNECTIONSOPTS $OTHEROPTS " ${RESTIC} snapshots $AUTHOPTS $CONNECTIONSOPTS 2>&1 >> ${LOG} rv=$? elif [[ $CMD == "stats" ]]; then @@ -815,9 +1218,6 @@ for f in $REPOSTOBACKUP; do else cmdtorun="${RESTIC} init $AUTHOPTS $CONNECTIONSOPTS" if [[ $VERBOSE -eq 1 ]]; then - - - log "export RESTIC_REPOSITORY=\"$REPO_PATH\"" log "export RESTIC_PASSWORD_FILE=`getrepopassfile \"$f\"`" log "will run: [${cmdtorun}]" @@ -837,16 +1237,18 @@ for f in $REPOSTOBACKUP; do fi elif [[ $mode == "rsync" ]]; then REMOTE="" - if [[ $CMD == "go" ]]; then - #echo run ${RSYNC} ${RSYNC_OPTIONS} $DATAPATH/ ${RSYNC_REPOSITORY} 2>&1 >> ${LOG} - #exit 1 + checktag "$f" postgres + if [[ $? -eq 0 ]] ; then + log "Error: postgres backups not supported for rsync mode." + rv=1 + elif [[ $CMD == "go" ]]; then ${RSYNC} ${RSYNC_OPTIONS} $DATAPATH/ ${RSYNC_REPOSITORY} 2>&1 >> ${LOG} rv=$? elif [[ $CMD == "ls" ]]; then ssh ${DEF_RSYNC_USER}@${DEF_RSYNC_SERVER} ls ${RSYNC_FULLDIR} 2>&1 >> ${LOG} rv=$? elif [[ $CMD == "stats" ]]; then - echo "Size of remote data for ${REPO}:" + action "Size of remote data for ${REPO}:" ssh ${DEF_RSYNC_USER}@${DEF_RSYNC_SERVER} du -hs ${RSYNC_FULLDIR} 2>&1 >> ${LOG} rv=$? elif [[ $CMD == "diff" ]]; then @@ -866,7 +1268,7 @@ for f in $REPOSTOBACKUP; do fi fi elif [[ $CMD == "forget" ]]; then - log "Error: forget not supported for rsync mode." + log "Error: forget not supported for $mode mode." rv=1 else log "Error: invalid command $CMD" @@ -875,7 +1277,11 @@ for f in $REPOSTOBACKUP; do else # rclone REMOTE="" - if [[ $CMD == "go" ]]; then + checktag "$f" postgres + if [[ $? -eq 0 ]] ; then + log "Error: postgres backups not supported for $mode mode." + rv=1 + elif [[ $CMD == "go" ]]; then ${RCLONE} sync $RCLONEOPTS $CONNECTIONSOPTS $SPEEDOPTS $OTHEROPTS $DATAPATH $RCLONE_REPOSITORY 2>&1 >> ${LOG} rv=$? elif [[ $CMD == "ls" ]]; then @@ -965,3 +1371,4 @@ if [[ $CMD == "check" ]]; then fi exit $rv fi +