From 4a4cd8e85cb0851f5d7056098f5112b9016794ad Mon Sep 17 00:00:00 2001 From: Einhard Leichtfuß Date: Mon, 2 Apr 2018 04:01:47 +0200 Subject: Initial commit --- rsync-backup.sh | 296 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100755 rsync-backup.sh (limited to 'rsync-backup.sh') diff --git a/rsync-backup.sh b/rsync-backup.sh new file mode 100755 index 0000000..3a592d7 --- /dev/null +++ b/rsync-backup.sh @@ -0,0 +1,296 @@ +#!/bin/bash +# +# rsync-backup.sh - a backup script using rsync. +# +# Copyright 2017, 2018 Einhard Leichtfuß +# +# This file is part of rsync-backup. +# +# rsync-backup is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# rsync-backup is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with rsync-backup. If not, see . +# + +RSYNC=rsync + +typeset -a rsync_args +typeset -a rsync_inc_args +typeset -A bakpath +typeset filter_file_all +typeset -A filter_file + +# Default arguments for rsync. +rsync_args=( + --recursive + --perms --times --owner --group + #--acls --xattrs + --links + --hard-links + --sparse + --timeout=60 + -vv + ) + +# Supplemental arguments for rsync; only used when an actual incremental +# update is performed, i.e. when --link-dest is used. +rsync_inc_args=( + --fuzzy --fuzzy + ) + +filter_file_all=/etc/rsync-backup/filter + +config=/etc/rsync-backup/backup.conf +source "$config" + +src=/home/respiranto/txt/bash/rsync-backup/src +dest=/home/respiranto/txt/bash/rsync-backup/dest + +typeset -l reply +typeset -i i n + +# RSYNC args. +# basic args: --recursive --perms --times --owner --group --links +# extra args: --sparse --acls --xattrs --hard-links --timeout=60 +# --fuzzy --fuzzy +# --filter="merge " +# likely: --compress +# --partial{,dir=DIR} +# --progress +# possibly: --devices --specials +# --max-size=SIZE-OF-FILE +# --one-file-system +# --log-file=FILE --log-file-format=FORMAT +# --human-readable +# unlikely: --omit-{dir,link}-times --update (!--inplace) --delete +# --exclude +# great: --link-dest=DIR (timestamps?) +# testing: --verbose --dry-run (-vn) +# interesting: --sockopts --itemize-changes --out-format=FORMAT +# --stats + +function main +{ + # In case of a non-matching wildcard, return nothing. + shopt -s nullglob + # Include dotfiles (except . and ..) in regex matching. + shopt -s dotglob + + get_args "$@" || return $? + + check_paths || return $? + + # Change the separation char to $'\n' to avoid confusion with filenames + # containing spaces (default is $' \t\n'). + IFS=$'\n' + + find_existing_backup_dirs || return $? + + perform_backup || return $? + + refresh_symlinks || return $? +} + + +function get_args +{ + if [ $# -eq 0 ] + then + echo "Usage: $0 []" + return 1 + fi + + dirpath="$(realpath "$1")" + + if [ $# -gt 1 ] + then + bakpath="$(realpath $2)" + elif test -v bakpath[$dirpath] + then + bakpath="${bakpath[$dirpath]}" + else + echo "Error: Backup path for $dirpath not configured." + return 1 + fi + + filter_args=() + if test -v filter_file_all -a -f "$filter_file_all" + then + filter_args=("--filter=merge ${filter_file_all}") + fi + if test -v filter_file[$dirpath] && test -f "${filter_file[$dirpath]}" + then + filter_args+=("--filter=merge ${filter_file[$dirpath]}") + fi +} + + +function check_paths +{ + # Test whether the dirs exist already. + if ! test -d "$dirpath" + then + echo "Source (\"$dirpath\") does not exist." + return 1 + fi + + if ! test -d "$bakpath" + then + echo "Target (\"$bakpath\") does not exist." + echo -n "Do you want do create it [y/N]? " + read reply + if [[ "$reply" != "y" && "$reply" != "yes" ]] + then + return 1 + fi + + if ! mkdir -p "$bakpath" + then + echo "Backup creation has failed." + echo "\"$bakpath\" could not be created." + return 1 + fi + fi +} + + +function find_existing_backup_dirs +{ + n=0 + for file in `ls "$bakpath"`; do + if test -d $bakpath/$file \ + && [[ "$file" =~ ^[0-9]{4}-[0-1][0-9]-[0-3][0-9]_[0-2][0-9]([0-5][0-9]){2}.backup$ ]] + then + dirs[n]="$file" + n+=1 + fi + done +} + + +# $1 path +# $2 error message: string +function clear_file +{ + if test -e "$1" -o -L "$1"; then + echo "It seems like the last backup process failed or you have created" \ + "the file \"$1\" manually." + echo -n "Do you want to delete it [y/N]? " + read reply + if [[ "$reply" == "y" || "$reply" == "yes" ]] + then + printf "Deleting" + if rm -rf "$1"; then + printf " - done\n" + return 0 + else + printf '\n%s\n' "$2" + printf 'The file \"%s\" could not be deleted.\n' "$1" + return 1 + fi + else + printf '%s\n' "$2" + printf '%s %s\n' "Please remove the above mentioned file manually" \ + "or run this script once more." + return 1 + fi + fi + return 0 +} + + +function perform_backup +{ + bakdate=`date +%Y-%m-%d_%H%M%S.backup` + finaldir="$bakpath/$bakdate" + tempdir="$bakpath/new.backup" + clear_file "$tempdir" "Backup creation has failed." || return 1 + clear_file "$finaldir" "Backup creation has failed." || return 1 + + # note: $n == ${#dirs[@]} + printf 'Starting backup nr. %u.\n' "$n" + if [ $n -eq 0 ] + then + # Perform first backup, not incremental. + $RSYNC "${rsync_args[@]}" \ + "${filter_args[@]}" \ + "$dirpath"/ "$tempdir" \ + || return 1 + else + # Perform incremental backup on the basis of the last. + $RSYNC "${rsync_args[@]}" \ + "${rsync_inc_args[@]}" --link-dest="${bakpath}/${dirs[n-1]}" \ + "${filter_args[@]}" \ + "$dirpath"/ "$tempdir" \ + || return 1 + fi + + mv "$tempdir" "$finaldir" || return 1 + printf '\nCreating of backup nr. %u succesfully executed.\n' "$n" +} + + +function refresh_symlinks +{ + linkdir="$bakpath/latest" + if test -L "$linkdir" + then + rm "$linkdir" + ln -s "$bakdate" "$linkdir" + elif clear_file "$linkdir" "Creation of softlink \"$linkdir\" failed." + then + ln -s "$bakdate" "$linkdir" + fi + + # by_number + numdir="$bakpath/by_number" + if ! test -d "$numdir" + then + if clear_file "$numdir" "Creating directory $numdir failed." + then + mkdir "$numdir" + else + return 1 + fi + fi + + for file in "$numdir"/* + do + if test -L "$file" + then + rm "$file" + else + if ! clear_file "$file" "Setting up numbered softlinks failed." + then + return 1 + fi + fi + done + + dirs[$n]="$bakdate" # Set last dir on current. + max_n_zeros=$((${#n} - 1)) + i=1 + while [ $i -le $max_n_zeros ] + do + zeros[i]=$(printf '%.s0' {$i..$max_n_zeros}) + i+=1 + done + + i=0 + while [ $i -le $n ] + do + ln -s "../${dirs[i]}" "$numdir/${zeros[${#i}]}$i" + i+=1 + done +} + + +main "$@" -- cgit v1.2.3