Mumbling about computers

Backups, Backups, Backups

Posted on under [ ]

I reached a point where I could recreate all (or most) of my labs with ansible. What I had not yet configured is an extensive backup system. I'll explain here what I did.

Backing everything up to a central location

For this I use a combination of rsnapshot on the server where everything ends up and bash scripts.

Some of the entries as an example:

backup_script   /storage/scripts/backup/db.sh       db/
backup_script   /storage/scripts/backup/pfsense.sh  pfsense/
backup_script   /storage/scripts/backup/gogs.sh     gogs/

# LOCALHOST
backup  /home/david/git         localhost/home/david/
backup  /etc/                   localhost/
backup  /var/lib/lxc/           localhost/
backup  /storage/scripts/       localhost/

#NYC1
backup  root@nyc1:/etc/                     eba/
backup  root@nyc1:/backup/mysql/latest/*    eba/mysql/      +rsync_long_args=--no-relative
backup  root@nyc1:/backup/mongo/latest/*    eba/mongo/      +rsync_long_args=--no-relative

The db.sh script:

ssh root@db "mysqldump --all-databases --events --routines --triggers --single-transaction | gzip --rsyncable -3" > backup.sql.gz

The gogs.sh script:

ssh root@gogs "cd /home/git/gogs; tar c data | gzip --rsyncable" > gogsdata.tar.gz
ssh root@gogs "cd /home/git; tar c gogs-repositories | gzip --rsyncable" > gogs-repositories.tar.gz

This ends up filling and rotating 7 daily backups, which become 3 weekly backups and 3 monthly backups, and with the magic of storing only diffs this only takes about ~2x the space of the original backup.

Cloning the central backup to an external device

While the above backups are on a RAID1, nothing saves me from deleting everything or the Disks/server exploding.

So I need to have an offline backup. I did this by automating completely the process:

Automounting the device:

You need to get the UUID of your device with something like blkid.

/etc/udev/rules.d/70-usb-hdd.rules

ACTION=="add", KERNEL=="sd?1", ENV{ID_FS_UUID}=="14421387-1d03-4712-9f70-80a917f64b32", RUN+="/bin/mount /mnt/usbbackup", SYMLINK+="externalhdd"

Triggering backup on mount

I wrote a systemd service ( /etc/systemd/system/rsync-to-external.service) that starts backing up to the external device upon 'creation' of /dev/externalhdd

[Unit]
After=dev-externalhdd.device

[Service]
ExecStart=/storage/scripts/rsync-to-external.sh

[Install]
WantedBy=dev-externalhdd.device

Backing files up

I wrote this script to rsync the backups to an external disk and email me everything that's going on. I'm considering using rsnapshot to keep the external backup versioned.

#!/bin/bash

set -e

LOCKFILE=/tmp/usbbackup.lock
ERRFILE=/tmp/usbbackup.err
EMAIL="red@acted.com"

function cleanup {
        echo "Removing /tmp/backuplock"
        if [ -f $LOCKFILE ]; then
                rm $LOCKFILE
        fi
}

trap cleanup EXIT

function mailerr {
        echo $1 | mailx -s '[ERROR] rsync usb backup' $EMAIL
}

if [ $(id -u) -ne 0 ]; then
        echo 'Run as root'
        exit 1
fi

if [ -f $ERRFILE ]; then
        mailerr "Previous Error backing up!"
        exit 1
fi

mounted=$(mount | grep -l usbbackup | wc -l )
if [ $mounted -eq 0 ]; then
        mailerr "Device not mounted!!"
        exit 1
fi

touch $LOCKFILE
echo "Starting backup to external device" | mailx -s '[INFO] rsync usb backup' $EMAIL
rsync -av /backup/rsnapshot/daily.0/ /mnt/usbbackup/ >/tmp/usbbackup 2>&1 

if [ $? -ne 0 ]; then
        mailerr "RSYNC Error backing up!"
        exit 1
fi

umount /mnt/usbbackup/
echo "unmounting"
echo "Finished backup to external device" | mailx -s '[OK] rsync usb backup' $EMAIL

Actually backing files up

This would never really happening if there wasn't something pestering me about it. So I wrote a script and stuck it in a cronjob

0    */12   *   *   *   /storage/scripts/check-last-backup.sh
#!/bin/bash
set -e

if [ ! -f /tmp/usbbackup ]; then
        echo "touch /tmp/usbbackup or run a backup" | mailx -s '[Warning] No Last backup file!!' $MAIL #In case of server reboot
        exit 1
fi

now=$(date +%s)
last=$(stat -c %Y /tmp/usbbackup)
days=$(echo "($now-$last)/86400" | bc)

if [ $days -ge 6 ]; then
        echo "Go plug in the disk now" | mailx -s '[Warning] Last backup is over a week old' $MAIL
fi

Removing the disk so it's offline backup

Same thing as the previous point, the first time I ran the backups I left the drive plugged in for 2 days. This means it's not an offline backup anymore, so I wrote another script to check and pester me every 10 minutes to go and disconnect it.

*/10 *      *   *   *   /storage/scripts/check-external-backup.sh
#!/bin/bash
set -e
backing_up=0
mounted=0
connected=0

if [ $(mount | grep -c usbbackup) -gt 0 ]; then
        mounted=1
fi


usb=$(lsusb | grep -c "1058:0740")
if [ -L /dev/externalhdd ] || [ $usb -ne 0 ]; then
        connected=1
fi

if [ -f /tmp/usbbackup.lock ]; then
        backing_up=1
fi

if [ $backing_up -eq 1 ]; then
        exit 0
fi

if [ $mounted -eq 1 ]; then
        echo "The backup disk is mounted and the backup is done" | mailx -s '[Warning] USB Disk is still mounted' $MAIL
        exit 1
fi

if [ $connected -eq 1 ]; then
        echo "The backup disk is connected (unmounted) and the backup is done" | mailx -s '[Warning] USB Disk is still connected' $MAIL
        exit 1
fi

Cloning the central backup to an offsite server

Having a standard and an offline backup is not enough. I need an offsite backup in case something really bad happens.

And given the fact that rsnapshot doesn't accept a target over the network I wrote a script that does something like it (without versioning. I'm thinking about it)

#!/bin/bash
set -euo pipefail
ROOTPATH="/backup/rsnapshot/daily.0"
function backup() {
        backup_path="$1"
        relative=$(echo $1 | sed "s:^$ROOTPATH/::")
        exclude=""
        while [ $# -gt 1 ]; do
                exclude="$exclude --exclude=$2"
                shift
        done
        /usr/bin/rsync --bwlimit=3m -avz $exclude --delete --numeric-ids --no-relative --delete-excluded "$backup_path" offsite1:backup/"$relative"/
}

backup  $ROOTPATH/server1/mysql/
backup  $ROOTPATH/localhost/            home/   network/ usr/ web/ etc/alternatives etc/ssl
backup  $ROOTPATH/nyc1/                 home/   var/vmail etc/alternatives etc/ssl
backup  $ROOTPATH/db
backup  $ROOTPATH/gogs
backup  $ROOTPATH/mumble
backup  $ROOTPATH/server2                etc/alternatives etc/ssl
backup  $ROOTPATH/pfsense

This does not do a 1:1 mirror because:

  • I have a measly 4mbit upload and the initial backup would've taken ages
  • I'm using rsync.net on which I bought a year with 25GB, and the entire backup doesn't fit.