#!/bin/bash
# ================================================================================
#
# RESTORE - interactive restore
#
# Restore duplicity backup data to a local directory.
# Please notice: duplicity does not overwrite any dir or file. So the restore
# path is different to the path of the backed up data.
#
# SYNTAX:
# restore.sh [path-to-restore]
#
#   path-to-restore - optional
#   default: none ... you get an interactive selection of backup sets
#
# --------------------------------------------------------------------------------
# ah - Axel Hahn <axel.hahn@iml.unibe.ch>
# ds - Daniel Schueler <daniel.schueler@iml.unibe.ch>
#
# 2016-11-17  ah,ds  v1.0
# 2017-10-11  ah,ds  v1.1  added support for duplicity param --ssh-backend
# 2018-08-27  ah,ds  v1.2  fix restore target with a given selection; handle '*' placeholder
# 2019-06-05  ah,ds  v1.3  add custom cache dir
# ================================================================================


# --------------------------------------------------------------------------------
# CONFIG
# --------------------------------------------------------------------------------

  # . `dirname $0`/inc_config.sh

  . `dirname $0`/jobhelper.sh
  . `dirname $0`/inc_bash.sh

  STORAGE_BASEDIR=`_j_getvar ${STORAGEFILE} "storage"`

  # check
  if [ -z $STORAGE_BASEDIR ]; then
    echo ERROR: missing config for backup target.
    echo There must be an entry storage in ${STORAGEFILE}
    exit 1
  fi

  # ----- read something from config files
  RESTORE_BASEDIR=`_j_getvar ${STORAGEFILE} "restore-path"`
  PASSPHRASE=`_j_getvar ${STORAGEFILE} "gnupg-passphrase"`
  export PASSPHRASE

  sFileSshPrivkey=`_j_getvar ${STORAGEFILE} "ssh-privatekey"`

  sParams=

  # task#1623 - fallback ssh backend for Debian 8
  sSshBackend=`_j_getvar ${STORAGEFILE} "ssh-backend"`
  if [ ! -z $sSshBackend ]; then
    sParams="${sParams} --ssh-backend $sSshBackend"
  fi

  sFileSshPrivkey=`_j_getvar ${STORAGEFILE} "ssh-privatekey"`
  if [ ! -z $sFileSshPrivkey ]; then
    sParams="${sParams} --ssh-options="""-oIdentityFile=${sFileSshPrivkey}""" "
  fi

  # task#3046 - add custom cache dir
  sCacheDir=`_j_getvar ${STORAGEFILE} "cachedir"`
  if [ ! -z $sCacheDir ]; then
    sParams="${sParams} --archive-dir=$sCacheDir"
  fi

  # ----- what to restore ...
  sDir2restore= 
  sRestoreItem=
  sDate=`date +%Y-%m-%d`

  # duplicity target
  sTarget=

# --------------------------------------------------------------------------------
# FUNCTIONS
# --------------------------------------------------------------------------------

# ----------------------------------------------------------------------
# enter name of directory to restore; menu item
# ----------------------------------------------------------------------
function enterBackupDir(){

  h2 "select a directory to be restored"

  echo "This is a list of the directories from the backup configuration."
  echo "Enter the full path"
  echo

  echo "Enter the full path; marked entries with (*) do not exist on this machine"
  echo

  sDirs="`j_getDirs2Backup`"
  for mydir in $sDirs
  do
    if [ ! -d "$mydir" ]; then
      color error
      echo "$mydir (*)"
      color reset
    else
      echo "$mydir"
    fi
  done

  color input
  echo -n "[$sDir2restore] >"
  color reset
  read myinput

  setBackupDir "${myinput}"

}

# ----------------------------------------------------------------------
# enter date to restore from; menu item
# ----------------------------------------------------------------------
function enterDate(){

  h2 "edit date to restore"

  echo "The acceptible time strings are intervals (like \"3D64s\"), w3-datetime"
  echo "strings, like \"2002-04-26T04:22:01-07:00\" (strings like"
  echo "\"2002-04-26T04:22:01\" are also acceptable - duplicity will use the"
  echo "current time zone), or ordinary dates like 2/4/1997 or 2001-04-23"
  echo "(various combinations are acceptable, but the month always precedes"
  echo "the day)."
  echo
  echo "today in YYYY-MM-DD: `date +%Y-%m-%d`"
  echo 
  color input
  echo -n "[$sDate] >"
  color reset
  read sNewDate
  if [ ! -z $sNewDate ]; then
    sDate=$sNewDate
  fi
}

# ----------------------------------------------------------------------
# enter relative path to restore; menu item
# ----------------------------------------------------------------------
function enterRestoreitem(){

  h2 "set restore item (path)"

  echo "enter a relative path behind ${sDir2restore}/"
  echo "empty means: all data"
  echo 
  color input
  echo -n "[$sRestoreItem] >"
  color reset
  read sNewsRestoreItem

  if [ ! -z $sNewsRestoreItem ]; then
    sRestoreItem=$sNewsRestoreItem
    setVars
  fi
  getRemoteFiles
}


# ----------------------------------------------------------------------
# search a file in backup; menu item
# ----------------------------------------------------------------------
function searchFile(){
  h2 "search a file in backup --time $sDate"

  echo "Enter something that was backuped from ${sDir2restore}"
  echo "searchtext matches anywhere in filename or path - it is a grep grep in"
  echo "duplicity list-current-files --time $sDate [target]"
  echo "Press just Return to exit"
  echo
  color input
  echo -n "search for >"
  color reset
  read sSearch
  if [ ! -z $sSearch ]; then
    getRemoteFiles $sSearch
  fi
}

# ----------------------------------------------------------------------
# verify backup set with local files; menu item
# ----------------------------------------------------------------------
function verify(){

  h2 "verify"

  echo duplicity verify ${sParams} ${sTarget} -v4 $sDir2restore
  color cmd
  duplicity verify ${sParams} ${sTarget} -v4 $sDir2restore
  fetchrc
  color reset

}

# ----------------------------------------------------------------------
# show file changes in backupsets; menu item
# work in progress ... I don't know how to match a file...
# ----------------------------------------------------------------------
function showFilechanges(){

  tmpcache=~/.cache/duplicity-changes-${sSafeName}.txt

  h2 "show file changes"

  egrep "\ \ (changed|deleted|new)\ \ " `fgrep -l "Localdir ${sDir2restore}" ~/.cache/duplicity/*/*.manifest` >$tmpcache
  # ls -l $tmpcache
  # head $tmpcache
 
  echo "I hope you made a search first"
  echo "Enter filename with complete path that was backuped from ${sDir2restore}"
  echo "Press just Return to exit"
  echo
  color input
  echo -n "search for >"
  color reset
  read sSearch
  if [ ! -z "${sSearch}" ]; then
    echo  grep "${sSearch}"
    color cmd
    # duplicity collection-status --file-changed "`basename ${sSearch}`" ${sParams} ${sTarget}/`dirname "${sSearch}"`
    cat $tmpcache | grep "${sSearch}" | while read line
    do
       sManifest=`echo $line | cut -f 1 -d ":"`

       # duplicity-inc.20161111T152106Z.to.20161111T152141Z.manifest
       sTime=`basename "${sManifest}" | cut -f 2 -d "."` 
       sOut=`echo $line | cut -f 2- -d ":"`

       echo $sTime $sOut | sed "s#new#new    #" 
    done | sort
    echo
    color reset
  fi
}

# ----------------------------------------------------------------------
# show remote volumes with incremental and full backups
# ----------------------------------------------------------------------
function getRemoteVolumes(){
  tmpoutVolumes=/tmp/outvolumelist_$$

  h2 "volumes for $sDir2restore"

  echo duplicity collection-status ${sParams} ${sTarget}
  color cmd
  duplicity collection-status ${sParams} ${sTarget} | tee -a $tmpoutVolumes
  fetchrc
  color reset
  echo

  if [ `cat $tmpoutVolumes | egrep "(Full|Incremental)" | wc -l` -eq 0 ]; then
    color error
    echo "ERROR: no backup sets were found for directory [$sDir2restore]"
    echo
    color reset
    sDir2restore=
    setVars
  else
    color ok
    echo "OK,  `cat $tmpoutVolumes | grep "Full" | wc -l` Full and  `cat $tmpoutVolumes | grep "Incremental" | wc -l` incremental backups"
    color reset
  fi

  rm -f $tmpoutVolumes

}

# ----------------------------------------------------------------------
# list files from backupset
# ----------------------------------------------------------------------
function getRemoteFiles(){
  iLines=50
  tmpout=/tmp/outfilelist_$$
  sSearch=$1
  if [ -z $sSearch ]; then
    sSearch="^...\ ...\ ..\ ........\ ....\ $sRestoreItem"
  fi
  echo
  echo
  echo --- files ... filtered for date [$sDate] by [$sSearch]
  echo duplicity list-current-files --time $sDate ${sParams} ${sTarget} 
  color cmd
  duplicity list-current-files --time $sDate ${sParams} ${sTarget} 2>&1 | grep "$sSearch" | tee -a $tmpout | head -$iLines
  fetchrc
  color reset
  if [ `cat $tmpout | wc -l` -eq 0 ]; then
    color error
    echo ERROR: your file does not match ... try another time ... or another search string
    color reset
  else
    echo ... max $iLines lines ... maybe the output was cut
    echo
    if [ -z $1 ]; then
      echo
      doRestoreDryRun
      echo
      echo
    fi
  fi
  rm -f $tmpout
}

# ----------------------------------------------------------------------
# set backup dir .. called when using cli parameter or enterBackupDir
# param  string   full path of a directory to retore
# ----------------------------------------------------------------------
function setBackupDir(){
  if [ ! -z $1 ]; then
    sDirs2Backup="`j_getDirs2Backup`"
    bFound=0
    for mydir in ${sDirs2Backup}
    do
      if [ "${mydir}" = "${1}" ]; then
        bFound=1
      fi
    done

    if [ $bFound -eq 0 ]; then
      color error
      echo
      echo "WARNING: seems to be wrong ... "
      echo "At the moment I restore only known targets."
      color reset

      # TODO: if we want to retore other backup sets of other servers
      # we cannot be as strict

    else

      sDir2restore=$1
      setVars
      getRemoteVolumes

    fi
  fi
}

# ----------------------------------------------------------------------
# internal: set variables for target path and backup set to make 
# duplicity calls
# ----------------------------------------------------------------------
function setVars(){
  
  sSafeName=`j_getSafename "$sDir2restore"`
  sTarget=`j_getFullTarget "$sDir2restore"`

  sRestorepath="${RESTORE_BASEDIR}/${sSafeName}"
  if [ ! -z $sRestoreItem ]; then
    echo ${sRestoreItem} | grep '\*' >/dev/null
    if [ $? -eq 0 ]; then
      sRestoreItem=`dirname $sRestoreItem | sed 's#^\.##'`
      color error
      echo ERROR: using a placeholder is not allowed. Using the directory above.
      echo [$sRestoreItem]
      color reset
    fi

    sRestorepath="${sRestorepath}/${sRestoreItem}"
  fi
}

# ----------------------------------------------------------------------
# restore dry run ... called in getRemoteFiles
# ----------------------------------------------------------------------
function doRestoreDryRun(){
  echo "--- dry run"
  echo duplicity restore --dry-run --file-to-restore "$sRestoreItem" --time $sDate ${sParams} ${sTarget} ${sRestorepath}
  color cmd
  duplicity restore --dry-run --file-to-restore "$sRestoreItem" --time $sDate ${sParams} ${sTarget} ${sRestorepath}
  fetchrc
  color reset

}

# ----------------------------------------------------------------------
# restore and finish the script; menu item
# ----------------------------------------------------------------------
function doRestore(){
  mkdir -p $sRestorepath

  h2 "RESTORE"

  echo duplicity restore --file-to-restore "$sRestoreItem" --time $sDate ${sParams} ${sTarget} ${sRestorepath}
  color cmd
  duplicity restore --file-to-restore "$sRestoreItem" --time $sDate ${sParams} ${sTarget} ${sRestorepath}
  fetchrc
  color reset
  echo
  echo
  echo Restore is finished.
  echo Have look to the output above.
  echo "The restore path has `find ${sRestorepath} -type f | wc -l` files (`du -hs ${sRestorepath} | awk '{ print $1 }'`)"
  echo 
  echo find ${sRestorepath}

  echo
  exit 0
}


# --------------------------------------------------------------------------------
# MAIN
# --------------------------------------------------------------------------------

h1 "RESTORE - GET FILES FROM STORAGE"

# ----- Check requirements

  j_requireUser "root"
  j_requireBinary "duplicity"

# ----- set a directory to restore to have a useful initial point

  setBackupDir $1
  if [ -z $sDir2restore ]; then
    enterBackupDir
  fi

# ----- menu and loop

  while true
  do

    h1 "Restore :: Menu"

    # getRemoteVolumes
    # getRemoteFiles

    echo
    echo " D - directory to restore: $sDir2restore"
    echo " T - time                : $sDate"
    echo " W - what to restore     : $sRestoreItem"
    echo
    echo " C - show file changes"
    echo " S - search file"
    echo " V - verify"
    echo " B - Bash (Shell)"
    echo
    echo " R - start restore"
    echo
    echo "     retore from         : $sTarget"
    echo "     retore to           : $sRestorepath"
    echo -n "                           "
    ls -d $sRestorepath >/dev/null 2>&1
    if [ $? -eq 0 ]; then
      color error
      echo "WARNING: directory already exists! Backup will fail."
      color reset
    else
      echo "OK, it does not exist yet"
    fi
    echo
    echo " X - exit"
    echo

    color input
    echo -n "Select (not case sensitive) --> "
    color reset
    read action
    echo

    case $action in
      d|D)
        enterBackupDir
        ;;
      t|T)
        enterDate
        ;;
      c|C)
        showFilechanges
        ;;
      s|S)
        searchFile
        ;;
      v|V)
        verify
        ;;
      b|B)
        echo type exit to return...
        export PS1="[`basename $0` \u@\h \w]\$ "
        bash
        ;;
      w|W)
        enterRestoreitem
        ;;
      r|R)
        doRestore
        ;;
      x|X)
        exit 0
        ;;
      *)
        echo "Try again"
    esac


  done


# --------------------------------------------------------------------------------