Skip to content
Snippets Groups Projects
restic.sh 11.70 KiB
#!/bin/bash
# ================================================================================
#
# TRANSFER :: PLUGIN - TEMPLATE
#
# this script will be included in ../../transfer.sh
#
# --------------------------------------------------------------------------------
# ah - Axel Hahn <axel.hahn@iml.unibe.ch>
# 2021-05-19  ah    v0.0   INIT ... WIP
# 2022-01-06  ah    v0.0   added support for Repository with REST and authentication 
# ================================================================================

# --------------------------------------------------------------------------------
# INIT
# --------------------------------------------------------------------------------


    function t_checkRequirements(){
        j_requireBinary "restic"
        j_requireUser "root"
    }

    function t_setVars(){
        export RESTIC_PASSWORD=$PASSPHRASE

        # if we set RESTIC_REPOSITORY then "-r TARGET" is not 
        # needed in restic commands

        # TODO: for restic with https and auth - remove the host in the path
        local _target=$( j_getFullTarget )
        echo ${_target} | grep "https.*@" >/dev/null
        if [ $? -eq 0 ]; then
            local _host=$( hostname -f )
            _target=$( echo $_target | sed "s#${_host}/##" )
        fi

        export RESTIC_REPOSITORY=$_target

        # WORKAROUND for bug while writing on a SMB target
        export GODEBUG="asyncpreemptoff=1"

        RESTORE_ITEM=latest
        RESTIC_MOUNTPOINT=$( _j_getvar ${STORAGEFILE} "${CFGPREFIX}mountpoint" )
    }

# --------------------------------------------------------------------------------
# GENERATE PARAMS :: ALL DIRS
# --------------------------------------------------------------------------------

    # return a string with default params
    # param  string  param1 of transfer.sh; one of full|inc|auto
    # param  string  param2 of transfer.sh; for auto: date i.e. 3M for 3 monthes
    function t_getParamDefault(){
        # verbose to see more details
        echo -n --verbose=$( _j_getvar ${STORAGEFILE} "${CFGPREFIX}verbose" )

    }
    # return a string with backup parameters that will be added to defaults
    function t_getParamBackup(){
        local _tag
        local _nocacheFlag

        # tagging
        _tag=$( _j_getvar ${STORAGEFILE} "${CFGPREFIX}tag" )
        if [ "$_tag" != "" ]; then
            echo -n "--tag $_tag "
        fi

        # no cache ... to create smaller local cache dirs, but backup 3 times slower 
        _nocacheFlag=$( _j_getvar ${STORAGEFILE} "${CFGPREFIX}nocache" )
        if [ "$_nocacheFlag" != "" ] && [ "$_nocacheFlag" != "0" ] && [ "$_nocacheFlag" != "false" ]; then
            echo -n "--no-cache "
        fi
    }

    # return a cli parameter for a single exlude directory
    # param  string  cache directory for local index files
    function t_getParamCacheDir(){
        if [ ! -z "$1" ]; then
            local sCacheDir="$1"
            if [ ! -d $sCacheDir ]; then
                mkdir -p $sCacheDir
                chmod 750 $sCacheDir
            fi
            echo --cache-dir $sCacheDir
        fi
    }

    # return a cli parameter for a single exlude directory
    # param  string  exlude pattern
    function t_getParamExlude(){
        test -z "$1" || echo --exclude "'"$*"'"
    }
    # return a cli parameter for a single exlude directory
    # param  string  exlude pattern
    function t_getParamInlude(){
        test -z "$1" || echo --include "'"$*"'"
    }

    # return a cli parameter to use an ssh keyfile
    # param  string  filename if ssh private key file
    function t_getParamSshKey(){
        if [ ! -z "$1" ]; then
            echo $STORAGE_BASEDIR | grep -E "^(scp|sftp|rsync):" >/dev/null
            if [ $? -eq 0 ]; then
                # scp://backup@storage.example.com//netshare/backup/one
                #       ^^^^^^^^^^^^^^^^^^^^^^^^^^
                #       sshtarget fetches user@host ... if protocol matches
                local sshtarget=$( echo $STORAGE_BASEDIR | cut -f 3 -d '/' )
                echo -o sftp.command="'"ssh -i ${1} ${sshtarget} -s sftp"'"
            fi
        fi
    }

