remote-backup/remote-backup
2012-02-09 17:08:34 +01:00

292 lines
6.3 KiB
Bash
Executable File

#!/bin/bash
# ----------------------------------------------------------------------
# mikes handy rotating-filesystem-snapshot utility
# ----------------------------------------------------------------------
# this needs to be a lot more general, but the basic idea is it makes
# rotating backup-snapshots of /home whenever called
# ----------------------------------------------------------------------
#unset PATH # suggestion from H. Milz: avoid accidental use of $PATH
# ------------- system commands used by this script --------------------
###BEGIN: generic tool checker
# FIXME: To reuse this function, be sure to edit this line (space separated
# list).
needed_tools="id echo mount rm mv cp touch rsync"
#
# One shall not need edit anything below this line
###
checktools() {
# This functions attempts to be generic enough to be reused in any program
# that needs to check that specific tools are present on the system
# and can be found in the PATH
var=$(echo $1 | tr '[:lower:]' '[:upper:]')
echo -n "# Looking for $1... "
local_var="$(which $1)"
eval $var="$local_var"
if [ -z "$local_var" ]
then
echo 'NOT FOUND!'
NOTFOUND="$NOTFOUND$1 "
DIE=1
else
echo "$local_var"
FOUND="$FOUND$1 "
fi
unset local_var
}
for tool in $needed_tools
do
checktools "$tool"
done
echo
echo "# Found $FOUND"
echo
if [ -n "$DIE" ]
then
echo "Some tools were not found, please check your installation" >&2
echo "Needed tools: $needed_tools" >&2
echo -n "Not found:" >&2
for tool in $NOTFOUND
do
echo -n " $tool" >&2
done
echo '!' >&2
exit 127
fi
###END: generic tool checker
while getopts 'e:h' opt
do
case $opt in
e)
EMERG="$OPTARG"
;;
h)
help
;;
esac
done
declare -a failedBackups
declare -A failedWith
failRsync() {
nameFailed="$1"
code=$2
path="$3"
failedBackups+=( "$name" )
if [ -d "$3" ]
then
touch "$3/unfinished.remote-backup"
failedWith["$name"]="RSYNC_PARTIAL $code"
else
failedWith["$name"]="RSYNC_FAILED $code"
fi
}
openLog() {
exec 3>&1
exec 1>> /var/log/remote-backup
exec 2>&1
}
doBackup() {
config="$1"
if [ ! -e "$config" ]
then
echo "No backups defined, dying." >&2
echo "No backups defined, dying." >&3
exit 128
fi
source /etc/remote-backup/defaults.conf
source "$config"
echo "Debut de backup de ${REMOTE_LOCATION} vers ${SNAPSHOT_RW}/${NAME}/ a $(date +"%H:%M, le %d/%m/%Y")"
if [[ "$REMOUNT" == "true" ]]
then
# attempt to remount the RW mount point as RW; else abort
$MOUNT -o remount,rw "$MOUNT_DEVICE" "$SNAPSHOT_RW" ;
if (( $? ))
then
echo "snapshot: could not remount $SNAPSHOT_RW readwrite" >&2
failedBackups+=( "$NAME" )
failedReason["$NAME"]="REMOUNT_RW $MOUNT_DEVICE"
return 1
fi
fi
if [ ! -d "$SNAPSHOT_RW/$NAME" ]
then
mkdir -p "$SNAPSHOT_RW/$NAME"
fi
if [[ "$2" == emerg ]]
then
runBackup emerg.$(date +%Y%m%d%H%M)
else
rotateBackup
runBackup daily.0
fi
# now remount the RW snapshot mountpoint as readonly
if [[ "$REMOUNT" == "true" ]]
then
$MOUNT -o remount,ro "$MOUNT_DEVICE" "$SNAPSHOT_RW"
if (( $? ))
then
echo "snapshot: could not remount $SNAPSHOT_RW readonly" >&2
fi
fi
echo "Fin de backup de ${REMOTE_LOCATION} vers ${SNAPSHOT_RW}/${NAME}/ a $(date +"%H:%M, le %d/%m/%Y")"
echo '-----------------------------------------------------------------------'
}
rotateBackup() {
# rotating snapshots
if [ -f "$SNAPSHOT_RW/$NAME/daily.0" ]
then
echo "Previous backup failed, skipping rotation."
rm "$SNAPSHOT_RW/$NAME/daily.0"
elif [ -f "$SNAPSHOT_RW/$NAME/daily.0/unfinished.remote-backup" ]
then
echo "Previous backup interrupted, skipping rotation."
rm "$SNAPSHOT_RW/$NAME/daily.0/unfinished.remote-backup"
else
# step 1: delete the oldest snapshot, if it exists:
if [ -d "$SNAPSHOT_RW/$NAME/daily.${MAX_ROTATE}" ]
then
$RM -rf "$SNAPSHOT_RW/$NAME/daily.${MAX_ROTATE}"
fi
# step 2: shift the snapshots(s) back by one, if they exist
for (( backup_num=$MAX_ROTATE ; backup_num >= 0 ; backup_num-- ))
do
if [ -d "$SNAPSHOT_RW/$NAME/daily.${backup_num}" ]
then
$MV -f "$SNAPSHOT_RW/$NAME/daily.${backup_num}" \
"$SNAPSHOT_RW/$NAME/daily.$(( backup_num + 1 ))"
fi
done
fi
}
runBackup() {
# step 3: rsync from the system, linking to latest snapshot if files are
# identical.
RSYNC_OPTS="-a --exclude-from=$EXCLUDES --bwlimit=$BWLIMIT"
if [[ $2 == PROTOCOLMISMATCH ]]
then
proto29=1
MAX_ROTATE=1
fi
for (( backup_num=0 ; backup_num <= MAX_ROTATE ; backup_num++ ))
do
if [ -d "$SNAPSHOT_RW/$NAME/daily.$backup_num" ]
then
RSYNC_OPTS="$RSYNC_OPTS --link-dest=$SNAPSHOT_RW/$NAME/daily.$backup_num"
fi
done
for old_backup in "$SNAPSHOT_RW/$NAME/emerg".*
do
if [ -d "$old_backup" ]
then
RSYNC_OPTS="$RSYNC_OPTS --link-dest=$old_backup"
fi
done
if (( TIMEOUT ))
then
RSYNC_OPTS="$RSYNC_OPTS --timeout=$TIMEOUT"
else
RSYNC_OPTS="$RSYNC_OPTS --timeout=1800"
fi
$RSYNC \
$RSYNC_OPTS \
$ADDITIONAL_RSYNC_OPTS \
"${REMOTE_LOCATION}" \
"$SNAPSHOT_RW/$NAME/$1"
returncode=$?
case $returncode in
0)
:
;;
2)
if (( proto29 ))
then
fail "$NAME" PROTO_MISMATCH
else
echo "Rsync protocol mismatch, trying with only one --link-dest"
runBackup "$1" PROTOCOLMISMATCH
fi
;;
*)
failRsync "$NAME" $returncode "$SNAPSHOT_RW/$NAME/$1"
;;
esac
# step 5: update the mtime of daily.0 to reflect the snapshot time
$TOUCH "$SNAPSHOT_RW/$NAME/$1"
# and thats it
unset proto29
}
# make sure we're running as root
if [ ! $UID -eq 0 ]
then
echo "Sorry, must be root. Exiting..."
exit
fi
openLog
if [ -n "$EMERG" ]
then
doBackup /etc/remote-backup/"${EMERG//\//_}"/config emerg
else
for config in /etc/remote-backup/*/config
do
doBackup "$config"
done
fi
if (( ${#failedBackups} ))
then
cat <<-EOF
Some backups exited non-zero.
See the logfile for more detail.
EOF
for backup in ${failedBackups[@]}
do
echo -n "$backup failed: "
read info more_info <<< "${failedWith["$backup"]}"
case $info in
RSYNC_PARTIAL)
echo "Partial transfer, will *NOT* get rotated - rsync returned $more_info."
;;
RSYNC_FAILED)
echo "No files transferred - rsync returned $more_info."
;;
PROTO_MISMATCH)
echo "No files transferred - Protocol mismatch - Upgrade rsync daemon"
;;
REMOUNT_RW)
echo "Target device $more_info could not be mounted ReadWrite."
;;
*)
echo "$info $moreinfo"
;;
esac
done
fi