Skip to content

Example: Backup raw disk images of a remote server using Borg and iblocksync

Daniel Rudolf edited this page Feb 22, 2019 · 7 revisions

Requirements

Notes

You can also backup your remote server manually by simply running the script. The script is split up in two phases: First, it syncs the raw disk image using iblocksync, and second, runs the actual backup using Borg. You can skip syncing the raw disk image by passing the --no-sync option, or skip the actual Borg backup by passing the --no-borg option.

Please note that syncing /dev/sda of a running server is probably not a good idea. You should rather backup a snapshot of your disk, e.g. using LVM or a file system with built-in snapshot functionality like btrfs or ZFS. You can use iblocksync's IBLOCKSYNC_TARGET_EXEC env variable to create a snapshot of your disk right before syncing.

Shell script

#!/bin/bash
set -eE -o pipefail

# SSH server
SSH_CONNECTION="my-server"

# iblocksync source device and target dir
IBLOCKSYNC_SOURCE="/dev/sda"
IBLOCKSYNC_TARGET="$HOME/Backup/My Server"

# Borg repository path
BORG_HOSTNAME="My-Server"
BORG_REPOSITORY="$HOME/Backup/My Server (Borg)"

# parse options
# be verbose, otherwise output nothing (except errors)
BORG="yes"
BORG_CREATE_PARAMS=( -v --stats --show-rc )
BORG_DELETE_PARAMS=( -v --stats --show-rc )
BORG_PRUNE_PARAMS=( -v --list --stats --show-rc )

IBLOCKSYNC="yes"
IBLOCKSYNC_PARAMS=( -v --progress none )

if tty --quiet; then
    BORG_CREATE_PARAMS+=( --progress )
    BORG_DELETE_PARAMS+=( --progress )
    IBLOCKSYNC_PARAMS=( -v --progress full )
fi

while [ $# -gt 0 ]; do
    case "$1" in
        "-q"|"--quiet")
            BORG_CREATE_PARAMS=()
            BORG_DELETE_PARAMS=()
            BORG_PRUNE_PARAMS=()
            IBLOCKSYNC_PARAMS=( --progress none )
            ;;

        "--no-sync")
            IBLOCKSYNC="no"
            ;;
            
        "--no-borg")
            BORG="no"
            ;;

        *)
            echo "Unknown option: $1" >&2
            exit 1
            ;;
    esac

    shift
done

# sync disk images using iblocksync
if [ "$IBLOCKSYNC" == "yes" ]; then
    # prepare iblocksync target dir
    if [ ! -e "$IBLOCKSYNC_TARGET" ]; then
        mkdir "$IBLOCKSYNC_TARGET"
    fi
    if [ ! -d "$IBLOCKSYNC_TARGET" ]; then
        echo "Invalid iblocksync target dir: $IBLOCKSYNC_TARGET" >&2
        exit 1
    fi

    # sync raw disk image
    # if syncing fails, retry up to 3 times
    for (( i=0 ; i < 3 ; i++ )); do
        IBLOCKSYNC_STATUS=0
        iblocksync "${IBLOCKSYNC_PARAMS[@]}" "$SSH_CONNECTION:$IBLOCKSYNC_SOURCE" "$IBLOCKSYNC_TARGET/$(basename "$IBLOCKSYNC_SOURCE").img" \
            || { IBLOCKSYNC_STATUS=$?; true; }

        [ $IBLOCKSYNC_STATUS -ne 0 ] || break
        sleep 300
    done

    [ $IBLOCKSYNC_STATUS -eq 0 ] || exit $IBLOCKSYNC_STATUS
fi

# create backup using Borg
if [ "$BORG" == "yes" ]; then
    # prepare Borg repository
    if [ ! -e "$BORG_REPOSITORY" ]; then
        mkdir "$BORG_REPOSITORY"
        borg init --encryption=repokey "$BORG_REPOSITORY"
    fi
    if [ ! -d "$BORG_REPOSITORY" ]; then
        echo "Invalid repository: $BORG_REPOSITORY" >&2
        exit 1
    fi

    # create backup (ignore warnings)
    BORG_CREATE_STATUS=0
    borg create "${BORG_CREATE_PARAMS[@]}" \
        --compression=lz4 \
        "$BORG_REPOSITORY"::"$BORG_HOSTNAME-{now:%Y-%m-%dT%H:%M:%S}" \
        "$IBLOCKSYNC_TARGET" \
        || { BORG_CREATE_STATUS=$?; true; }

    [ $BORG_CREATE_STATUS -lt 2 ] || exit $BORG_CREATE_STATUS

    # remove checkpoints (ignore warnings)
    BORG_DELETE_STATUS=0
    if [ $BORG_CREATE_STATUS -eq 0 ]; then
        BORG_CHECKPOINTS="$(borg list --short "$REPOSITORY" | grep -E '\.checkpoint(\.[0-9]+)?$' || true)"
        if [ -n "$BORG_CHECKPOINTS" ]; then
            xargs --max-args 1 --no-run-if-empty -I "%ARCHIVE%" -- \
                borg delete "${BORG_DELETE_PARAMS[@]}" "$REPOSITORY"::"%ARCHIVE%" \
                <<< "$BORG_CHECKPOINTS" \
                || { BORG_DELETE_STATUS=$?; true; }

            [ $BORG_DELETE_STATUS -lt 2 ] || exit $BORG_DELETE_STATUS
        fi
    fi

    # prune old backups (ignore warnings)
    # keep everything within two days + 12 daily backups (= 2 weeks),
    # 26 weekly backups (= 6 months), 24 monthly backups (= 2 years),
    # 10 yearly backups
    BORG_PRUNE_STATUS=0
    borg prune "${BORG_PRUNE_PARAMS[@]}" \
        "$BORG_REPOSITORY" --prefix "$BORG_HOSTNAME-" \
        --keep-within=2d --keep-daily=12 \
        --keep-weekly=26 --keep-monthly=24 \
        --keep-yearly=10 \
        || { BORG_PRUNE_STATUS=$?; true; }

    [ $BORG_PRUNE_STATUS -lt 2 ] || exit $BORG_PRUNE_STATUS

    if [ $BORG_CREATE_STATUS -eq 1 ] || [ $BORG_DELETE_STATUS -eq 1 ] || [ $BORG_PRUNE_STATUS -eq 1 ]; then
        exit 254
    fi
fi