Rolling Backup with rsync

Author: akeil
Date: 2014-03-21
Version: 2
updated: 2014-03-24

This post describes a backup solution for a constellation where the source machine - the one that holds the data to be backed up - is "always running". The target machine - where backups are stored - is not always available.

More precisely we are backing up data from a RaspberryPi to a desktop computer.

The backup is controlled from the target machine, where it is scheduled to run periodically with anacron.

Most of this is taken from this website [1].

Rolling Backup

A rolling backup is used, where we create one backup per day and keep the n most recent backups.

Daily backups are kept for seven days. Every week, the oldest of the daily backups is kept as a weekly backup. Weekly backups are kept for 3 Weeks.

The resulting directory structure is this:

/backup-root
    /backup.daily.0
    /backup.daily.1
    /backup.daily.2
    /backup.daily.3
    /backup.daily.4
    /backup.daily.5
    /backup.daily.6
    /backup.weekly.0
    /backup.weekly.1
    /backup.weekly.2

RSync

rsync [2] is used to transfer data from the source to the target machine and to manage the backed up files. rsync also allows to create incremental backups by hard-linking files that were unchanged from older backups.

rsync can be used to sync data over the network but it can also work to sync directories athe same machine.

Install rsync and start the rsyncd service (Arch Linux):

# pacman -S rsync
# systemctl enable rsyncd
# systemctl start rsyncd

If a the iptables firewall is is used, allow the backup-machine to connect to rsyncd listening on port 873:

-A TCP -p tcp -m tcp --src 192.168.2.0/24 --dport 873 -j ACCEPT

Assumes that a chain named TCP is set up that handles TCP traffic. Also assumes that 192.168.2.* is your local network and that the basic policy is to DROP everything that is not explicitly allowed.

rsync will make only make directories available if they are configured in a module in /etc/rsyncd.conf. From the rsync documentation:

... each module exports a directory tree as a symbolic name. Modules are exported by specifying a module name in square brackets [module] followed by the parameters for that module. The module name cannot contain a slash or a closing square bracket. If the name contains whitespace, each internal sequence of whitespace will be changed into a single space, while leading or trailing whitespace will be discarded.
[home.username]
    path = /home/username
    comment = My Home
    read only = yes
    uid = username
    gid = username

You address it like so:

$ rsync rsync://HOST/MODULE

Backup-Script

Daily Backups

#!/bin/sh

# Backup script for radicale calendar data.
# Data is stored on a remote machine which is assumed to be always running.
# The source machine must run rsyncd.
# The backup is initialized from a client that is not always on.
# Intended to be run as a daily job via anacron.
# rsync is required on the target machine.

set -o errexit
unset PATH  # avoid accidental use of $PATH

# Source machine, hostname or IP
SRC_HOST=my-host
# rsync-module on host
SRC_MODULE=my-module
# target
BACKUP_ROOT=/mnt/storage/backup

# End Configuration ----------------------------------------------------------


# Define and create backup directories.
b0="$BACKUP_ROOT/backup.daily.0"
b1="$BACKUP_ROOT/backup.daily.1"
b2="$BACKUP_ROOT/backup.daily.2"
b3="$BACKUP_ROOT/backup.daily.3"
b4="$BACKUP_ROOT/backup.daily.4"
b5="$BACKUP_ROOT/backup.daily.5"
b6="$BACKUP_ROOT/backup.daily.6"

/bin/mkdir -p "$b0"
/bin/mkdir -p "$b1"
/bin/mkdir -p "$b2"
/bin/mkdir -p "$b3"
/bin/mkdir -p "$b4"
/bin/mkdir -p "$b5"
/bin/mkdir -p "$b6"

# discard oldest backup
if [ -d "$b6" ]; then
    /bin/rm -rf "$b6"
fi

# move remaining backups up by one level
if [ -d "$b5" ]; then
    /bin/mv "$b5" "$b6"
fi

if [ -d "$b4" ]; then
    /bin/mv "$b4" "$b5"
fi

if [ -d "$b3" ]; then
    /bin/mv "$b3" "$b4"
fi

if [ -d "$b2" ]; then
    /bin/mv "$b2" "$b3"
fi

if [ -d "$b1" ]; then
    /bin/mv "$b1" "$b2"
fi

if [ -d "$b0" ]; then
    /bin/mv "$b0" "$b1"
fi

# fetch a new backup
src="rsync://$SRC_HOST/$SRC_MODULE/"
/usr/bin/rsync --archive --delete --link-dest "../backup.1" "$src" "$b0"

# have mtime reflect backup date
/usr/bin/touch "$b0"

Rotate Backups

The script for rotating weekly backups:

#!/bin/bash

# Rotate Weekly Backups
# Drop the oldest weekly backup,
# copy the oldest daily backup to become the youngest weekly backup.
# Intended to be run weekly via anacron.

set -o errexit
unset PATH

BACKUP_ROOT=/mnt/storage/backup
OLDEST_DAILY_BACKUP="$BACKUP_ROOT/backup.daily.6"


# ----------------------------------------------------------------------------

b0="$BACKUP_ROOT/backup.weekly.0"
b1="$BACKUP_ROOT/backup.weekly.1"
b2="$BACKUP_ROOT/backup.weekly.2"

/bin/mkdir --parents "$b0"
/bin/mkdir --parents "$b1"
/bin/mkdir --parents "$b2"

# delete oldest backup
if [ -d "$b2" ]; then
    /bin/rm -rf "b2"
fi

# move previous backups up one level
if [ -d "$b1" ]; then
    /bin/mv "$b1" "$b2"
fi

if [ -d "$b0" ]; then
    /bin/mv "$b0" "$b1"
fi

# copy the latest daily backup to becom the youngest weekly backup
if [ -d "$OLDEST_DAILY_BACKUP" ]; then
    /bin/cp --archive --link "$oldest_daily_backup" "$b0"
fi

[1] http://www.mikerubel.org/computers/rsync_snapshots/
[2] https://rsync.samba.org/