#!/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* .var/app/org.signal.Signal vpn .config/Nextcloud/ nextcloud-test.*/ .VirtualBox/ DMS/ tmp .zoom/ .steam/ pagefile.sys .config/Signal/ .var/app/com.microsoft.Teams GTC/ Eingang/ _neu _nru_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 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 }