
It's going to be a long post

1. What and why.
I'm doing IT for a small bookkeeping/financial company. They are running (among other things) MS SQL Server, on which 90% of their software keeps their customer's databases. Needless to say, if they lose this data, it's going to be sad. Most of it is "restorable" from hardcopies, but the amount of work...
So of course I'm doing backup of this. Actually, 2 backups:
a) entire server, sans last partition, gets backup to a QNAP installed locally in the office. Standard MS Backup to an iSCSI share (since normal network share does not allow for versioning). Saved my bottom twice already.
b) all of the databases are backup using internal MS SQL Backup function to the last partition, for versioning and quick restore.
This works well enough, but there are still 2 failure modes not covered with this:
a) something happens to the office - burglary, fire, flood, whatever
b) the staff is not reliable when it comes to IT security, so it's possible to get infected by Cryptolocker or something similar
Solution: off-site backup. Additional requirements - the backup server has to act as "Active", because:
a) the office network has some public static IPs, while off-site would be running off a DSL/cable with random IPs
b) if it's the server that connects, then Cryptolocker can't reach to it and encrypt anything on the backup server.
So, how to do this?
2. Prepare "source" server.
Unfortunately, the work was done quite a while ago, and I can't remember what guides I used. In general:
a) install Cygwin on the server, with openssh server
b) generate keys for a user you want to run backup with (I think it has to be domain-joined user, but I won't bet anything on it)
c) test if you can login with keys
d) modify SSH config to only allow single user, and only allow key-based auth; then restart the service
Code: Select all
PasswordAuthentication no
AllowUsers <your_user>
f) check if you can login from outside with keys, but not with password
g) (optional) install script that removes oldest SQL backups, but leaves ones made on Sunday (see below)
Code: Select all
#/bin/sh
# from https://stackoverflow.com/questions/20034415/bash-delete-all-files-older-than-1-month-but-leave-files-from-mondays
export q="'"
# hack from https://unix.stackexchange.com/questions/187651/how-to-echo-single-quote-when-using-single-quote-to-wrap-special-characters-in
find "/cygdrive/<path_to_SQL_backup_dir>" -type f -mtime +90 -exec sh -c 'test $(LC_TIME=C date +%a -r "$1") = Sun || echo rm "${q}$1${q}"' -- {} \; | sh
3. Prepare backup server.
a) install script that runs the backup job
Code: Select all
#!/usr/bin/bash
# Inspired by: https://netfuture.ch/2013/08/simple-versioned-timemachine-like-backup-using-rsync/
# Usage: timemachine.sh <label>
BACKUP_DIR="/home/company/backup"
LOG_FILE="$BACKUP_DIR/timemachine_job.log"
LOG_FILE_RM="$BACKUP_DIR/rm.log"
MINIMAL_FREE_SPACE=150000000
if [ "$#" -ne 1 ]; then
/usr/bin/echo "$0: Expected 1 argument, received $#: $@"
exit 1
fi
/usr/bin/echo `/usr/bin/date`" | Starting job." >> $LOG_FILE
/usr/bin/echo `/usr/bin/date`" | Trying to remove old backups, except for those made on Sunday." >> $LOG_FILE
# remove old backups, except for those made on Sunday - runs on remote machine
/usr/bin/ssh -p 22 user@server ./remove_old_dumps.sh
/usr/bin/echo `/usr/bin/date`" | Old SQL dumps removed from source machine" >> $LOG_FILE
# remove oldest local backups, until there's at least 150 GB of free space for new backup
# from https://unix.stackexchange.com/questions/28939/how-to-delete-the-oldest-directory-in-a-given-directory
DISK_FREE=`/usr/bin/df $BACKUP_DIR | /usr/bin/tail -1 | /usr/bin/awk '{print $4}'`
# find latest backup, we never want to remove this one
IFS= read -r -d $'\0' line < <(/usr/bin/find $BACKUP_DIR -maxdepth 1 -type d -name 2* -printf '%T@ %p\0' | /usr/bin/sort -r -z -n)
DIR_TO_KEEP="${line#* }"
while [[ $DISK_FREE -lt $MINIMAL_FREE_SPACE ]];
do
/usr/bin/echo `/usr/bin/date`" | Freeing up local diskspace, current free space: $DISK_FREE bytes." >> $LOG_FILE
# from https://unix.stackexchange.com/questions/28939/how-to-delete-the-oldest-directory-in-a-given-directory
# limit search only to directories starting with "2"
IFS= read -r -d $'\0' line < <(/usr/bin/find $BACKUP_DIR -maxdepth 1 -type d -name 2* -printf '%T@ %p\0' 2>/dev/null | /usr/bin/sort -z -n)
DIR_TO_REMOVE="${line#* }"
if [[ "$DIR_TO_REMOVE" == "$DIR_TO_KEEP" ]];
then
/usr/bin/echo `/usr/bin/date`" | Not removing latest backup directory $DIR_TO_REMOVE, will attempt to make backup with potentially insufficient disk space." >> $LOG_FILE
break
fi
/usr/bin/echo `/usr/bin/date`" | Removing directory $DIR_TO_REMOVE." >> $LOG_FILE
# call me paranoid, but I'd *really* like to avoid removing anything outside of $BACKUP_DIR
if [[ "$DIR_TO_REMOVE" =~ "$BACKUP_DIR" ]];
then
/usr/bin/rm -rfv $DIR_TO_REMOVE 2>&1 >$LOG_FILE_RM
if [[ $? != "0" ]]; then
/usr/bin/echo `/usr/bin/date`" | Removing directory $DIR_TO_REMOVE encountered issues, check $LOG_FILE_RM" >> $LOG_FILE
/usr/bin/echo `/usr/bin/date`" | Bailing out." >> $LOG_FILE
exit 2
fi
else
/usr/bin/echo `/usr/bin/date`" | Directory to remove - $DIR_TO_REMOVE - does not include backup dir, something is wrong." >> $LOG_FILE
/usr/bin/echo `/usr/bin/date`" | Bailing out." >> $LOG_FILE
exit 3
fi
DISK_FREE=`/usr/bin/df $BACKUP_DIR | /usr/bin/tail -1 | /usr/bin/awk '{print $4}'`
done
/usr/bin/echo `/usr/bin/date`" | Free space prior to backup start: $DISK_FREE bytes." >> $LOG_FILE
if [ -d "$BACKUP_DIR/__prev/" ]; then
/usr/bin/rsync -z -a --no-perms --no-owner --no-group --delete --link-dest="$BACKUP_DIR/__prev" --exclude-from=/home/company/rsync-exclude.txt -r -t -e "ssh -p 22" user@server:/cygdrive/f/ "$BACKUP_DIR/$1"
else
/usr/bin/rsync -z -a --no-perms --no-owner --no-group --exclude-from=/home/company/rsync-exclude.txt -r -t -e "/usr/bin/ssh -p 22" user@server:/cygdrive/f/ "$BACKUP_DIR/$1"
fi
/usr/bin/echo `/usr/bin/date`" | Rsync complete." >> $LOG_FILE
/usr/bin/rm -f "$BACKUP_DIR/__prev"
/usr/bin/ln -s "$BACKUP_DIR/$1" "$BACKUP_DIR/__prev"
/usr/bin/echo `/usr/bin/date`" | Directories re-linked, I'm done." >> $LOG_FILE
b) (optional) install rsync-exclude.txt file
Code: Select all
$RECYCLE.BIN/
System Volume Information/
Code: Select all
0 4 * * * /home/company/timemachine.sh `/usr/bin/date +\%Y-\%m-\%d` 2>&1 >/home/company/backup/timemachine.log

4. Lean back and relax, observing backup done every day

Hope it helps someone
