I decided to streamline the process described in the two articles and to improve the included snapshot creation code. The result can be found below and so far it seems to be working very well. I plan to post the script on the wiki, but I an a complete Linux and shell scripting newbie (this is my very first shell script), so I wanted to get some feedback on whether I'm breaking any rules, as well as other suggestions on how to improve the script.
The script backs up a set of folders to a specific local directory (I recommend backing on on an attached USB, or SATA drive) and makes snap shots of older backups, so that older versions of a file can be retrieved in addition to the latest backup up version. The code is an improvement on the techniques described in the wiki article, in the following ways:
- Daily, weekly and monthly backup snapshots are created only if necessary (the script looks for the last time a particular type of backup was made. The original code would create a snapshot whenever the script was called.
- Daily, weekly and monthly backup folders are deleted, not based on how many there are, but based on how old they are.
- Only a single crontab entry is required for snapshot creation and the backup itself.
- Backup and rotation results are logged in a file.
- Backup and rotation results are emailed to the administrator (optional).
Note that the script can also be used for making snapshots of the backups created via the Synology remote backup feature (just leave the SRC parameter empty and point to the location where these backups are made). Similarly, it should be able to "pull" data from a remote rsync server, by including the server's host name in the SRC parameter. I have not tried either of these configurations.
- Code: Select all
#!/opt/bin/bash
# Usage: rotate_backup.sh <rootpath> <dir> <src>
#
# <rootpath>: The root location where backups are stored
# <dir>: The name of the backup directory
# <src>: The source directory being backup up. May contain
# multiple directories, separated with spaces (enclose
# the whole thing in quotes). If empty, then no backup
# is performed.
#
# Example:
# ./rotate_backup.sh "/volumeUSB1/usbshare/" "RsyncLocalBackup" "/volume1/music /volume1/photo /volume1/video"
#
# Requirements: This script requires the following:
# 1. Bash package
# 2. Coreutils package, because the version of cp that comes with
# the current Synology OS does not support hardlinks
# 3. Findutils package, because the version of find that comes with
# the current Synology OS does not support all the necessary
# switches.
# 4. Optional: Nail package for emailing logs to the administrator.
# The number of days to keep in each type of backup.
# Set the value to 0 to skip a particular type
DAILYHISTORY=6 # 6 daily backups
WEEKLYHISTORY=27 # 3 weekly backups
MONTHLYHISTORY=185 # 6 monthly backups
# If ADMIN_EMAIL is empty, then the log is not emailed to the administrator
ADMIN_EMAIL=tiritas@foxweb.com
#-----------------------------------------------------
# This function makes a hardlink copy of the backup forlder. The new folder
# name starts with "@", and contains the backup type and timestamp.
# The function also deletes old backup folders (based on the BACKUP_TYPE_AGE
# parameter.
function RotateFolder()
{
BACKUP_TYPE=$1
BACKUP_TYPE_INTERVAL=$2
BACKUP_TYPE_AGE=$3
if [ $BACKUP_TYPE_AGE -gt 0 ]; then
HASBACKUP=`/opt/bin/findutils-find $ROOTPATH@$DIRNAME.$BACKUP_TYPE.* -maxdepth 0 -daystart -mtime -$(($BACKUP_TYPE_INTERVAL+1)) 2>/dev/null`
if [ -z $HASBACKUP ]; then
echo "Creating $BACKUP_TYPE copy of backup"
# Create a new directory appended with date
NEWDIR=@$DIRNAME.$BACKUP_TYPE.`date +\%Y\%m\%d`
rm -rf $ROOTPATH$NEWDIR
/opt/bin/coreutils-cp -al $ROOTPATH$DIRNAME $ROOTPATH$NEWDIR
fi
fi
# Delete directories that are older then the specified age for this type
/opt/bin/findutils-find $ROOTPATH@$DIRNAME.$BACKUP_TYPE.* -maxdepth 0 -daystart -mtime +$BACKUP_TYPE_AGE -exec rm -rf {} \; 2>/dev/null
}
#-----------------------------------------------------
# This function does all the work
function MainFunction()
{
ROOTPATH=$1
DIRNAME=$2
SRC=$3
echo "
------------------------------------------
`date`
" >> $LOG_FILE
# Make sure that we are using a newer version of cp. Older versions do not support the -al switch to create hardlinks
if [ ! -f "/opt/bin/coreutils-cp" ]; then
echo "The cp command from the coreutils package was not found (default cp will not work). Install coreutils package" >&2
return 1
fi
if [ ! -f "/opt/bin/findutils-find" ]; then
echo "The find command from the findutils package was not found (default find will not work). Install findutils package" >&2
return 1
fi
if [ -z "$ROOTPATH" ]; then
echo "Must specify ROOTPATH" >&2
return 1
fi
if [ -z "$DIRNAME" ]; then
echo "Must specify DIRNAME" >&2
return 1
fi
if [ ! -d "$ROOTPATH" ]; then
echo "ROOTPATH directory ('$ROOTPATH') does not exist" >&2
return 1
fi
# Make historical hardlink copies of our backup directory
if [ -d "$ROOTPATH$DIRNAME" ]; then
echo "Checking whether rotation is necessary for $ROOTPATH$DIRNAME"
echo
# We already have at least one backup
RotateFolder daily 1 $DAILYHISTORY
RotateFolder weekly 7 $WEEKLYHISTORY
RotateFolder monthly 30 $MONTHLYHISTORY
else
echo "$ROOTPATH$DIRNAME does not exist. No rotation is necessary"
echo
fi
if [ -z "$SRC" ]; then
echo "No backup source was specified. Skipping backup."
else
# Perform the actual backup
echo "Backing up \"$SRC\" to $ROOTPATH$DIRNAME"
echo
rsync -av --delete --delete-excluded --exclude=#recycle/ --exclude=@eaDir/ $SRC $ROOTPATH$DIRNAME/
fi
# Change the backup directory modification date, by adding a file
# to the root of the backup location, so that purging works
if [ -d "$ROOTPATH$DIRNAME" ]; then
rm -f $ROOTPATH$DIRNAME/@BackupTime
date > $ROOTPATH$DIRNAME/@BackupTime
fi
}
#-----------------------------------------------------
# Script starts here
# The reason for wrapping MainFunction, which does the actual work,
# is so that we can email the log to the administrator.
# The LOG_FILE has the same name as this script, but with a .log extension
# and is located in /var/log/. It contains a cumulative log.
LOG_FILE=/var/log/`expr match "$0" '.*\/\(.*\)\..*'`.log
# The EMAIL_LOG_FILE only contains the output of the current run only and
# is used to email the results to the administrator.
EMAIL_LOG_FILE=/tmp/`expr match "$0" '.*\/\(.*\)\..*'`.log
MainFunction "$1" "$2" "$3" 2>&1 | tee $EMAIL_LOG_FILE | tee -a $LOG_FILE
# Mail the log to the administrator
if [ ! -z "$ADMIN_EMAIL" ]; then
if [ ! -f "/opt/bin/nail" ]; then
echo "Nail package not found. The log cannot be mailed to the administrator." >&2
else
/opt/bin/nail -s "$HOSTNAME: Local Backup Results" "$ADMIN_EMAIL" < $EMAIL_LOG_FILE
fi
fi
rm -f $EMAIL_LOG_FILE
As I mentioned above, I'm a complete beginner, when it comes to writing shell scripts. If you spot something, please let me know.
Important: You need to disable native Local Backup on your DiskStation, or at least make sure that $ROOTPATH$DIRNAME does not point to the same location as the destination of Local Backup. If not, then the two methods of backing up will step on each other and you will probably end up with broken snapshot directories.
Thanks,
Pando



