Procedure for scheduled backup to external USB drive - Used in addition to snapper & nightly data backup

As the title says, I like redundancy. Yes, I have snapper running with the brtfs file system set up with btrfs Assistant. And yes, every night all my user data (changed) gets backed up to my Synolgoy drive with cron. But coming from a non-btrfs system, I was interested in having full partition backups that if the snapshot doesn’t work, then I have a full backup of everything outside of my home folder.

And I had an extra 500GB M2 (about $125) so I just hang it off the back of the computer and mount it with fstab on boot. I formatted the drive btrfs but I don’t think it matters.

I created this. Obviously change as necessary and your mileage may vary:

sudo mkdir -p /mnt/emergency_backup

Add this line to /etc/fstab with your devices UUID which you can find with lsblk -f:

UUID=3d567213-440f-4152-bd4e-ecd38f15927e  /mnt/emergency_backup  btrfs  defaults,nofail,compress=zstd  0  2

Create this script with nano or whatever, name it emergency_archive.sh in /usr/local/bin

#!/bin/bash 

# --- CONFIGURATION – Using the -x flag ensure no /home--- 
TARGET="/mnt/emergency_backup/fedora-archive" 
LOGFILE="/var/log/fedora_rsync_archive.log" 
EXCLUDES=( 
   --exclude="/home/harry1/Synology" 
   --exclude="/home/harry1/Synology/*" 
   --exclude="*Synology*" 
   --exclude="/mnt/emergency_backup/*" 
   --exclude="/mnt/emergency_backup" 
   --exclude="/dev/*" 
   --exclude="/proc/*" 
   --exclude="/sys/*" 
   --exclude="/tmp/*" 
   --exclude="/run/*" 
   --exclude="/media/*" 
   --exclude="/lost+found" 
   --exclude="/.snapshots"      # Catches Btrfs snapshots 
   --exclude="/.snapshots/*"    # Catches Btrfs snapshots 
   --exclude="*timeshift*"      # Catches Timeshift backups 
   --exclude="/home/harry1/GoogleDrive/*" 
   --exclude="/home/harry1/.cache/*" 
) 

# --- PRE-FLIGHT CHECKS --- 
if [ ! -d "$TARGET" ]; then 
   echo "$(date): Backup failed - External drive not mounted at $TARGET" >> $LOGFILE 
   exit 1 
fi 

echo "--- Starting Weekly Archive: $(date) ---" >> $LOGFILE 

# --- EXECUTION --- 

# 1. Main System: The 'x' flag stays on the M.2 and skips Synology/Snapshots 
/usr/bin/ionice -c 3 /usr/bin/rsync -aAXHvx --sparse --delete --ignore-errors \ 
   "${EXCLUDES[@]}" \ 
   / "$TARGET/" >> $LOGFILE 2>&1 

# 2. Boot Files: Explicitly grab these since 'x' above skips them 
/usr/bin/ionice -c 3 /usr/bin/rsync -aAXHv --delete /boot/ "$TARGET/boot/" >> $LOGFILE 2>&1 
/usr/bin/ionice -c 3 /usr/bin/rsync -aAXHv --delete /boot/efi/ "$TARGET/boot/efi/" >> $LOGFILE 2>&1 

# --- POST-FLIGHT --- 
RSYNC_EXIT=$? 
if [ $RSYNC_EXIT -eq 0 ]; then 
   echo "--- Backup Completed Successfully: $(date) ---" >> $LOGFILE 
else 
   echo "--- Backup encountered errors: $(date) ---" >> $LOGFILE 
fi
  1. Make it executable:
    sudo chmod +x /usr/local/bin/emergency_archive.sh

  2. Create the log file and set permissions:

sudo touch /var/log/fedora_rsync_archive.log && sudo chmod 666
	/var/log/fedora_rsync_archive.log

Make the backup run with cron:

1. Open root's crontab: sudo crontab -e
2. Add this line to run it every Sunday at 3:00 AM: 

00 03 * * 0 /usr/local/bin/emergency_archive.sh

Pruning the Log File which you create in the /etc/logrotate.d directory

