paulgorman.org/technical

Linux Workstation Backup

Tue 16 Nov 2021 11:19:16 AM EST

We want to run a weekly backup of our Linux workstation to an encrypted external USB drive.

Initial Set Up

Install the necessary encryption packages and modules, if they’re not already installed on this workstation:

🐚 ~ $ sudo apt install cryptsetup
🐚 ~ $ grep -i config_dm_crypt /boot/config-$(uname -r)
CONFIG_DM_CRYPT=m
🐚 ~ $ lsmod | grep dm_crypt
🐚 ~ $ sudo modprobe dm_crypt
🐚 ~ $ lsmod | grep dm_crypt
dm_crypt               53248  0
dm_mod                163840  1 dm_crypt

Plug in a fresh USB hard drive, and set it up.

Take pains to make sure we’re looking at the correct device before overwriting it!

🐚 ~ $ sudo dmesg -T | grep -e sd | tail
[Tue Nov 16 08:35:52 2021] sd 0:0:0:0: [sda] Starting disk
[Tue Nov 16 08:35:52 2021] sd 1:0:0:0: [sdb] Starting disk
[Tue Nov 16 12:33:38 2021] sd 8:0:0:0: Attached scsi generic sg4 type 0
[Tue Nov 16 12:33:38 2021] sd 8:0:0:0: [sdd] 1953458176 512-byte logical blocks: (1.00 TB/931 GiB)
[Tue Nov 16 12:33:38 2021] sd 8:0:0:0: [sdd] Write Protect is off
[Tue Nov 16 12:33:38 2021] sd 8:0:0:0: [sdd] Mode Sense: 47 00 10 08
[Tue Nov 16 12:33:38 2021] sd 8:0:0:0: [sdd] No Caching mode page found
[Tue Nov 16 12:33:38 2021] sd 8:0:0:0: [sdd] Assuming drive cache: write through
[Tue Nov 16 12:33:38 2021]  sdd: sdd1
[Tue Nov 16 12:33:38 2021] sd 8:0:0:0: [sdd] Attached SCSI disk
🐚 ~ $ lsblk | grep sdd
sdd      8:48   0 931.5G  0 disk 
└─sdd1   8:49   0 931.5G  0 part
🐚 ~ $ mount | grep sdd

Once we identify the right drive (sdd in this case) wipe its data and encrypt it with LUKS.

🐚 ~ $ sudo wipefs /dev/sdd*
DEVICE OFFSET       TYPE UUID             LABEL
sdd    0x200        gpt                   
sdd    0xe8decffe00 gpt                   
sdd    0x1fe        PMBR                  
sdd1   0x3          ntfs EC94806C94803ADA Elements
🐚 ~ $ sudo wipefs --all --backup /dev/sdd
/dev/sdd: 8 bytes were erased at offset 0x00000200 (gpt): 45 46 49 20 50 41 52 54
/dev/sdd: 8 bytes were erased at offset 0xe8decffe00 (gpt): 45 46 49 20 50 41 52 54
/dev/sdd: 2 bytes were erased at offset 0x000001fe (PMBR): 55 aa
/dev/sdd: calling ioctl to re-read partition table: Success
🐚 ~ $ sudo cryptsetup luksFormat /dev/sdd

WARNING!
========
This will overwrite data on /dev/sdd irrevocably.

Are you sure? (Type 'yes' in capital letters): YES
Enter passphrase for /dev/sdd: 
Verify passphrase:

Open the now-encrypted drive, and create a partition and file system in it.

🐚 ~ $ sudo cryptsetup luksOpen /dev/sdd backup
🐚 ~ $ lsblk | grep -e sdd -e backup
sdd        8:48   0 931.5G  0 disk  
└─backup 254:0    0 931.5G  0 crypt
🐚 ~ $ ls -l /dev/mapper/backup
lrwxrwxrwx 1 root root 7 Nov 23 08:08 /dev/mapper/backup -> ../dm-0
🐚 ~ $ sudo mkfs.ext4 /dev/mapper/backup

Mount, test, unmount, and close the drive (close, as in wipe cryptographic key from kernel memory):

