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.