First create -rw-r–r–. 1 root root 88 Dec 26 09:36 fedorarchive, and include this code

/var/log/fedora_rsync_archive.log { 
   weekly 
   rotate 12 
   compress 
   missingok
}

You now have a browsable weekly backup that if restoring a snapshot doesn’t work, you can grab files or entire partitions. You can even do a complete re-install from this. If anyone is interested, I can include the steps to recovery using this archive.

Hi.

Some comments.

Beware: you are not testing the exit code of the rsync / call.

You can do this copy with a single rsync like this:

/usr/bin/ionice -c 3 /usr/bin/rsync -aAXHvx --sparse --delete --ignore-errors \
   "${EXCLUDES[@]}" \
   --relative \
   / /./boot /./boot/efi "$TARGET/" >> $LOGFILE 2>&1 

/./ is a mean to not have to do a cd before using rsync --relative.
Ref: man rsync

When excluding a directory it is sufficient to exclude it: no need to exclude also its
members with /* appended.

IMO:

  • -i (–itemize-changes) is better than -v
  • adding --stats to have a final statistics is nice

Thank you for taking the time to look at this. I’m a rank amateur and grateful for your help. I’ve included an amended one here in case anyone else wants to grab it:

harry1@fedora:/usr/local/bin$ cat emergency_archive.sh

#!/bin/bash

# --- CONFIGURATION ---
TARGET="/mnt/emergency_backup/fedora-archive"
LOGFILE="/var/log/fedora_rsync_archive.log"
ERROR_COUNT=0

EXCLUDES=(
    --exclude="/home/harry1/Synology"
    --exclude="/home/harry1/Synology/*"
    --exclude="*Synology*"
    --exclude="/mnt/emergency_backup/*"
    --exclude="/mnt/emergency_backup"
    --exclude="/dev/*"
    --exclude="/proc/*"
    --exclude="/sys/*"
    --exclude="/tmp/*"
    --exclude="/run/*"
    --exclude="/media/*"
    --exclude="/lost+found"
    --exclude="/.snapshots"
    --exclude="/.snapshots/*"
    --exclude="*timeshift*"
    --exclude="/home/harry1/GoogleDrive/*"
    --exclude="/home/harry1/.cache/*"
)

# --- PRE-FLIGHT CHECKS ---
if [ ! -d "$TARGET" ]; then
    echo "$(date): Backup failed - External drive not mounted at $TARGET" >> $LOGFILE
    exit 1
fi

echo "--- Starting Weekly Archive: $(date) ---" >> $LOGFILE

# --- EXECUTION ---

# 1. Main System
# Removed --ignore-errors for safety; added a check for the exit code
/usr/bin/ionice -c 3 /usr/bin/rsync -aAXHix --sparse --delete --stats \
    "${EXCLUDES[@]}" \
    / "$TARGET/" >> $LOGFILE 2>&1

if [ $? -ne 0 ]; then
    ((ERROR_COUNT++))
    echo "Error: Main system rsync failed." >> $LOGFILE
fi

# 2. Boot Files
/usr/bin/ionice -c 3 /usr/bin/rsync -aAXHi --delete --stats /boot/ "$TARGET/boot/" >> $LOGFILE 2>&1
if [ $? -ne 0 ]; then
    ((ERROR_COUNT++))
    echo "Error: /boot rsync failed." >> $LOGFILE
fi

# 3. EFI Files
/usr/bin/ionice -c 3 /usr/bin/rsync -aAXHi --delete --stats /boot/efi/ "$TARGET/boot/efi/" >> $LOGFILE 2>&1
if [ $? -ne 0 ]; then
    ((ERROR_COUNT++))
    echo "Error: /boot/efi rsync failed." >> $LOGFILE
fi

# --- POST-FLIGHT ---
if [ $ERROR_COUNT -eq 0 ]; then
    echo "--- Backup Completed Successfully: $(date) ---" >> $LOGFILE
else
    echo "--- Backup finished with $ERROR_COUNT error(s): $(date) ---" >> $LOGFILE
fi