Automating Linux File Backups With Bash
“Always keep a backup” is one of the most fundamental rules in the digital age, but just like everyone else, I rarely follow it. Not too long ago, I faced the consiquences of my actions when my hard drive failed, and I lost a lot of important data because all my backups were outdated by about 1 year.
So I made this script to automate the process. It will make it more easier to make backups. I will be using luks to encrypt the drive because I don’t like to keep my private data openly on uncrypted drives.
Preparing the drive
I will connect the drive to my laptop, and run lsblk to find the block device.
(I will be using a 16GB usb drive for the example)
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 931.5G 0 disk
├─sda1 8:1 0 2G 0 part /boot
└─sda2 8:2 0 929.5G 0 part
└─cryptlvm 253:0 0 929.5G 0 crypt
├─MyVolGroup-swap 253:1 0 8G 0 lvm [SWAP]
├─MyVolGroup-root 253:2 0 100G 0 lvm /
└─MyVolGroup-home 253:3 0 821.5G 0 lvm /home
sdb 8:16 1 14.6G 0 disk
└─sdb1 8:17 1 14.6G 0 part
sr0 11:0 1 1024M 0 rom
From the output, I can see that my usb drive is listed as sdb. Now I’m going to format the drive and encrypt it with luks. Before we encrypt it, we need to generate a key to decrypt it. We can use a password, but then we will have to enter it manually.
To create the key file,
$ sudo dd if=/dev/urandom of=/root/luks.key bs=4096 count=1
# Set the permissions to read-only
$ sudo chmod 400 /root/luks.key
Before encrypting the drive, remember that this will completely wipe your data. So make sure to backup anything on that drive. Be careful not to format the wrong drive by accident as well(I have made that mistake before).
To create an encrypted luks volume, use the following command.
$ sudo cryptsetup luksFormat /dev/sdb1 \
-c aes-xts-plain64 \
--hash sha512 \
--key-size 512 \
--iter-time 5000 \
--key-file /root/luks.key
Now that we have encrypted our drive, we can work on the backup script which will handle the decryption and the file backup.
Decrypting the drive
To decrypt the drive, we can use the command sudo cryptsetup luksOpen /dev/sdb1 luks_drive --key-file /root/luks.key. But there’s a problem. There is no guarantee that our encrypted partition will be named /dev/sdb1 after the next system reboot. So we need to identify the partition each time using the UUID of the partition.
$ ls -hAlF /dev/disk/by-uuid
total 0
lrwxrwxrwx 1 root root 10 Nov 17 18:13 29982e4e-ac5a-494a-9335-d542c3e6e379 -> ../../dm-2
lrwxrwxrwx 1 root root 10 Nov 17 18:13 3A12-B39A -> ../../sda1
lrwxrwxrwx 1 root root 10 Nov 17 18:13 499c4f61-9f38-4b0f-aef1-f0dd87bf2336 -> ../../dm-1
lrwxrwxrwx 1 root root 10 Nov 17 18:13 8e3c15bd-dfb8-4c6b-a031-552cebb7fc77 -> ../../sda2
lrwxrwxrwx 1 root root 10 Nov 18 09:57 ace19864-ea89-4db9-8da1-aa7956501198 -> ../../sdb1
lrwxrwxrwx 1 root root 10 Nov 17 18:13 cc35e6cd-cbd3-41d4-9386-894f5f76cc59 -> ../../dm-3
As you can see, the UUID of the sdb1 partition is ace19864-ea89-4db9-8da1-aa7956501198. Now we can open the luks container.
$ sudo cryptsetup luksOpen \
/dev/disk/by-uuid/ace19864-ea89-4db9-8da1-aa7956501198 \
luks_drive \
--key-file /root/luks.key
Here, luks_drive is the name we are using to map the decrypted drive. This means that the decrypted drive is now available at /dev/mapper/luks_drive. Since this is the first time, we need to create a filesystem in our decrypted drive. So let’s format it with ext4.
$ sudo mkfs.ext4 /dev/mapper/luks_drive
Now we can mount the drive.
$ sudo mkdir /media/tmp_luks_drive
$ sudo mount /dev/mapper/luks_drive /media/tmp_luks_drive
We can access the decrypted drive at /media/tmp_luks_drive.
Making file backups with rsync
To copy the files/folders to our decrypted drive, we can use rsync. I will backup my notes folder in my home directory as an example.
$ rsync -a notes /media/tmp_luks_drive
It’s important to use notes instead of notes/ because the latter will copy the contents of notes to the output instead of copying the entire folder.
Closing the luks drives
Now that we have made the backup, it’s time to unmount the decrypted drive, then close the decrypted luks container.
$ sudo umount /dev/mapper/luks_drive
$ sudo cryptsetup luksClose /dev/mapper/luks_drive
$ sudo rmdir /media/tmp_luks_drive
Putting it all together and creating the backup script
To create the initial version of the backup script, let’s just put everything we did in the previous steps to one place. I will name the script as backup.sh. It should look like this so far,
#!/bin/bash
# Decrypt the luks container
sudo cryptsetup luksOpen \
/dev/disk/by-uuid/ace19864-ea89-4db9-8da1-aa7956501198 \
luks_drive \
--key-file /root/luks.key
# Mounting the decrypted drive
sudo mkdir /media/tmp_luks_drive
sudo mount /dev/mapper/luks_drive /media/tmp_luks_drive
# Making the backup
rsync -a notes /media/tmp_luks_drive
# Cleaning up
sudo umount /dev/mapper/luks_drive
sudo cryptsetup luksClose /dev/mapper/luks_drive
sudo rmdir /media/tmp_luks_drive
Make sure to add file permissions for executing the script with chmod +x backup.sh. Then it can be executed with:
$ ./backup.sh
I will clean up the code a little and add some general error handling to the script. Then the final script will look like bellow.
#!/bin/bash
UUID="894bd20c-5ed8-49b2-8efd-58ff9c828f79"
KEY_FILE=/root/usb.key
MAPPER="/dev/mapper/luks-$UUID"
MNT_POINT="/media/luks-$UUID"
set -euo pipefail
# Cleanup function will handle
# unmounting the disk,
# removing mount point and
# closing the luks container
cleanup() {
# unmount the disk
if mountpoint -q $MNT_POINT; then
if ! sudo umount "$MNT_POINT"; then
echo "Warning: failed to unmount mount point" >&2
else
echo "Unmounted mount point"
fi
fi
# remove mount point
if [ -d "$MNT_POINT" ]; then
if sudo rmdir "$MNT_POINT"; then
echo "Removing mount point"
else
echo "Warning: mount point not empty" >&2
fi
fi
# close luks
if [ -e "$MAPPER" ]; then
if sudo cryptsetup luksClose "$MAPPER"; then
echo "Luks container closed"
else
echo "Warning: failed to close luks container" >&2
fi
fi
}
trap cleanup EXIT
# Check if luks container is open
sudo cryptsetup luksOpen "/dev/disk/by-uuid/$UUID" "luks-$UUID" --key-file "$KEY_FILE"
# Check if mountpoint already exist
if [ ! -d "$MNT_POINT" ]; then
sudo mkdir -p $MNT_POINT
fi
echo "Mounting drive"
sudo mount "$MAPPER" "$MNT_POINT"
echo "Performing backup process"
if sudo rsync -a "/home/algonotion/notes" "$MNT_POINT"; then
echo "File backup successful"
ls "$MNT_POINT"
else
echo "Backup error" >&2
exit 1
fi
echo "Script executed successfully"
With this, all I have to do is plug in the drive, then execute the script. You can modify this script further to suit your system. Then you can use it for your own customized backup system.