-
Hahn Axel (hahn) authoredHahn Axel (hahn) authored
transfer.sh 14.91 KiB
#!/bin/bash
# ================================================================================
#
# TRANSFER LOCAL DATA TO BACKUP STORAGE
#
# SYNTAX:
# transfer.sh - incremental backup
# transfer.sh full - full backup
# transfer.sh dumps - transfer local dumps only
# transfer.sh prune - cleanup backup data
# transfer.sh help - show help
#
# --------------------------------------------------------------------------------
# ah - Axel Hahn <axel.hahn@iml.unibe.ch>
# ds - Daniel Schueler <daniel.schueler@iml.unibe.ch>
#
# 2016-11-10 ah,ds v1.0
# 2016-12-19 ah,ds v1.1 added parameter "dumps"
# 2017-02-16 ah,ds v1.2 added support for storage slots
# 2017-10-11 ah,ds v1.3 added support for duplicity param --ssh-backend
# 2017-10-17 ah,ds v1.4 remove PIPESTATUS for Debian8 compatibility
# 2017-11-17 ah,ds v1.5 check pid of lockfile in process list if process still runs
# 2018-06-19 ah,ds v1.6 replace --exclude with --exclude regexp in custom dirs
# 2019-06-05 ah,ds v1.7 add custom cache dir
# 2019-09-09 ah,ds v1.8 add testfile on target
# 2019-10-30 ah,ds v1.9 for rsync targets: create remote target dir with ssh command
# 2020-01-21 ah,ds v1.10 show colored OK or FAILED at the end of output
# 2020-02-25 ah,ds, v1.11 fix test -z with non existing vars; show final backup status
# 2021-01-29 ah,ds, v1.12 abort on empty passphrase
# 2021-05-19 ah,ds, v2.0 plugin driven to support multiple backup tools (duplicity + restic)
# 2021-12-02 ah v2.1 added parameter "prune" to cleanup only
# 2022-02-10 ah v2.2 update logging (removing tee)
# 2022-10-01 ah v2.3 customize prune and verify action
# 2022-10-04 ah v2.4 prune and verify are non directory based
# 2022-10-07 ah v2.5 unescape regex with space to prevent "grep: warning: stray \ before white space"
# 2022-10-20 ah v2.6 move hook 20-before-transfer (after init of the backup tool)
# 2022-10-21 ah v2.7 shell fixes;
# 2022-11-04 ah v2.8 rename hooks
# 2022-11-07 ah v2.9 run brefore-transfer hook after registration
# 2023-02-22 ah v2.10 fix touch of last_backup
# ================================================================================
# --------------------------------------------------------------------------------
# CONFIG
# --------------------------------------------------------------------------------
# . `dirname $0`/inc_config.sh
. $(dirname $0)/jobhelper.sh
. $(dirname $0)/inc_bash.sh
typeset -i rc=0
typeset -i doBackup=1
typeset -i doPrune
typeset -i doVerify
typeset -i doValue
if [ ! -r "${DIRFILE}" -o ! -r "${STORAGEFILE}" ]; then
echo "SKIP backup of local files - one of the files is not readable (no error): ${DIRFILE} | ${STORAGEFILE}"
exit 0
fi
STORAGE_BIN=$(_j_getvar "${STORAGEFILE}" "bin")
STORAGE_BASEDIR=$(_j_getvar "${STORAGEFILE}" "storage")
STORAGE_TESTFILE=$(_j_getvar "${STORAGEFILE}" "storage-file")
PASSPHRASE=$(_j_getvar "${STORAGEFILE}" "passphrase")
STORAGE_REGISTER=$(_j_getvar "${STORAGEFILE}" "storage-register")
typeset -i TIMER_TRANSFER_START
TIMER_TRANSFER_START=$(date +%s)
# check
if [ -z "$STORAGE_BIN" ]; then
STORAGE_BIN=restic
fi
CFGPREFIX=${STORAGE_BIN}_
if [ -z "$STORAGE_BASEDIR" ]; then
color error
echo ERROR: missing config for backup target.
echo There must be an entry "storage = " in ${STORAGEFILE}
color reset
exit 1
fi
if [ -n "$STORAGE_TESTFILE" -a ! -f "$STORAGE_TESTFILE" ]; then
color error
echo "ERROR: missing testfile $STORAGE_TESTFILE on backup target."
echo "The Backup target disk / NAS is not mounted."
color reset
exit 1
fi
# support old value
if [ -z "${PASSPHRASE}" ]; then
echo "WARNING: The value gnupg-passphrase in ${STORAGEFILE} is deprecated. Replace it with passphrase=..."
PASSPHRASE=$(_j_getvar "${STORAGEFILE}" "gnupg-passphrase")
fi
if [ -z "${PASSPHRASE}" ]; then
echo "ERROR: no value passphrase was set in ${STORAGEFILE} to encrypt backup data."
echo "Aborting."
exit 1
fi
# For duplicity only
# METHOD incremental is default; full backup will be triggered with
# first param "full"
METHOD=
ACTION=backup
transferlog="${DIR_LOGS}/transfer-$(date +%Y%m%d-%H%M%S).log"
lockfile="${DIR_LOGS}/transfer.running"
lastbackupfile="${DIR_LOGS}/last_backup"
lastprunefile="${DIR_LOGS}/last_prune"
lastverifyfile="${DIR_LOGS}/last_verify"
rcfile=/tmp/transfer-rc.$$.tmp
# --------------------------------------------------------------------------------
# FUNCTIONS
# --------------------------------------------------------------------------------
# get age of a file in sec
# https://www.axel-hahn.de/blog/2020/03/09/bash-snippet-alter-einer-datei/
# param string filename to test
# return integer
function _getFileAge(){
echo $(($(date +%s) - $(date +%s -r "$1")))
}
# set default behaviur based on limit (from config) and its last
# execution (read from touchfile)
# Function is enabled if no touchfile was found or it is older than
# given limit
#
# It is used to set flags for prune and verify
# param string name of action; one of prune|verify
# param string filename of touchfile of last execution
function setAction(){
local action=$1
local myfile=$2
typeset -i local iLimit
iLimit=$(_j_getvar ${STORAGEFILE} "$action-after")
if [ ! -f "${myfile}" ]; then
echo "Info: $action is ENABLED - no last $action detected"
doValue=1
else
typeset -i iLastDone
iLastDone=$( _getFileAge "${myfile}" )/60
typeset -i iLastDoneD=iLastDone/60/24
echo "Info: Last $action was $iLastDone min ago ($iLastDoneD days). Limit is $iLimit days."
if [ $iLastDoneD -ge $iLimit ]; then
echo "Info: $action is ENABLED - last $action is outdated"
doValue=1
else
echo "Info: $action is not needed yet."
doValue=0
fi
fi
}
# --------------------------------------------------------------------------------
# MAIN
# --------------------------------------------------------------------------------
if [ "$1" = "help" -o "$1" = "-h" -o "$1" = "-?" ]; then
echo "HELP:
Transfer local files to a backup target.
target $( echo ${STORAGE_BASEDIR} | sed 's#:[^:]*@#:**********@#' )
backup tool $STORAGE_BIN
PARAMETERS:
transfer.sh - incremental backup
transfer.sh full - full backup (Duplicity only)
transfer.sh dumps - transfer local dumps only
transfer.sh prune - cleanup backup data only (no backup)
transfer.sh verify - verify backup data (no backup)
transfer.sh help - show this help (works with -h and -? too)
"
exit 0
fi
# set defaults for prune and verify
echo ">>> Detect default behaviour:"
setAction "prune" "$lastprunefile"; doPrune=$doValue
setAction "verify" "$lastverifyfile"; doVerify=$doValue
echo
echo ">>> Check parameters"
if [ "$1" = "prune" ]; then
echo "Info: Forcing prune only by parameter."
ACTION=$1
doBackup=0
doPrune=1
doVerify=0
transferlog="${DIR_LOGS}/prune-$(date +%Y%m%d-%H%M%S).log"
fi
if [ "$1" = "verify" ]; then
echo "Info: Forcing verify only by parameter."
ACTION=$1
doBackup=0
doPrune=0
doVerify=1
transferlog="${DIR_LOGS}/verify-$(date +%Y%m%d-%H%M%S).log"
fi
exec 1> >( tee -a "$transferlog" ) 2>&1
echo "INFO: Start logging into $transferlog"
h1 "$( date ) TRANSFER LOCAL DATA TO STORAGE"
echo "TOOL : $STORAGE_BIN"
echo "ACTION : $ACTION"
echo "TARGET : ${STORAGE_BASEDIR}" | sed 's#:[^:]*@#:**********@#'
echo
if [ "$ACTION" = "backup" ]; then
echo "METHOD : $METHOD"
echo "REGISTER : ${STORAGE_REGISTER}"
echo "PRUNE : $doPrune"
echo "VERIFY : $doVerify"
echo
fi
. $(dirname $0)/plugins/transfer/$STORAGE_BIN.sh || exit 1
# --------------------------------------------------------------------------------
# ----- Check requirements
t_checkRequirements || exit 1
test -z "$STORAGE_REGISTER" || . $(dirname $0)/plugins/register/$STORAGE_REGISTER.sh || exit 1
echo Check locking of a running transfer
if [ -f "${lockfile}" ]; then
color error
echo A lock file for a running transfer was found
cat "${lockfile}"
color reset
echo
# 1659 - check process id inside the lock file
# detect pid from lockfile and search for this process
lockpid=$(cat "${lockfile}" | cut -f 2 -d "-" | cut -f 4 -d " " | grep "[0-9]")
if [ -z "$lockpid" ]; then
color error
echo "ERROR: pid was not fetched from lock file. Check the transfer processes manually, please."
color reset
exit 1
fi
echo "transfer processes with pid or ppid ${lockpid}:"
color cmd
ps -ef | grep "$lockpid" | grep "transfer"
rccheck=$?
color reset
if [ $rccheck -eq 0 ]; then
color error
echo "ERROR: The transfer with pid $lockpid seems to be still active. Aborting."
color reset
exit 1
fi
color ok
echo "OK, the transfer seems not to be active anymore. I IGNORE the lock and continue..."
color reset
fi
echo Creating a lock file ...
echo "transfer started $( date ) - process id $$" > "${lockfile}"
if [ $? -ne 0 ]; then
color error
echo "ABORT - unable to create transfer lock"
color reset
exit 2
fi
# --------------------------------------------------------------------------------
# ----- BACKUP VARS
# parameters for all
t_setVars || exit 1
export ARGS_DEFAULT
ARGS_DEFAULT="$( t_getParamDefault $1 $2 )"
if [ "$1" = "dumps" ]; then
sDirs2Backup="$(_j_getvar ${JOBFILE} dir-localdumps)"
else
sDirs2Backup="$(j_getDirs2Backup)"
fi
sParamExclude=
for sItem in $(_j_getvar "${DIRFILE}" exclude)
do
sParamExclude="$sParamExclude $( t_getParamExlude $sItem )"
done
sFileSshPrivkey=$(_j_getvar ${STORAGEFILE} "ssh-privatekey")
if [ -n "$sFileSshPrivkey" ]; then
ARGS_DEFAULT="${ARGS_DEFAULT} $( t_getParamSshKey $sFileSshPrivkey )"
fi
# task#3046 - add custom cache dir
sCacheDir=$(_j_getvar "${STORAGEFILE}" "${CFGPREFIX}cachedir")
if [ -n "$sCacheDir" ]; then
ARGS_DEFAULT="${ARGS_DEFAULT} $( t_getParamCacheDir $sCacheDir )"
fi
# --------------------------------------------------------------------------------
# ----- PRE transfer
h2 "$( date ) Wait for a free slot"
if [ -z "$STORAGE_REGISTER" ]; then
echo "SKIP"
else
iExit=1
until [ $iExit -eq 0 ]; do
registerBackupSlot "$FQDN"
iExit=$?
if [ $iExit -ne 0 ]; then
statusBackupSlot
iRnd=$(($RANDOM%30+30))
echo "I wait a bit ... random time ... $iRnd sec ..."
sleep $iRnd
fi
done
fi
_j_runHooks "300-before-transfer"
h2 "$( date ) PRE transfer tasks"
t_backupDoPreTasks
echo
# --------------------------------------------------------------------------------
# ----- START BACKUPS
(
if [ "$ACTION" = "backup" ]; then
for mydir in $sDirs2Backup
do
# remove ending slash ... otherwise duplicity will fail
mydir=$(echo "$mydir" | sed 's#\/$##g')
if [ -d "$mydir" ]; then
BACKUP_DIR=$mydir
h2 "$( date ) STORE $BACKUP_DIR"
# --- build parameters
sSafeName=$(j_getSafename "$BACKUP_DIR")
sTarget="$( t_backupDirGetTarget $BACKUP_DIR )"
ARGS_BACKUP="${sParamExclude} $( t_getParamBackup )"
# detect custom backup sets and add its includes and excludes
backupid=$(j_getSetnameOfPath "$BACKUP_DIR")
sSpaceReplace="___SPACE___"
if [ ! -z $backupid ]; then
for sItem in $(_j_getvar ${DIRFILE} "${backupid}\-\-include" | sed "s# #${sSpaceReplace}#g")
do
ARGS_BACKUP="${ARGS_BACKUP} $( t_getParamInlude $sItem)"
done
for sItem in $(_j_getvar ${DIRFILE} "${backupid}\-\-exclude" | sed "s# #${sSpaceReplace}#g")
do
ARGS_BACKUP="${ARGS_BACKUP} $( t_getParamExlude $sItem)"
done
fi
# --- pre task
h3 "$( date ) PRE backup task for ${BACKUP_DIR}"
t_backupDirDoPreTasks
# sCmdPre="$( t_backupDirDoPreTasks )"
# --- backup
h3 "$( date ) Backup ${BACKUP_DIR}"
if [ $doBackup -eq 0 ]; then
echo "SKIP backup"
else
_j_runHooks "310-before-folder-transfer"
sCmd="$( t_backupDirGetCmdBackup )"
echo "what: ${BACKUP_DIR}"
echo "target: ${sTarget}" | sed 's#:[^:]*@#:**********@#'
echo "command: $sCmd"
echo
color cmd
$sCmd
fetchrc
color reset
echo
t_rcCheckBackup $myrc "${BACKUP_DIR}"
test $myrc -ne 0 && j_notify "Dir ${BACKUP_DIR}" "Backup for ${BACKUP_DIR} failed with rc=$myrc. See log for details: $JOB_LOGFILE" 1
_j_runHooks "320-after-folder-transfer" "$myrc"
fi
echo
# --- post action
h3 "$( date ) POST backup task for ${BACKUP_DIR}"
t_backupDirDoPostTasks
echo
else
color warning
echo "DIR SKIP $mydir ... does not exist (no error)"
color reset
fi
echo
done
test $rc -eq 0 && touch "${lastbackupfile}"
else
echo "SKIP backup of dirs"
fi
echo $rc > ${rcfile}
exit $rc
)
# rc=${PIPESTATUS[0]}
rc=$(cat ${rcfile})
# --------------------------------------------------------------------------------
# --- transfer POST tasks
h2 "$( date ) POST transfer tasks"
# --- prune
if [ $doPrune -eq 0 ]; then
echo "SKIP prune"
else
h3 "$( date ) PRUNE repository data"
if t_backupDoPrune; then
touch "${lastprunefile}"
else
rc+=1
j_notify "Prune" "Pruning old data in the repostitory failed." 1
fi
ls -l "${lastprunefile}"
echo
fi
echo
# --- verify
if [ $doVerify -eq 0 ]; then
echo "SKIP verify"
else
h3 "$( date ) VERIFY repository data"
if t_backupDoVerify; then
touch "${lastverifyfile}"
else
rc+=1
j_notify "Verify" "Verify of repository data failed." 1
fi
ls -l "${lastverifyfile}"
echo
fi
echo
# --- unlock
t_backupDoPostTasks
rm -f "${lockfile}" "${rcfile}"
echo "Local lock file was removed."
h2 "$( date ) Unregister used slot"
if [ -z "$STORAGE_REGISTER" ]; then
echo "SKIP"
else
unregisterBackupSlot "$FQDN" $rc
fi
h2 "$( date ) Backup finished"
echo "STATUS $0 exit with final returncode rc=$rc"
echo
if [ $rc -eq 0 ]; then
color ok
echo Backup OK
else
color error
echo Backup FAILED :-/
fi
color reset
_j_runHooks "400-post-backup" "$rc"
echo
typeset -i TIMER_TRANSFER
TIMER_TRANSFER=$(date +%s)-$TIMER_TRANSFER_START
echo "$( date ) $ACTION DONE in $TIMER_TRANSFER sec"
ls -l "$transferlog"
exit $rc
# --------------------------------------------------------------------------------