2017-06-09 10:54:55 -04:00
.. include :: ../global.rst.inc
.. highlight :: none
Automated backups to a local hard drive
=======================================
This guide shows how to automate backups to a hard drive directly connected
to your computer. If a backup hard drive is connected, backups are automatically
started, and the drive shut-down and disconnected when they are done.
This guide is written for a Linux-based operating system and makes use of
systemd and udev.
Overview
--------
2025-08-23 18:21:57 -04:00
A udev rule is created to trigger on the addition of block devices. The rule contains a tag
that triggers systemd to start a one-shot service. The one-shot service executes a script in
2017-06-09 10:54:55 -04:00
the standard systemd service environment, which automatically captures stdout/stderr and
logs it to the journal.
2025-08-23 18:21:57 -04:00
The script mounts the added block device if it is a registered backup drive and creates
backups on it. When done, it optionally unmounts the filesystem and spins the drive down,
2017-06-09 10:54:55 -04:00
so that it may be physically disconnected.
Configuring the system
----------------------
First, create the `` /etc/backups `` directory (as root).
All configuration goes into this directory.
2025-08-23 18:21:57 -04:00
Find out the ID of the partition table of your backup disk (here assumed to be /dev/sdz)::
2023-05-14 08:52:39 -04:00
lsblk --fs -o +PTUUID /dev/sdz
2017-06-09 10:54:55 -04:00
2025-01-18 08:22:47 -05:00
Then, create `` /etc/backups/80-backup.rules `` with the following content (all on one line)::
2017-06-09 10:54:55 -04:00
2025-01-19 08:41:52 -05:00
ACTION=="add", SUBSYSTEM=="block", ENV{ID_PART_TABLE_UUID}=="<the PTUUID you just noted>", TAG+="systemd", ENV{SYSTEMD_WANTS}+="automatic-backup.service"
2017-06-09 10:54:55 -04:00
The "systemd" tag in conjunction with the SYSTEMD_WANTS environment variable has systemd
launch the "automatic-backup" service, which we will create next, as the
`` /etc/backups/automatic-backup.service `` file:
.. code-block :: ini
[Service]
Type=oneshot
ExecStart=/etc/backups/run.sh
2025-08-23 18:21:57 -04:00
Now, create the main backup script, `` /etc/backups/run.sh `` . Below is a template;
modify it to suit your needs (e.g., more backup sets, dumping databases, etc.).
2017-06-09 10:54:55 -04:00
.. code-block :: bash
#!/bin/bash -ue
# The udev rule is not terribly accurate and may trigger our service before
# the kernel has finished probing partitions. Sleep for a bit to ensure
# the kernel is done.
#
# This can be avoided by using a more precise udev rule, e.g. matching
# a specific hardware path and partition.
sleep 5
#
# Script configuration
#
# The backup partition is mounted there
MOUNTPOINT=/mnt/backup
# This is the location of the Borg repository
TARGET=$MOUNTPOINT/borg-backups/backup.borg
# Archive name schema
DATE=$(date --iso-8601)-$(hostname)
# This is the file that will later contain UUIDs of registered backup drives
DISKS=/etc/backups/backup.disks
# Find whether the connected block device is a backup drive
for uuid in $(lsblk --noheadings --list --output uuid)
do
if grep --quiet --fixed-strings $uuid $DISKS; then
break
fi
uuid=
done
if [ ! $uuid ]; then
echo "No backup disk found, exiting"
exit 0
fi
echo "Disk $uuid is a backup disk"
partition_path=/dev/disk/by-uuid/$uuid
2025-08-23 18:21:57 -04:00
# Mount filesystem if not already done. This assumes that if something is already
# mounted at $MOUNTPOINT, it is the backup drive. It will not find the drive if
2017-06-09 10:54:55 -04:00
# it was mounted somewhere else.
2023-05-14 08:52:39 -04:00
findmnt $MOUNTPOINT >/dev/null || mount $partition_path $MOUNTPOINT
2017-06-09 10:54:55 -04:00
drive=$(lsblk --inverse --noheadings --list --paths --output name $partition_path | head --lines 1)
echo "Drive path: $drive"
#
# Create backups
#
# Options for borg create
2024-08-22 11:52:23 -04:00
BORG_OPTS="--stats --one-file-system --compression lz4"
2017-06-09 10:54:55 -04:00
# Set BORG_PASSPHRASE or BORG_PASSCOMMAND somewhere around here, using export,
# if encryption is used.
2025-08-23 18:21:57 -04:00
# Because no one can answer these questions non-interactively, it is better to
# fail quickly instead of hanging.
2017-06-09 10:54:55 -04:00
export BORG_RELOCATED_REPO_ACCESS_IS_OK=no
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=no
# Log Borg version
borg --version
echo "Starting backup for $DATE"
# This is just an example, change it however you see fit
borg create $BORG_OPTS \
2022-05-27 02:24:27 -04:00
--exclude root/.cache \
--exclude var/lib/docker/devicemapper \
2017-06-09 10:54:55 -04:00
$TARGET::$DATE-$$-system \
/ /boot
2025-08-23 18:21:57 -04:00
# /home is often a separate partition/filesystem.
# Even if it is not (add --exclude /home above), it probably makes sense
2017-06-09 10:54:55 -04:00
# to have /home in a separate archive.
borg create $BORG_OPTS \
2022-05-27 02:24:27 -04:00
--exclude 'sh:home/*/.cache' \
2017-06-09 10:54:55 -04:00
$TARGET::$DATE-$$-home \
/home/
echo "Completed backup for $DATE"
# Just to be completely paranoid
sync
if [ -f /etc/backups/autoeject ]; then
umount $MOUNTPOINT
hdparm -Y $drive
fi
if [ -f /etc/backups/backup-suspend ]; then
systemctl suspend
fi
Create the `` /etc/backups/autoeject `` file to have the script automatically eject the drive
2025-08-23 18:21:57 -04:00
after creating the backup. Rename the file to something else (e.g., `` /etc/backups/autoeject-no `` )
when you want to do something with the drive after creating backups (e.g., running checks).
2017-06-09 10:54:55 -04:00
Create the `` /etc/backups/backup-suspend `` file if the machine should suspend after completing
2022-12-29 17:26:54 -05:00
the backup. Don't forget to disconnect the device physically before resuming,
2017-06-09 10:54:55 -04:00
otherwise you'll enter a cycle. You can also add an option to power down instead.
2025-08-23 18:21:57 -04:00
Create an empty `` /etc/backups/backup.disks `` file, in which you will register your backup drives.
2017-06-09 10:54:55 -04:00
2025-08-23 18:21:57 -04:00
Finally, enable the udev rules and services:
2017-06-09 10:54:55 -04:00
.. code-block :: bash
2025-01-18 08:22:47 -05:00
ln -s /etc/backups/80-backup.rules /etc/udev/rules.d/80-backup.rules
2017-06-09 10:54:55 -04:00
ln -s /etc/backups/automatic-backup.service /etc/systemd/system/automatic-backup.service
systemctl daemon-reload
udevadm control --reload
Adding backup hard drives
-------------------------
Connect your backup hard drive. Format it, if not done already.
2025-08-23 18:21:57 -04:00
Find the UUID of the filesystem on which backups should be stored::
2017-06-09 10:54:55 -04:00
lsblk -o+uuid,label
2025-08-23 18:21:57 -04:00
Record the UUID in the `` /etc/backups/backup.disks `` file.
2017-06-09 10:54:55 -04:00
2025-08-23 18:21:57 -04:00
Mount the drive at /mnt/backup.
2017-06-09 10:54:55 -04:00
Initialize a Borg repository at the location indicated by `` TARGET `` ::
borg init --encryption ... /mnt/backup/borg-backups/backup.borg
Unmount and reconnect the drive, or manually start the `` automatic-backup `` service
to start the first backup::
systemctl start --no-block automatic-backup
See backup logs using journalctl::
journalctl -fu automatic-backup [-n number-of-lines]
Security considerations
-----------------------
2025-08-23 18:21:57 -04:00
The script as shown above will mount any filesystem with a UUID listed in
`` /etc/backups/backup.disks `` . The UUID check is a safety/annoyance-reduction
2017-06-09 10:54:55 -04:00
mechanism to keep the script from blowing up whenever a random USB thumb drive is connected.
2025-08-23 18:21:57 -04:00
It is not meant as a security mechanism. Mounting filesystems and reading repository
data exposes additional attack surfaces (kernel filesystem drivers,
possibly userspace services, and Borg itself). On the other hand, someone
2017-06-09 10:54:55 -04:00
standing right next to your computer can attempt a lot of attacks, most of which
2025-08-23 18:21:57 -04:00
are easier to do than, e.g., exploiting filesystems (installing a physical keylogger,
2017-06-09 10:54:55 -04:00
DMA attacks, stealing the machine, ...).
Borg ensures that backups are not created on random drives that "just happen"
to contain a Borg repository. If an unknown unencrypted repository is encountered,
then the script aborts (BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=no).
Backups are only created on hard drives that contain a Borg repository that is
either known (by ID) to your machine or you are using encryption and the
passphrase of the repository has to match the passphrase supplied to Borg.