Linux USB Backup

My entire webserver, all the databases and files and, well, everything, is not a huge amount of data. It fits with a whole lot of room to spare on a 64GB USB stick.
I had the idea to get a super-tiny USB device and hang it on my keyring, so that whenever I leave the house I have my entire webserver with me, in case the worst happens. It makes sense to me: I never leave the house without my keys, right? This would require very little training to remember.
The tricky part was having the backup happen automatically. I envisioned a scenario where I come home and plug my keys into a USB port, triggering the backup and everything’s ready to go in a few seconds and so I can take my keys with me later without worrying about anything. Simple. But, oh yeah, it’s Linux, so nothing is ever simple.
First, I had to learn how udev works. udev is how Linux (in this case, Ubuntu 19.04) manages USB devices. It allows me to create a udev rule that fires every time a specific USB device is plugged in. Perfect. But… udev only launches small processes, it’ll kill any long-running (more than a few seconds) activity as a matter of course, so I had to tell udev to start a service, which starts the backup script.
So first, create a udev rule (I called mine 10-NFG-Backup) in /etc/udev/rules.d/

SUBSYSTEM=="block", ACTION=="add", ATTRS{idVendor}=="0781" SYMLINK+="external%n", RUN+="/usr/bin/systemctl start NFGbackup.service"

The important parts are the ATTRS{idVendor} and RUN+=. The former is the vendor ID for the USB device. It’ll trigger whenever a Sandisk device is connected. Use the command lsusb to get a list of connected USB devices and their vendor IDs.

# lsusb
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 004: ID 0781:5583 SanDisk Corp. Ultra Fit

The two numbers are vendor and device, in this case 0781 and 5583. There are other ways to identify the device in the udev rule but this is what I used. ¯\_(ツ)_/¯
Next up, the service. I created a new service file in /etc/systemd/system/ called NFGbackup.service. It does nothing but call the backup script.

[Unit]
Description=Backup to USB
[Service]
Type=oneshot
ExecStart=/home/nfg/.usbackup
[Install]
WantedBy=multi-user.target

Note the Type=oneshot which defines this service as something that fires once and doesn’t need to stay running.
And then finally, the backup script itself. It’s a standard bash script full of commands to dump SQL databases and rsync files to the USB device. I leave that to you, dear reader, to write as you require. Mine’s full of encryption configs and timers so that I can see its progress. At the end, it fires off a request to Notica, a free service that provides browser notifications.
There was a fuckload of research and education involved in creating this process. I hope this post saves you some time. =)
Here’s the skeleton of my backup script. Feel free to tell me if you know a way to improve it. =)

#!/bin/sh
 
echo "Backup Started!"
 
PATH=/bin:/usr/bin:/usr/local/bin
 
#  SET SOME VARS
 
PASS=<redacted>
BACKUP_SOURCE="/home/user/backup"
BACKUP_DEVICE="/dev/disk/by-id/usb-SanDisk_Ultra_Fit_0101c5d10cfbb8aa698dfdd868c04df678223688334e4ce13e9c9636b9969e84cb40000000000-0:0"
MOUNT_POINT="/mnt/device"
 
#  First let's dump some SQL:
 
mysqldump --defaults-file=/home/user/backup/.mysql.cnf DBname > /home/user/backup/DBname.sql
 
#  Copy some files...
 
rsync -a /home/user/files /home/user/backup/
 
# DISMOUNT DRIVE:
 
veracrypt -d
 
# MOUNT DRIVE:
 
veracrypt -t -k "" --pim=0 -p$PASS $BACKUP_DEVICE $MOUNT_POINT
 
if grep -qs '/mnt/ceres ' /proc/mounts; then
 
  # Backup Copy to USB
 
  rsync -a /home/user/backup /mnt/device
 
  # DISMOUNT DRIVE:
 
  veracrypt -d
 
else
 
  echo "USB Device Not Attached."
  # this comment is pointless if running as a service, but it works if running as a script.
  # there was a whole ton of user-friendly status reporting removed for brevity and readability.
 
fi
 
# COMPLETE
 
now=$(date +"%T")
curl --data "d:Backup Complete: $now" "https://notica.us/?<redacted>" ;

As a bonus, I use this code in the script to display a timer at every step. Insert into your own scripts as you like:

# START TIMER
 
start=$(date +%s)
 
# do some stuff
 
end=$(date +%s)
endm=$(echo "$((end-start))/60" | bc)
ends=$(echo "$((end-start))-60*$endm" | bc)
echo "[ $endm m $ends s ] Stuff Complete."

Displays the following:
[ 1 m 12 s ] Stuff Complete
Repeat those last four lines anywhere in the script to update the user on progress.

Leave a comment

* - Required fields