gaboshlib/gaboshlib/g_backup.bashfunc

194 lines
6.3 KiB
Bash

#!/bin/bash
function g_backup {
#### Config defaults
# dir(s) to backup (: separated)
local BACKUPDIRS=""
# Excludefile für rsync
local BACKUPEXCLUDES=""
# Backup-Server (Backup-Destination)
local BACKUPSRV=""
# SSH Port
local BACKUPSRVPORT="22"
# SSH User
local BACKUPSRVUSER=""
# Destination path on Backup-Server
local BACKUPSRVPATH=""
# run optional command on server and start backup only on success
local BACKUPSRVTESTCMD=""
# Copy only without hostname/backup-date Structure
local NODIRSTRUCT=""
g_tries=5
g_tries_delay=120
# Config files for custom vars
[ -r /usr/local/etc/g_backup.conf ] && . /usr/local/etc/g_backup.conf
[ -r ~/.config/g_backup.conf ] && . ~/.config/g_backup.conf
# Use CMD-line args
[ -n "$1" ] && BACKUPDIRS=$1
[ -n "$2" ] && BACKUPSRVPATH=$2
[ -n "$3" ] && BACKUPEXCLUDES=$3
[ -n "$4" ] && BACKUPSRV=$4
[ -n "$5" ] && BACKUPSRVPORT=$5
[ -n "$6" ] && BACKUPSRVUSER=$6
[ -n "$7" ] && BACKUPSRVTESTCMD=$7
[ -n "$8" ] && NODIRSTRUCT=$8
local usage="
$0 BACKUPDIR1[:BACKUPDIR2:BACKUPDIR3] BACKUPPATH [BACKUPEXCLUDES BACKUPSRV BACKUPSRVPORT BACKUPSRVUSER BACKUPSRVTESTCMD NODIRSTRUCT]"
[ -z "$BACKUPDIRS" ] && g_echo_error "No Directory to Backup (BACKUPDIR) given $usage" && return 1
[ -z "$BACKUPSRVPATH" ] && g_echo_error "No path to put the Backup (BACKUPPATH) given $usage" && return 1
if [ "$BACKUPEXCLUDES" == "default" ]
then
BACKUPEXCLUDES="$g_tmp/default-excludes"
echo '.local/share/gvfs-metadata
.local/share/Trash
.unison*
unison.log
.unison.sh.log
.local/share/Steam/
dwhelper
*cache*
*tmp*
*temp*
*.log
*.log.*
*.del
*.bak
*.old
*backup/
*backups/
*.backup
*.backups
*Backup/
*Backups/
*.Backup
*.Backups
.var/app/org.signal.Signal
vpn
.config/Nextcloud/
nextcloud-test.*/
.VirtualBox/
DMS/
tmp
.zoom/
.steam/
pagefile.sys
.config/Signal/
.var/app/com.microsoft.Teams
.var/app/im.riot.Riot
GTC/
Eingang/
_neu/
_neu_pre/
.xsession-errors*
part.dd
.local/share/Steam
/Nextcloud/
var-lib-docker
/proc/
/sys/
/dev/
/run/
/mnt/' >$g_tmp/default-excludes
fi
if [ -n "$BACKUPSRV" ]
then
[ -z "$BACKUPSRVPORT" ] && g_echo_error "No SSH-Port given $usage" && return 1
local sshopts="g_retrycmd ssh -n -p $BACKUPSRVPORT -o BatchMode=yes -o StrictHostKeyChecking=accept-new $BACKUPSRVUSER@$BACKUPSRV"
local sshoptsnoretry="ssh -n -p $BACKUPSRVPORT -o BatchMode=yes -o StrictHostKeyChecking=accept-new $BACKUPSRVUSER@$BACKUPSRV"
fi
rsyncopts="-aXAH --no-inc-recursive --timeout=180 --force -yy --one-file-system --open-noatime --delete-after --ignore-missing-args --partial"
[ -n "$BACKUPEXCLUDES" ] && rsyncopts="$rsyncopts --delete-excluded --exclude-from=$BACKUPEXCLUDES"
[ -n "$BACKUPSRV" ] && rsyncopts="$rsyncopts -e 'ssh -p $BACKUPSRVPORT'"
[ "$BACKUPSRVUSER" == "root" ] && syncopts="$rsyncopts --numeric-ids"
local ORIGIFS="$IFS"
IFS=':'
# Prepare Dest-Dir on Backup-Server
local CMDS="mkdir -p ${BACKUPSRVPATH}/$(hostname):chmod 700 ${BACKUPSRVPATH}/$(hostname)"
[ -n "$NODIRSTRUCT" ] && CMDS="mkdir -p ${BACKUPSRVPATH}"
[ -n "$BACKUPSRVTESTCMD" ] && CMDS="$BACKUPSRVTESTCMD:$CMDS"
for CMD in $CMDS
do
IFS="$ORIGIFS"
g_runcmd $sshopts $CMD || return $?
done
IFS=':'
for BACKUPDIR in $BACKUPDIRS
do
IFS="$ORIGIFS"
rsyncoptsrun="$rsyncopts"
# prepare paths
if [ -z "$NODIRSTRUCT" ]
then
# build structure
BACKUPSRVPATH_WITH_HOST="$BACKUPSRVPATH/$(hostname)"
BACKUPSRVPATH_WITH_DATE="$BACKUPSRVPATH_WITH_HOST/backup-$(date +%Y-%m-%d)/$BACKUPDIR"
BACKUPSRVPATH_WITH_DATE_INCOMPLETE="$BACKUPSRVPATH_WITH_DATE-incomplete"
# linkdest/hardlink if present
BACKUPSRVPATH_LINK_DEST="$($sshoptsnoretry ls -d1 "$BACKUPSRVPATH_WITH_HOST/backup-*/$BACKUPDIR" 2>/dev/null | egrep -v "/backup.+-monthly/" | sort | tail -n1 | perl -pe 's/$/\//' )"
BACKUPSRVPATH_LINK_DEST_INCOMPLETE="$($sshoptsnoretry ls -d1 "$BACKUPSRVPATH_WITH_HOST/backup-*/$BACKUPDIR-incomplete" 2>/dev/null | grep -v $BACKUPSRVPATH_WITH_HOST/backup-$(date +%Y-%m-%d) | tail -n 19 | sort | perl -pe 's/^/--link-dest=/; s/\n/\/ /g')"
[ -n "$BACKUPSRVPATH_LINK_DEST_INCOMPLETE" ] && rsyncoptsrun="$rsyncoptsrun $BACKUPSRVPATH_LINK_DEST_INCOMPLETE"
#[ -n "$BACKUPSRVPATH_LINK_DEST" ] && rsyncoptsrun="$rsyncoptsrun --link-dest=$BACKUPSRVPATH_LINK_DEST"
g_runcmd $sshopts mkdir -p "$BACKUPSRVPATH_WITH_DATE_INCOMPLETE" || continue
# Create hardlinks before rsync because of some strange behaviour with "rsync --link-dest="
[ -n "$BACKUPSRVPATH_LINK_DEST" ] && g_runcmd $sshopts cp -rlfTP "$BACKUPSRVPATH_LINK_DEST"/ "$BACKUPSRVPATH_WITH_DATE_INCOMPLETE"/ || continue
else
BACKUPSRVPATH_WITH_DATE_INCOMPLETE="$BACKUPSRVPATH"
fi
# Backup
[ -n "$BACKUPSRV" ] && sshrsync="$BACKUPSRVUSER@$BACKUPSRV:"
g_runcmd g_retrycmd rsync $rsyncoptsrun "$BACKUPDIR"/ "${sshrsync}$BACKUPSRVPATH_WITH_DATE_INCOMPLETE"/ || continue
# done if no direstructure
[ -n "$NODIRSTRUCT" ] && continue
# if backups from this day already exists move it to $BACKUPSRVPATH_WITH_DATE-$$-old
if $sshopts test -e "$BACKUPSRVPATH_WITH_DATE"
then
$sshopts mv "$BACKUPSRVPATH_WITH_DATE" "$BACKUPSRVPATH_WITH_DATE-$$-old"
fi
# move from -incomplete to default dir
g_runcmd $sshopts mv "$BACKUPSRVPATH_WITH_DATE_INCOMPLETE" "$BACKUPSRVPATH_WITH_DATE" || continue
local yearmonth="$(date +%Y-%m-)"
# Create monthly dir if not exists
BACKUPSRVPATH_MONTHLY="$BACKUPSRVPATH_WITH_HOST/backup-${yearmonth}monthly/$BACKUPDIR"
if ! $sshopts test -e "$BACKUPSRVPATH_MONTHLY"
then
g_echo "Creating monthly-Backup $BACKUPSRVPATH_MONTHLY"
g_runcmd $sshopts mkdir -p "$BACKUPSRVPATH_MONTHLY" || continue
g_runcmd $sshopts mv -T "$BACKUPSRVPATH_WITH_DATE" "$BACKUPSRVPATH_MONTHLY" || continue
g_runcmd $sshopts ln -s "$BACKUPSRVPATH_MONTHLY" "$BACKUPSRVPATH_WITH_DATE" || continue
fi
# Cleanup old backups
local yearmonthbefore="$(date +%Y-%m- --date='-1 month')"
$sshopts find $BACKUPSRVPATH_WITH_HOST -mindepth 1 -maxdepth 1 -type d -name "backup-*" | egrep -v "backup-$yearmonth|backup-$yearmonthbefore|backup-20[0-9][0-9]-[0-9][0-9]-monthly" | while read g_toremove
do
g_echo_warn "Removing old Backup $g_toremove"
g_runcmd $sshopts chmod -R +w "$g_toremove"
g_runcmd $sshopts rm -r "$g_toremove"
done
done
return 0
}