Incorporate changes for version 1.3.2
This commit is contained in:
485
dist/usr/local/bin/ribs
vendored
Executable file
485
dist/usr/local/bin/ribs
vendored
Executable file
@@ -0,0 +1,485 @@
|
||||
#!/bin/bash
|
||||
# Rod's Incremental Backup System
|
||||
|
||||
# Define constants
|
||||
|
||||
RIBS_VERSION="1.3.2"
|
||||
USER_CONF_DIR="$HOME/.ribs/conf-enabled"
|
||||
SYSTEM_CONF_DIR="/etc/ribs/conf-enabled"
|
||||
|
||||
# Define functions
|
||||
|
||||
function find_configs() {
|
||||
# Search for config files in the default places and build a list
|
||||
# Takes one optional argument, the name of a config file.
|
||||
# Defines the variable conf_files, a list of conf files
|
||||
|
||||
conf_files=()
|
||||
# First check user's conf directory, then the system conf directory
|
||||
if [[ $1 != "" ]]
|
||||
then
|
||||
conf_files+=("$1")
|
||||
elif ls -1 "$USER_CONF_DIR"/* >/dev/null 2>&1
|
||||
then
|
||||
for conf_file in "$USER_CONF_DIR"/*
|
||||
do
|
||||
conf_files+=("$conf_file")
|
||||
done
|
||||
elif ls -1 "$SYSTEM_CONF_DIR"/* >/dev/null 2>&1
|
||||
then
|
||||
for conf_file in "$SYSTEM_CONF_DIR"/*
|
||||
do
|
||||
conf_files+=("$conf_file")
|
||||
done
|
||||
else
|
||||
echo "Couldn't find any config files in"
|
||||
echo "$USER_CONF_DIR or $SYSTEM_CONF_DIR,"
|
||||
echo "and one was not specified on the command line."
|
||||
echo "Please refer to the file /etc/ribs/conf-available/ribs.conf.sample"
|
||||
echo "and create one."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function read_config() {
|
||||
# Read the configuration file
|
||||
|
||||
# Make sure the provided config file is readable.
|
||||
if [[ ! -r $CONF_FILE ]]
|
||||
then
|
||||
echo -n "Reading configuration file $CONF_FILE"
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
echo "The config file $CONF_FILE does not exist or is not readable."
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
echo -n "Reading configuration file... "
|
||||
if ! source "$CONF_FILE"
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
echo "An error was encountered while reading the configuration file"
|
||||
echo "$CONF_FILE."
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
|
||||
# Add slash to the beginning of $BACKUP_SUBDIR if it's not there
|
||||
if [[ "$(echo "$BACKUP_SUBDIR" | cut -c1)" != "/" ]]
|
||||
then
|
||||
BACKUP_SUBDIR="/$BACKUP_SUBDIR"
|
||||
fi
|
||||
# Remove slash from the end of $BACKUP_SUBDIR if it's there
|
||||
if [[ "$(echo "$BACKUP_SUBDIR" | rev | cut -c1)" = "/" ]]
|
||||
then
|
||||
numchars=$(echo "$BACKUP_SUBDIR" | wc -m);let "numchars-=2"
|
||||
BACKUP_SUBDIR=$(echo "$BACKUP_SUBDIR" | cut -c-$numchars)
|
||||
fi
|
||||
}
|
||||
|
||||
function generate_excludes() {
|
||||
# Generate exclude array
|
||||
declare -a excludes
|
||||
if [[ $EXCLUDE_LIST != "" ]]
|
||||
then
|
||||
for pattern in $EXCLUDE_LIST
|
||||
do
|
||||
echo "excluding pattern $pattern"
|
||||
excludes+=' --exclude="$pattern" '
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
function mount_storage() {
|
||||
# Takes one argument, rw, ro, or u
|
||||
mount_mode=$1
|
||||
if [[ $mount_mode = "ro" ]]
|
||||
then
|
||||
mode_text="read-only"
|
||||
elif [[ $mount_mode = "rw" ]]
|
||||
then
|
||||
mode_text="read-write"
|
||||
elif [[ $mount_mode = "u" ]]
|
||||
then
|
||||
mode_text="unmounted"
|
||||
else
|
||||
echo "Program error: invalid option \"$mode_text\" passed to mount_storage()"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Mount the backup drive
|
||||
# First, check to see if backup drive partition is mounted
|
||||
if ! mount|grep -q "$BACKUP_ROOT"
|
||||
then
|
||||
remount_option="remount,"
|
||||
else
|
||||
remount_option=""
|
||||
fi
|
||||
|
||||
if [[ $mount_mode = "u" ]]
|
||||
then
|
||||
# Unmount drive
|
||||
echo -n "Unmounting backup storage... "
|
||||
if ! umount "$BACKUP_ROOT"
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
echo "An error was encountered while attempting to unmount $BACKUP_ROOT."
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
else
|
||||
# Mount the drive in the desired mode
|
||||
echo -n "Mounting backup storage $mode_text... "
|
||||
if [[ $MOUNT_PARAMETERS != "" ]]
|
||||
then
|
||||
if ! mount "$MOUNT_PARAMETERS" "$BACKUP_ROOT" -o"$remount_option""$mount_mode"
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
echo "An error was encountered while attempting to mount the backup storage"
|
||||
echo "on $BACKUP_ROOT in $mode_text mode. Check MOUNT_PARAMETERS in your"
|
||||
echo "configuration file."
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
else
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
echo "Unable to mount backup storage."
|
||||
echo "No MOUNT_PARAMETERS were provided in config file."
|
||||
echo "Aborting."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function list_backups() {
|
||||
# Generate a list of backups based on storage type
|
||||
if [[ $STORAGE_TYPE = "REMOTE" ]]
|
||||
then
|
||||
ssh $REMOTE_USER_HOST cd $BACKUP_ROOT$BACKUP_SUBDIR && ls -1d ????-??-??*
|
||||
else
|
||||
cd $BACKUP_ROOT$BACKUP_SUBDIR && ls -1d ????-??-??*
|
||||
fi
|
||||
}
|
||||
|
||||
function delete_backup() {
|
||||
# Delete a backup. Takes one argument, the date directory to delete
|
||||
echo -n "Removing old backup $1... "
|
||||
remove_fail=false
|
||||
if [[ $STORAGE_TYPE = "REMOTE" ]]
|
||||
then
|
||||
if ! ssh $REMOTE_USER_HOST rm -r $BACKUP_ROOT$BACKUP_SUBDIR/$1
|
||||
then
|
||||
remove_fail=true
|
||||
fi
|
||||
else
|
||||
if ! rm -r $BACKUP_ROOT$BACKUP_SUBDIR/$1
|
||||
then
|
||||
remove_fail=true
|
||||
fi
|
||||
fi
|
||||
if $remove_fail
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
}
|
||||
|
||||
function run_backup() {
|
||||
CONF_FILE="$1"
|
||||
|
||||
echo "Run started with configuration file:"
|
||||
echo "$CONF_FILE"
|
||||
date "+%a %b %d %H:%M:%S %Z %Y"
|
||||
echo ""
|
||||
|
||||
# Read config file
|
||||
read_config
|
||||
|
||||
# Generate exclude array
|
||||
generate_excludes
|
||||
|
||||
# Mount offline storage if required
|
||||
if [[ $STORAGE_TYPE == "OFFLINE" ]]
|
||||
then
|
||||
mount_storage rw
|
||||
fi
|
||||
|
||||
# Today's date
|
||||
current_datetime=$(date +%Y-%m-%dT%H:%m:%S)
|
||||
echo "Today's date: $current_datetime"
|
||||
|
||||
# Previous backup's date
|
||||
if $list_cmd >/dev/null 2>&1
|
||||
then
|
||||
previous_datetime=$($list_cmd | tail -n1)
|
||||
else
|
||||
# there are no other date directories
|
||||
previous_datetime=""
|
||||
fi
|
||||
if [[ $previous_datetime == "" ]]
|
||||
then
|
||||
echo "No previous backup date."
|
||||
fi
|
||||
|
||||
# set target and link destination directories and day to remove
|
||||
if [[ $STORAGE_TYPE == "REMOTE" ]]
|
||||
then
|
||||
TRG="$REMOTE_USER_HOST:$BACKUP_ROOT$BACKUP_SUBDIR/$current_datetime/"
|
||||
LNK="$BACKUP_ROOT$BACKUP_SUBDIR/$previous_datetime/"
|
||||
if ssh "$REMOTE_USER_HOST" test -e "$LNK"
|
||||
then
|
||||
link_dest_exists=true
|
||||
else
|
||||
link_dest_exists=false
|
||||
fi
|
||||
else
|
||||
TRG="$BACKUP_ROOT$BACKUP_SUBDIR/$current_datetime/"
|
||||
LNK="$BACKUP_ROOT$BACKUP_SUBDIR/$previous_datetime"
|
||||
if [[ -e "$LNK" ]]
|
||||
then
|
||||
link_dest_exists=true
|
||||
else
|
||||
link_dest_exists=false
|
||||
fi
|
||||
fi
|
||||
|
||||
# rsync options
|
||||
for element in $excludes
|
||||
do
|
||||
echo "excludes array contains $element"
|
||||
done
|
||||
if ! $link_dest_exists
|
||||
then
|
||||
OPT="-s -ah --delete ${excludes[@]}"
|
||||
else
|
||||
OPT="-s -ah --delete --link-dest=$LNK ${excludes[@]}"
|
||||
fi
|
||||
if $DRY_RUN
|
||||
then
|
||||
OPT="$OPT -n"
|
||||
fi
|
||||
|
||||
if [[ $STORAGE_TYPE != "REMOTE" ]]
|
||||
then
|
||||
# -------- Run some tests -----
|
||||
testtimestamp=$(date +%Y%m%d%H%M%S%N)
|
||||
test_filename="RIBS${testtimestamp}"
|
||||
test_linkname="RIBS${testtimestamp}hardlink"
|
||||
|
||||
# Make sure the backup root is writeable
|
||||
echo -n "Testing backup location for writeability... "
|
||||
if ! touch "$BACKUP_ROOT"/"$test_filename" >/dev/null 2>&1
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
echo "Aborting."
|
||||
if [[ $STORAGE_TYPE = "OFFLINE" ]]
|
||||
then
|
||||
if $REMOUNT_RO
|
||||
then
|
||||
mount_storage ro
|
||||
else
|
||||
mount_storage u
|
||||
fi
|
||||
fi
|
||||
exit 1
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
|
||||
# Make sure the backup root supports hard links
|
||||
echo -n "Testing backup location for hard link support... "
|
||||
if ! ln "$BACKUP_ROOT"/"$test_filename" "$BACKUP_ROOT"/"$test_linkname" >/dev/null 2>&1
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
echo "Aborting."
|
||||
rm -f "$BACKUP_ROOT"/"$test_filename"
|
||||
if [[ $STORAGE_TYPE == "OFFLINE" ]]
|
||||
then
|
||||
if $REMOUNT_RO
|
||||
then
|
||||
mount_storage ro
|
||||
else
|
||||
mount_storage u
|
||||
fi
|
||||
fi
|
||||
exit 1
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
|
||||
rm -f "$BACKUP_ROOT"/"$test_filename"
|
||||
rm -f "$BACKUP_ROOT"/"$test_linkname"
|
||||
fi
|
||||
|
||||
# -------- Start backup -------
|
||||
# Create backup subdir if it's not there
|
||||
if [[ $STORAGE_TYPE == "REMOTE" ]]
|
||||
then
|
||||
if ! $DRY_RUN && [[ $BACKUP_SUBDIR != "" ]] && ! ssh "$REMOTE_USER_HOST" test -d "$BACKUP_ROOT$BACKUP_SUBDIR"
|
||||
then
|
||||
ssh "$REMOTE_USER_HOST" mkdir -p "$BACKUP_ROOT""$BACKUP_SUBDIR"
|
||||
fi
|
||||
else
|
||||
if ! $DRY_RUN && [[ $BACKUP_SUBDIR != "" ]] && ! test -d "$BACKUP_ROOT$BACKUP_SUBDIR"
|
||||
then
|
||||
mkdir -p "$BACKUP_ROOT""$BACKUP_SUBDIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Starting incremental backup."
|
||||
if $DRY_RUN
|
||||
then
|
||||
echo "This will be a dry run. Nothing will actually be backed up."
|
||||
sleep 10
|
||||
fi
|
||||
# Execute the backup
|
||||
echo -n "running rsync backup with command rsync $OPT $SRC $TRG... "
|
||||
if ! bash -c "rsync $OPT $SRC $TRG"
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
echo "An error was encountered while attempting to backup. No old"
|
||||
echo "backups will be removed."
|
||||
if [[ $STORAGE_TYPE == "OFFLINE" ]]
|
||||
then
|
||||
if $REMOUNT_RO
|
||||
then
|
||||
mount_storage ro
|
||||
else
|
||||
mount_storage u
|
||||
fi
|
||||
fi
|
||||
exit 1
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
|
||||
# Delete the specified backups, if it exists
|
||||
limit_date=$(date -d "$current_datetime $DAY_LMT days ago")
|
||||
limit_date_sec=$(date -d "$limit_date" +%s)
|
||||
|
||||
if $DRY_RUN
|
||||
then
|
||||
echo "This is a dry run, so no old backups will be removed."
|
||||
for date_dir in $(list_backups)
|
||||
do
|
||||
date_dir_sec=$(date -d "$date_dir" +%s)
|
||||
if [[ $date_dir_sec < $limit_date_sec ]]
|
||||
then
|
||||
echo "We would be removing $date_dir"
|
||||
fi
|
||||
done
|
||||
else
|
||||
for date_dir in $(list_backups)
|
||||
do
|
||||
date_dir_sec=$(date -d "$date_dir" +%s)
|
||||
if [[ $date_dir_sec < $limit_date_sec ]]
|
||||
then
|
||||
delete_backup $date_dir
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Clean up
|
||||
echo -n "Syncing disks... "
|
||||
if ! sync
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
|
||||
# Update capacity file if using offline storage
|
||||
if [[ $STORAGE_TYPE == "OFFLINE" ]]
|
||||
then
|
||||
echo -n "Updating capacity file... "
|
||||
if ! df -h |grep "$BACKUP_ROOT" > "$BACKUP_ROOT"/capacity.txt
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Unmount or remount offline storage as required
|
||||
if [[ $STORAGE_TYPE == "OFFLINE" ]]
|
||||
then
|
||||
if $REMOUNT_RO
|
||||
then
|
||||
echo -n "Remounting backup storage read-only... "
|
||||
if ! mount_storage ro
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
else
|
||||
echo -n "Unmounting backup storage... "
|
||||
if ! mount_storage u
|
||||
then
|
||||
echo -e "\e[31mFAILED\e[0m"
|
||||
else
|
||||
echo -e "\e[32mOK\e[0m"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "Backup complete."
|
||||
echo ""
|
||||
|
||||
echo "Run ended with configuration file:"
|
||||
echo "$CONF_FILE"
|
||||
date "+%a %b %d %H:%M:%S %Z %Y"
|
||||
echo ""
|
||||
}
|
||||
|
||||
function show_help() {
|
||||
echo "Usage: $0 [-h|--help] [config_file]"
|
||||
echo ""
|
||||
echo "Run an incremental backup using a config file specified."
|
||||
echo "If no config file is specified, search for and read all config"
|
||||
echo "files in the user's config directory ($USER_CONF_DIR)."
|
||||
echo "If none are found there, search for and read all config files in"
|
||||
echo "the system config directory ($SYSTEM_CONF_DIR)."
|
||||
echo ""
|
||||
echo "Please refer to the sample config file, "
|
||||
echo "/etc/ribs/conf-available/ribs.conf.sample for an explanation"
|
||||
echo "of parameters."
|
||||
echo ""
|
||||
echo " Options:"
|
||||
echo ""
|
||||
echo " -h|--help: Display this help message."
|
||||
echo ""
|
||||
}
|
||||
|
||||
|
||||
function main() {
|
||||
# Begin execution
|
||||
echo "RIBS $RIBS_VERSION"
|
||||
echo "Rod's Incremental Backup System"
|
||||
echo ""
|
||||
|
||||
case "$arg" in
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
find_configs "$arg"
|
||||
;;
|
||||
esac
|
||||
|
||||
for each_conf in "${conf_files[@]}"
|
||||
do
|
||||
run_backup "$each_conf"
|
||||
done
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user