# --------------------------------------------------------------------------------
# BACKUP ACTIONS :: TRANSFER
# --------------------------------------------------------------------------------
    # pre backup actions
    # uses global vars from ../../transfer.sh
    # function t_backupDoPreTasks(){
    function t_backupDoPreTasks(){

        local _mycmd="restic init ${ARGS_DEFAULT}"
        echo $_mycmd
        color cmd 
        eval $_mycmd
        local _myrc=$?
        color reset

        # detect return code ... do not abort on any error.
        t_rcCheckInit $_myrc

    }


    # post backup actions
    # uses global vars from ../../transfer.sh
    function t_backupDoPostTasks(){

        echo "--- CLEANUP local data:"
        echo restic cache --cleanup
        color cmd
        restic cache --cleanup
        local _myrc=$?
        color reset
        case $_myrc in
            0) color ok;      echo "OK" ;;
            *) color error;   echo "Cleanup error - returncode was $_myrc" ;;
        esac
        color reset
        echo

        echo "--- UNLOCK ... just in case :-)" 
        echo restic unlock ${ARGS_DEFAULT}
        color cmd
        eval restic unlock ${ARGS_DEFAULT}
        color reset
        echo

        echo "--- PRUNE"
        local _tag=$( _j_getvar ${STORAGEFILE} "${CFGPREFIX}tag")

        local _keep_h=$( _j_getvar ${STORAGEFILE} "${CFGPREFIX}keep-hourly")
        local _keep_d=$( _j_getvar ${STORAGEFILE} "${CFGPREFIX}keep-daily")
        local _keep_w=$( _j_getvar ${STORAGEFILE} "${CFGPREFIX}keep-weekly")
        local _keep_m=$( _j_getvar ${STORAGEFILE} "${CFGPREFIX}keep-monthly")
        local _keep_y=$( _j_getvar ${STORAGEFILE} "${CFGPREFIX}keep-yearly")
        
        local _mycmd="restic forget \
          ${ARGS_DEFAULT} \
          --tag $_tag \
          --group-by "paths,tags" \
          --keep-hourly $_keep_h \
          --keep-daily $_keep_d \
          --keep-weekly $_keep_w \
          --keep-monthly $_keep_m \
          --keep-yearly $_keep_y"
        echo $_mycmd
        color cmd 
        eval $_mycmd
        local _myrc=$?
        color reset

        t_rcCheckCleanup $_myrc
        echo
    }

# --------------------------------------------------------------------------------
# BACKUP ACTIONS :: SINGLE DIR
# --------------------------------------------------------------------------------

    # get target url/ directory
    # param  string  directory to backup
    function t_backupDirGetTarget(){
        # directory based target
        # j_getFullTarget "$1"
        # for host based backup target - remove param:
        j_getFullTarget ""
    }

    function t_backupDirGetCmdBackup(){
        echo eval restic backup ${ARGS_DEFAULT} ${ARGS_BACKUP} ${mydir}
    }

    # pre backup actions
    # uses global vars from ../../transfer.sh
    function t_backupDirDoPreTasks(){
        echo "Nothing to do."
    }


    # post backup actions
    # uses global vars from ../../transfer.sh
    function t_backupDirDoPostTasks(){
        echo "Nothing to do."
    }