🐚 ~ $ sudo mkdir /backup
🐚 ~ $ sudo mount /dev/mapper/backup /backup
🐚 ~ $ ls -l /backup/
total 16
drwx------ 2 root root 16384 Nov 23 08:11 lost+found
🐚 ~ $ df -h | grep backup
/dev/mapper/backup  916G   28K  870G   1% /backup
🐚 ~ $ sudo umount /backup
🐚 ~ $ sudo cryptsetup luksClose backup

Running Backups

Run backups with two shell scripts and a cron job.

This cron job:

0  */2 *   *   *     /home/paulgorman/bin/backup-nag 5

…runs this script to nag us if we haven’t recently backed up:

#!/bin/sh

# Nag me via cron job if I haven't run a backup in N days.
# Install the `libnotify-bin` and `mako-notifier` packages on Debian, under Sway.
#
#       m  h   dom mon dow   command
#       0  */2 *   *   *     /home/paulgorman/bin/backup-nag 5
#
# Paul Gorman, November 2021

days="$1"
f="$HOME"/.lastbackup

export $(cat /proc/$(pgrep -u $(whoami) -f 'dbus-daemon --session')/environ | grep -z '^DBUS_SESSION_BUS_ADDRESS=')

if [ ! -e "$f" ]; then
    touch "$f"
fi

if [ $(find "$f" -mtime +"$days") ]; then
    notify-send "Plug in a backup drive. Run '~/bin/backup'."
fi

Here’s the script that actually writes the backup. It requires user input, so we run it manually.

#!/bin/sh
set -uf

# Back up to an external LUKS-encrypted disk.
# Run as my user, not root.
# Paul Gorman, November 2021

echo '--- $ lsblk | grep sd'
lsblk | grep sd
echo
echo '--- $ df -h | grep sd'
df -h | grep sd
echo
echo -n 'Enter device name of backup drive (like "/dev/sdd"): '
read -r backupDevice

echo -n "Backup will be written to $backupDevice. Type 'yes' to continue: "
read -r continue
if [ "$continue" != "yes" ]; then
	exit
fi

sudo mkdir -p /backup

mkdir -p "$HOME"/backups

sudo chown "$USER":root "$HOME"/backups
sudo chmod 0770 "$HOME"/backups
if [ $(find "$HOME"/backups/nanook:backup-nanook.tgz -mmin +120 -print) ] || [ ! -f "$HOME"/backups/nanook:backup-nanook.tgz ]; then
	scp nanook:backup-nanook.tgz "$HOME"/backups/
fi
if [ $(find "$HOME"/backups/nanook:backup-inky.tgz -mmin +120 -print) ] || [ ! -f "$HOME"/backups/nanook:backup-inky.tgz ]; then
	scp example.org:/backups/backup-inky.tgz "$HOME"/backups/
fi


sudo cryptsetup luksOpen "$backupDevice" backup
sudo mount /dev/mapper/backup /backup

cleanup () {
	sudo umount /backup
	sudo cryptsetup luksClose backup
	exit "$1"
}

sudo mkdir -p /backup/Trash
ok=$? ; if [ ! "$ok" ]; then cleanup 1 ; fi

sudo rsync -aqh --delete --backup --backup-dir=/backup/Trash \
	--exclude tmp \
	--exclude Downloads \
	--exclude .cache \
	"$HOME" /backup
ok=$? ; if [ ! "$ok" ]; then cleanup 1 ; fi
sudo rsync -aqh --delete --backup --backup-dir=/backup/Trash /root /backup
ok=$? ; if [ ! "$ok" ]; then cleanup 1 ; fi
sudo rsync -aqh --delete --backup --backup-dir=/backup/Trash --exclude old /data /backup
ok=$? ; if [ ! "$ok" ]; then cleanup 1 ; fi
sudo rsync -aqh --delete --backup --backup-dir=/backup/Trash /etc /backup
ok=$? ; if [ ! "$ok" ]; then cleanup 1 ; fi
sudo rsync -aqh --delete --backup --backup-dir=/backup/Trash /usr/local/etc /backup
ok=$? ; if [ ! "$ok" ]; then cleanup 1 ; fi
sudo rsync -aqh --delete --backup --backup-dir=/backup/Trash /var/spool/cron /backup
ok=$? ; if [ ! "$ok" ]; then cleanup 1 ; fi
sudo rsync -aqh --delete --backup --backup-dir=/backup/Trash /var/spool/anacron /backup

cleanup 0

date +%s > "$HOME"/.lastbackup
touch "$HOME"/.lastbackup

References