Skip to content

Instantly share code, notes, and snippets.

@michaeldye
Created March 20, 2020 16:45
Show Gist options
  • Save michaeldye/05af2c7bee523e691c24c3a19d07707a to your computer and use it in GitHub Desktop.
Save michaeldye/05af2c7bee523e691c24c3a19d07707a to your computer and use it in GitHub Desktop.
A simple differential backup script that uses hard links to manage trees of filesystem backup deltas (heavy lifting done by rsync)
#!/bin/bash -x
# Backup scheme description
#
# This script is intended to prevent data loss from hardware failure or
# accidental deletion by performing regular (perhaps nightly) differential
# backups against a max period full backup (perhaps weekly).
#
# 0 is newest backup, largest number is the oldest. Script keeps 0 to
# (N-1) differential backups on system, Nth is a full. This script was tested
# only with N_BACKUPS >= 3
N_BACKUPS=15
#
# TODO: 1. option to send content of a most recent backup after success to a remote system as a tar, optionally encrypted (streaming way? maybe a dynamic sshfs mount?)
#
if [ "$#" -lt 3 ]; then
echo "usage: $0 hostname src_path dst_dir [exclude_file_path]"
exit 1
fi
function rotate_all() {
local path_prefix=$1; shift
local ct_max=$((N_BACKUPS-1))
#TODO: dangerous! find a way to make this safer
sudo rm -rf "${path_prefix}$ct_max"
for ix in $(seq 1 $ct_max); do
tix=$((ct_max - ix))
sudo mv "${path_prefix}${tix}" "${path_prefix}$((tix+1))"
done
}
if [[ "$1" == 'localhost' ]] || [[ "$1" == "$(hostname)" ]]; then
SOURCE="$2"
else
SOURCE="$1:$2"
fi
# --itemize-changes
ARGS="--archive --delete --one-file-system --hard-links --inplace --numeric-ids"
if [ -e "$4" ]; then
ARGS="$ARGS --exclude-from "$4""
fi
# replace all /'s with _'s in path
DIR=${2//\//_}
# rem leading _
DIR=${DIR/_/}
PATH_PREFIX="$3/$1/$DIR-backup."
if [ -d "${PATH_PREFIX}0" ]; then
if [ "$(ls ${PATH_PREFIX}0/backup_completed_on*)" == "" ]; then
# dst dir exists but contains unsuccessful backup, remove it
echo "Backup in ${PATH_PREFIX}0 failed previously, deleting it and keeping older backups"
sudo rm -rf "${PATH_PREFIX}0"
else
# dst dir exists and it contains a successful backup, try to rotate
rotate_all "${PATH_PREFIX}"
ARGS="$ARGS --link-dest=${PATH_PREFIX}1/"
echo "Rotated backup dirs"
fi
else
mkdir -p $(dirname "$PATH_PREFIX")
fi
CMD="rsync $ARGS "$SOURCE" "${PATH_PREFIX}0""
echo "$CMD"
eval "$CMD"
touch "${PATH_PREFIX}0/backup_completed_on-$(date +%F_%T)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment