BorgBackup automation scripts

Distributed, encrypted, deduplicated and compressed backups
Published on May 16, 2022.

I have been using BorgBackup for a while now to backup my machines to external drives and a NAS. For more convenience I created a set of scripts to automate this process.

The first part is a script doing the actual backup process by calling borg. This is inspired by the example script in the documentation.

#!/bin/sh

# ~/bin/backup-borg

# Helper script for automated borg backups.
# BACKUP_PATH, BACKUP_EXCLUDES, BORG_REPO and BORG_PASSPHRASE
# should already be set in environment from calling script!

if [[ -z "$BACKUP_PATH" ]]; then
    echo "Please set BACKUP_PATH before calling"
    exit 1
fi
if [[ -z "$BACKUP_EXCLUDES" ]]; then
    echo "Please set BACKUP_EXCLUDES before calling"
    exit 1
fi
if [[ -z "$BORG_REPO" ]]; then
    echo "Please set BORG_REPO before calling"
    exit 1
fi
# $BORG_PASSPHRASE is allowed to be empty

# some helpers and error handling:
info() { printf "\n%s\n%s\n\n" "$( date )" "$*" >&2; }
trap 'echo $( date ) Backup interrupted >&2; exit 2' INT TERM

info "Starting backup"

# Backup the most important directories into an archive named after
# the machine this script is currently running on:

borg create              \
    --verbose            \
    --list               \
    --filter E           \
    --stats              \
    --progress           \
    --show-version       \
    --show-rc            \
    --compression lz4    \
    --exclude-caches     \
    $BACKUP_EXCLUDES     \
                         \
    ::'{hostname}-{now}' \
    $BACKUP_PATH

backup_exit=$?

info "Pruning repository"

# Use the `prune` subcommand to maintain 7 daily, 4 weekly and 6 monthly
# archives of THIS machine. The '{hostname}-' prefix is very important to
# limit prune's operation to this machine's archives and not apply to
# other machines' archives also:

borg prune                 \
    --verbose              \
    --list                 \
    --prefix '{hostname}-' \
    --progress             \
    --show-version         \
    --show-rc              \
    --keep-daily    7      \
    --keep-weekly   4      \
    --keep-monthly  6

prune_exit=$?

info "Compacting repository"

# Also do a compact after each prune, to free space
borg compact       \
    --verbose      \
    --progress     \
    --show-version \
    --show-rc

compact_exit=$?

# use highest exit code as global exit code
bp_exit=$(( backup_exit > prune_exit ? backup_exit : prune_exit ))
global_exit=$(( bp_exit > compact_exit ? bp_exit : compact_exit ))

if [ ${global_exit} -eq 1 ];
then
    info "Backup finished with a warning"
fi

if [ ${global_exit} -gt 1 ];
then
    info "Backup finished with an error"
fi

exit ${global_exit}

Run the following commands to create a new repository and export it's key. This key needs to be stored somewhere safe, along with the password you set in the first command.

borg init --encryption=repokey /path/to/repo
borg key export /path/to/repo key-file-name

I have multiple different borg repositories on various disks and machines. For each combination of backup input and output location, I created another script, calling the previous one with the proper values for location, excludes and repository password.

#!/bin/sh

# ~/bin/backup-root-extern

export BORG_REPO=/mnt/backup/borg-linux-root
export BORG_PASSPHRASE='REDACTED'

export BACKUP_PATH=/
export BACKUP_EXCLUDES="    \
    --exclude=/home/*       \
    --exclude=/var/cache/*  \
    --exclude=/var/tmp/*    \
    --exclude=/media/*      \
    --exclude=/mnt/*        \
    --exclude=/dev/*        \
    --exclude=/proc/*       \
    --exclude=/sys/*        \
    --exclude=/tmp/*        \
    --exclude=/run/*        \
    --exclude=/lost+found/* \
"

backup-borg

backup_exit=$?
exit ${backup_exit}

For convenience, I created yet another script that does all the backups into the same locations / machine.

#!/bin/sh

# ~/bin/backup-extern

sudo backup-root-extern
root_exit=$?

sudo backup-home-extern
home_exit=$?

sudo backup-data-extern
data_exit=$?

# use highest exit code as global exit code
rh_exit=$(( root_exit > home_exit ? root_exit : home_exit ))
global_exit=$(( rh_exit > data_exit ? rh_exit : data_exit ))

if [ ${global_exit} -eq 1 ];
then
    echo "One or Multiple Backups finished with a warning"
fi

if [ ${global_exit} -gt 1 ];
then
    echo "One or Multiple Backups finished with an error"
fi

exit ${global_exit}

Which can easily be combined with the following script, which tries to mount one of the external backup disks I rotate through.

#!/bin/bash

# ~/bin/mount-backup

( sudo mount -U xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt/backup -t ext4 ||
  sudo mount -U xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /mnt/backup -t ext4
) && echo ok

I can now simply plug in a backup disk and run "mount-backup && backup-extern" to run a new backup.

I'm also using these scripts for Cloud backups.