Last active
July 29, 2024 10:23
-
-
Save RobertHue/698dca49fddc9ee017067a79402aed79 to your computer and use it in GitHub Desktop.
Linux dash script to perform incremental backups using rsync
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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