Backing up to a USB stick automatically via udev
There was some interesting discussion on the fediverse about tools for backing up to USB devices on Linux.
Here’s what I use, in case it helps anyone.
This is just one of my backups, of course. This is a backup to a USB device, which mounts to /media/neil/backup_disk
, which has a LUKS-encrypted partition on it. The objective of this particular backup is so that I have an offline copy of my most important data, just in case something (ransomware?) affected my online systems.
The reason for making it run automatically, via udev
, is to make it as easy to use as possible: I just need to plug it in and, a few minutes later, unmount it and remove it.
That way, I’ll actually use it. (And the key, from an anti-ransomware perspective, is to remove it once finished.)
udev rule
This is the bit of the config which triggers the backup automatically when the specific USB memory stick is plugged in.
In /etc/udev/rules.d/80-backupdrive.rules
I have:
ACTION=="add", SUBSYSTEM=="usb", ENV{PRODUCT}=="789/5678/123", ENV{DEVTYPE}=="usb_device", RUN+="/home/neil/briefcase/Store/tools/backupdrive/udevbackuptimer.sh"
Multiple udev matching criteria
I match based on both the product serial number and the device type, both ascertained through watching the output of sudo udevadm monitor --kernel --property --subsystem-match=usb
when I plugged in the device in question.
Originally I just used the product serial number, but because this appears in multiple udev messages, it meant that the script would be triggered multiple times. There was only one udev message with both the product serial number and the device type of “usb_device”, so, by matching on that, it means the script gets run only once.
The timer script which udev runs
The udev rule runs the script at /home/neil/briefcase/Store/tools/backupdrive/udevbackuptimer.sh
.
It is a very short and simple bash script:
#!/bin/bash
alert() {
sudo -u neil DISPLAY=wayland-0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send "$1" --icon=dialog-information -t 1000
}
sudo -u neil /usr/bin/at now +1 minutes -f /home/neil/briefcase/Store/tools/backupdrive/udevbackup.sh
alert "Backup to begin in 1 minute"
All it does is use the at
command to run the actual backup script in one minute, and send a desktop alert about it.
Why bother with this?
The reason this script exists at all is that it seems - in my experimentation at least - that the script linked from the udev rule is run, and needs to complete, before the USB device is mounted.
If I use the udev rule to run the backup script itself, it attempts to do this before the USB device, onto which the backup is made, is available. So it fails.
One approach would be to include, within the script run by the udev rule, code to cover the mounting etc. of the USB device. Because I want this to be handed by the OS, which also takes care of the decryption of the LUKS partition using a key stored in the keychain, that is not a route I have explored.
So this is a bit of a workaround: a short script, which sets the backup script itself to run in one minutes time and then completes, after which the USB device is mounted and the LUKS partition is decrypted, automatically. Then the backup script runs.
Why the lengthy notify-send function?
Because of the way notify-send
works - I will be logged in as user “neil”, and so I want to ensure that the alert is visible to me. Without this, notify-send
would try to send the alert to the root user, which runs the script indicated by the udev rule, and so it would fail as there isn’t a login/desktop session for the root user.
The backup script itself
At /home/neil/briefcase/Store/tools/backupdrive/udevbackup.sh
, I have the backup script itself.
It is not fancy and, obviously, is tailored to the directories that I want to back up.
It backs up:
- my home directory (but neither (a) a large disk image, nor (b) a particular FUSE-mounted directory, “Clients”, if it is not accessible to the backup script)
- /usr/local
- /etc/apt
- apt, dpkg, snap, and flatpak installation information
I should probably set it to back up cronjobs too, just in case I had any.
To avoid it running multiple concurrent times, I’m using a very basis file-based mechanism: the script first checks if the file “/media/neil/backup_disk/.isbackingup” is present. If it is, it assumes that the script is already running, and so exits. It would also exit if, for some reason, the script was not running but had terminated before it got to the point of deleting that sort-of-lock-file. I’m sure there’s a better way of doing this.
Again, I use notify-send
for desktop notifications. These are not perfect, as GNOME does not recognise the timer (-t 1000
) switch, and so sometimes one alert hangs around meaning I don’t see others. It’s not the end of the world, as I can look in the notifications history in the taskbar to see what the latest alert was.
#! /bin/bash
FILE=/media/neil/backup_disk/.isbackingup
alert() {
sudo -u neil DISPLAY=wayland-0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send "$1" --icon=dialog-information -t 1000
}
backup() {
# alert "Disk is mounted"
if test -f "$FILE"; then
# alert "$FILE exists, so don't back up"
exit
else
alert "Starting backup"
touch "$FILE"
# alert "$FILE does not exist, so backing up"
# alert "Starting home backup"
if [ -f "/home/neil/Clients/.isdecrypted" ]; then
# alert "Backing up Clients"
rsync -avz --ignore-errors --delete-before --exclude "neil/Desktop/provisioning_pi.img" /home/neil /media/neil/backup_disk
else
# alert "Not backing up Clients"
rsync -avz --ignore-errors --delete-before --exclude "neil/Desktop/provisioning_pi.img" --exclude "neil/Clients" /home/neil /media/neil/backup_disk
fi
# alert "Starting /usr/local backup"
rsync -avz --delete-before /usr/local /media/neil/backup_disk
# alert "Starting /etc/apt backup"
rsync -avz --delete-before /etc/apt /media/neil/backup_disk
# alert "Starting package installation backup"
apt list --installed > /media/neil/backup_disk/list_of_apt_installed_packages.txt
dpkg-query -f '${binary:Package}n' -W > /media/neil/backup_disk/list_of_dpkg_installed_packages.txt
snap list > /media/neil/backup_disk/list_of_snap_installed_packages.txt
flatpak list > /media/neil/backup_disk/list_of_flatpak_installed_packages.txt
alert "Backup finished"
# alert "Removing $FILE"
rm "$FILE"
if [ -f "$FILE" ]; then
alert "Removing $FILE failed"
# else
# alert "Removing $FILE succeeded"
fi
alert "Disk ready to unmount"
fi
}
if [ -f "/media/neil/backup_disk/.ismounted" ]; then
backup
else
alert "No backup disk detected, sleeping"
sleep 10
if [ -f "/media/neil/backup_disk/.ismounted" ]; then
backup
else
alert "Error backing up"
exit
fi
fi
I should probably add a function at the end to automatically close the LUKS partition and unmount the device when it is finished. For now, I just unmounted it using Nautilus.
Others’ suggestions
systemd
I’d be inclined to use the udev rule to start a system service which you can run as a user
As an example, they said:
File is
/etc/systemd/system/mq2my.service
- change themq2my
to whatever you want to call it, and the user/group to your user accountMake
WantedBy
a blank string to not start at boot.
[Unit]
Description=MQTT to MySQL service
After=mysql.service
[Service]
Type=simple
User=andy
Group=andy
TimeoutStartSec=0
RemainAfterExit=no
ExecStart=/opt/mq2my/startMQ2MY.sh
[Install]
WantedBy=multi-user.target
when you’ve built this file,
systemctl enable mq2my.service
and
systemctl daemon-reload
Udev rule just starts the service which runs your script as you.
Thanks!
systemd, no udev
why a udev rule at all? I can’t remember the details, but I have a systemd service file that has the following two lines and starts the service configured in it when the device is connected:
[Install]
WantedBy=dev-disk-byx2did-atax2dWDC_WD30EFRXx2dFOOx2dBARx2dpart1.device
and
I use
"PrivateMounts=true"
in the service section so that the mount is not even seen in the regular filesystem
Making it work like macOS’s Time Machine
Fedizen Frank added this useful tip:
You can tease time-machine-like time slices out of rsync with a pointer to a preceding backup. rsync will then set hard links to files that haven’t changed, in a fresh directory, so if you have backed up after bad stuff happens, you still have earlier pristine copies to rebuild from. Here’s the script I use for that: https://gist.github.com/fbennett/dcb066fc71e116d3e35d2ec9e144ab71
You may also like:
- Automating actions in Nautilus (GNOME's file manager) with scripts
- Fixing espanso incomplete text replacement
- Jabra Evolve2 40 and Debian Linux
- Debian on a £190 Lenovo ThinkPad X1 Yoga Gen 2
- Enabling Webauthn in Firefox via snap
- Decrapifying (mostly) an Amazon Fire 8 HD Kids tablet via Linux
- Microsoft SurfaceBook 2 running Debian Linux working with two 4K screens
- Fixing rkhunter's 'Update failed' error
- PINE64's PineBuds Pro: my first impressions
- #FreeSoftwareAdvent: all my Free software suggestion posts in one place
- Wireless printing and scanning with a Brother MFC L2750DW on Debian
- Adding image resizing options to nautilus right-click menu
- Syncing signatures in Evolution
- Scheduling posts on Mastodon, the hack-y way
- Early impressions of CryptPad on a Raspberry Pi