# --------------------------------------------------------------------------------
# RESTORE
# --------------------------------------------------------------------------------

    # show stored volumes on backup repository
    # used in restore; directory param is checked before
    # param  string  name of backup dir, i.e. /etc
    function t_restoreDoShowVolumes(){
        eval restic snapshots ${ARGS_DEFAULT} --path ${BACKUP_DIR}
    }

    # select a snapshot to restore from
    function t_restoreDoSelect(){
        local _selid=
        echo "--- Existing snapshots:"
        color cmd
        t_restoreDoShowVolumes
        color reset
        showPrompt "ID of the snapshot or \"latest\" to restore from [${RESTORE_ITEM}] >"
        read _selid
        test -z "$_selid" && _selid=${RESTORE_ITEM}
        RESTORE_ITEM=${_selid}

        test "$RESTORE_ITEM" = "latest" && RESTORE_ITEMINFO="automatic value"
        test "$RESTORE_ITEM" = "latest" || RESTORE_ITEMINFO=$( t_restoreDoShowVolumes | grep "^${RESTORE_ITEM} " | awk '{ print $2 " " $3} ' )

        if [ -z "${RESTORE_ITEMINFO}" ]; then
            color error
            echo ERROR: A napshot ID \"${_selid}\" does not exist.
            RESTORE_ITEM=latest
            color reset
        fi
        echo using \"${RESTORE_ITEM}\"
        echo
    }

    # set a filter to reduce count of files to restore
    function t_restoreDoSetIncludeFilter(){
        local _inc=
        echo "You can enter ..."
        echo "  - a single directory name anywhere in the folderstructure"
        echo "  - a filename without path"
        echo "  - a filemask"
        showPrompt "Include >"
        read _inc
        RESTORE_FILTER="$( t_getParamInlude "${_inc}" )"
        echo using parameter \"${RESTORE_FILTER}\"
        echo
    }
    # show stored volumes on backup repository
    # used in restore; directory param is checked before
    # param  string  name of backup dir, i.e. /etc
    function t_restoreDoRestore(){
        echo "eval restic restore ${ARGS_DEFAULT} --path ${BACKUP_DIR} --target ${RESTORE_TARGETPATH} ${RESTORE_FILTER} ${RESTORE_ITEM}"
    }

    # Mount backup data
    # see "restic help mount"
    function t_restoreDoMountBackupdata(){
        local _cmd=
        echo "HINT: This feature requires fuse. It works with root on UNIX/ LINUX platforms - not on MS Windows."
        echo "      It can mount a single directory and shows all snapshots (not only [$RESTORE_ITEM])."
        echo
        if [ -z "$RESTIC_MOUNTPOINT" ]; then
            color error
            echo "ERROR: no mountpoint was set in ${STORAGEFILE}; example: restic_mountpoint = /mnt/restore"
            color reset
        else
            j_requireUser "root"
            test -d "$RESTIC_MOUNTPOINT" || mkdir -p $RESTIC_MOUNTPOINT
            _cmd="restic mount ${ARGS_DEFAULT} $RESTIC_MOUNTPOINT"
            test -z "${BACKUP_DIR}" || _cmd="restic mount ${ARGS_DEFAULT} --path ${BACKUP_DIR} $RESTIC_MOUNTPOINT"
            echo $_cmd
            color cmd
            eval $_cmd
            color reset
        fi
    }

    # search a file in the given snapshot and backup dir
    # param  string  regex to filter
    function t_restoreDoSearchFile(){
        eval restic ls ${ARGS_DEFAULT} --path "${BACKUP_DIR}" ${RESTORE_ITEM} | grep -E "$1"
    }

# --------------------------------------------------------------------------------
# VERIFY RETURNCODES
# --------------------------------------------------------------------------------

    # init repository
    function t_rcCheckInit(){
        case $1 in
            0) color ok;      echo "OK - the repository was created." ;;
            1) color warning; echo "You can ignore the error if the repository was initialized already." ;;
            *) color error;   echo "Verify output above - returncode of restic init was $1" ;;
        esac
        color reset
    }
    # backup files
    function t_rcCheckBackup(){
        case $1 in
            0) color ok;      echo "OK" ;;
            1) color error;   echo "Unable to connect with restic repository." ;;
            *) color error;   echo "Backup error - returncode was $1" ;;
        esac
        color reset
    }

    # repository cleanup
    function t_rcCheckCleanup(){
        case $1 in
            0) color ok;      echo "OK" ;;
            *) color error;   echo "Cleanup error - returncode was $1" ;;
        esac
        color reset
    }

    # restore files
    function t_rcCheckRestore(){
        case $1 in
            0) color ok;      echo "OK" ;;
            *) color error;   echo "Restore error - returncode was $1" ;;
        esac
        color reset
    }

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