More Robust Rsync Backups

It’s a curious thing about [software engineering]: not only do we not learn from our mistakes, we also don’t learn from our successes.

Keith Braithwaite

In my most recent post I talked about using cronjobs and openrsync(1) for simple bloat-free backups. This is not going to be a long post as I’m short on time, but I recently had a few issues with my backup scripts that I needed to address:

  1. When autofs(8) unmounts my backup directory to save resources, large backups are dumped into my root partition and fill up my disk.
  2. Having a unified file for both includes and excludes à la .gitignore would be more ergonomic.
  3. I want to easily see when the last backup took place, to ensure the backup system is running properly.

Well, it turns out that fixing the second issue is not really possible (without doing my own parsing in shell script) with openrsync(1), but if you’re happy to use the sloppy rsync(1) then it is possible via filters. Here is what I ended up writing after a bit of manual reading:

#!/bin/sh

readonly BACKUP_DIR=/mnt/backups
readonly OCCURANCE=daily
readonly LOCKFILE="/run/lock/backup-$OCCURANCE.lock"

exec 9>"$LOCKFILE"
if ! flock -n 9; then
	>&2 printf 'backup: another backup is already running\n'
	exit 1
fi

until mountpoint -q "$BACKUP_DIR"; do
	sleep 60
done

rsync -Aamqr -f 'merge /etc/backuplist' --delete --numeric-ids \
	/ "$BACKUP_DIR/desktop.$OCCURANCE/"
sync -f "$BACKUP_DIR/desktop.$OCCURANCE"
sed -i '/^desktop.'"$OCCURANCE"'\t/s/[^\t]*$/'"$(date)"'/' "$BACKUP_DIR/when"

My filter file is located at /etc/backuplist and looks something like the following, and allows me to use wildcards as well as + and - prefixes to control includes and excludes.

# System Excludes
- /bin/
- /boot/
# ...
- /usr/
- /var/

# Excludes
- /home/*/down/
- /home/*/var/cache/
# ...
- lost+found/
- node_modules/

# Directory Includes
+ /etc/autofs/***
+ /etc/cron.daily/***
# ...
+ /home/***
+ /root/***

# File Includes
+ /etc/backuplist
+ /etc/crypttab
# ...
+ /etc/resolv.conf
+ /etc/resolvconf.conf

# Catch-Alls
+ */
- *

Finally, in my backups directory I have a when file that looks something like the following. It might render weird in the browser (it does for me at least), but in a text editor it’s just fine.

desktop.daily	Sat May 16 17:37:05 CEST 2026
desktop.weekly	Sat May 16 17:42:55 CEST 2026
srv.daily	...
srv.weekly	...