Skip to content

Instantly share code, notes, and snippets.

@RobertHue
Last active July 29, 2024 10:23
Show Gist options
  • Save RobertHue/698dca49fddc9ee017067a79402aed79 to your computer and use it in GitHub Desktop.
Save RobertHue/698dca49fddc9ee017067a79402aed79 to your computer and use it in GitHub Desktop.
Linux dash script to perform incremental backups using rsync
#!/bin/dash
# A script to perform incremental backups using rsync and a backup device
#
# Usage:
# - Place this script into /usr/local/sbin/
# - Adapt BACKUP_DEVICE variable to your backup device
# - Setup backup.sh as a automated job/task:
# - If you have a desktop that is not turned on all the time
# it is recommended to use anacron instead of cron.
#
# This is done by adding the following entry to /etc/anacrontab:
# "1 10 example.daily /usr/local/sbin/backup.sh > /var/log/backup.log"
# Also see: https://www.tecmint.com/cron-vs-anacron-schedule-jobs-using-anacron-on-linux/
#
# Notes:
# - This script is an adaption of the example from
# https://linuxconfig.org/how-to-create-incremental-backups-using-rsync-on-linux
# - Use duplicity if you want to backup in the network
# -e: exit on command fail
# -u: treat unset variables as an error
# -x: option causes bash to print each command before executing it. For debugging.
set -eux
readonly BACKUP_DEVICE="/dev/sdb1"
readonly SOURCE_DIR="/home/"
readonly BACKUP_DIR="/media/BACKUP"
readonly DATETIME="$(date '+%Y-%m-%d_%H:%M:%S')"
readonly BACKUP_PATH="${BACKUP_DIR}/${DATETIME}"
readonly LATEST_LINK="${BACKUP_DIR}/latest"
################### MAIN ###################
main() {
trap cleanup_error INT QUIT TERM
trap cleanup EXIT
if ! isPathMounted "${BACKUP_DIR}"; then
if [ ! -d "${BACKUP_DIR}" ]; then
mkdir "${BACKUP_DIR}"
fi
mount "${BACKUP_DEVICE}" "${BACKUP_DIR}"
if [ ! -d "${LATEST_LINK}" ]; then
mkdir "${LATEST_LINK}"
fi
fi
if isPathMounted "${BACKUP_DIR}"; then
echo "backing up ${SOURCE_DIR} into ${BACKUP_DIR}..."
################### BACKUP BY USING RSYNC ###################
# -a: archive - preserves the most important attributes of the source files
# -t: preserve time
# -v: verbose output
# --delete: files deleted from source are also deleted on destination
# --protect-args: no space-splitting; wildcard chars only
# --progress: shows progress during transfer
rsync --archive \
--verbose \
-t \
--delete \
--progress \
--checksum \
--backup \
--protect-args \
--link-dest "${LATEST_LINK}" \
--exclude="Downloads/" \
--exclude=".local/share/Trash/" \
--exclude=".cache/" \
"${SOURCE_DIR}" \
"${BACKUP_PATH}"
# feedback to the user
EXIT_STATUS=$?
if [ ${EXIT_STATUS} -eq 0 -o ${EXIT_STATUS} -eq 24 ]; then
echo "BACKUP done successfully! =)"
# update the link to the latest backup
rm "${LATEST_LINK}" # delete the link but keep the backup
cd "${BACKUP_DIR}"
ln -s "${DATETIME}" "${LATEST_LINK}"
cd ~
else
echo "BACKUP aborted with error code ${EXIT_STATUS}"
fi
fi
}
################### CHECK MOUNT ###################
# Checks whether path or device is mounted
# where: -r = --raw, -n = --noheadings, -o = --output
# returns: 0 = found, 1 = not found
isMounted () { findmnt -rno SOURCE,TARGET "$1" >/dev/null; } # path or device
isDevMounted () { findmnt -rno SOURCE "$1" >/dev/null; } # device only
isPathMounted () { findmnt -rno TARGET "$1" >/dev/null; } # path only
################### WAITING UMOUNT ###################
# Tries to umount with a retry-time of 10s until it was successful
waiting_umount() {
echo "unmounting $1..."
busy=true
while $busy; do
if isPathMounted "$1"; then
umount "$1"
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 0 ]; then
busy=false # umount successful
echo "unmounting successfully!"
else
echo -n '.' # output to show that the script is alive
sleep 10
fi
else
busy=false # not mounted
fi
done
}
################### FINALLY ###################
cleanup_error() {
# unset traps
trap - INT QUIT TERM
# feedback to the user
echo "BACKUP finished with status: $?"
# remove the faulty backup
if [ -d "${BACKUP_PATH}" ]; then
rm -rf "${BACKUP_PATH}"
fi
}
cleanup() {
# unset traps
trap - EXIT
# script cleanup here
waiting_umount "${BACKUP_DIR}"
if [ -d "${BACKUP_DIR}" ]; then
rmdir "${BACKUP_DIR}"
fi
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment