Skip to content

Instantly share code, notes, and snippets.

Created Apr 9, 2015
Embed
What would you like to do?
Rsync wrapper that snapshots an lvm volume and redirects rsync to use the snapshot
#!/bin/bash
# This script is a wrapper around rsync that will snapshot the volume
# being synced and 'serve' that snapshot instead of the live data.
# This prevents errors generated by moved/deleted files (eg: maildir
# new/ -> cur/)
# It assumes that all volumes being synced by this script are in
# /my/base/path/... and that the path represents the root of a
# volume. It would need more intelligence if this isn't true.
# Call the script from the machine that will be the destination of the
# data like:
# rsync --rsync-path=/path/to/this/script \
# remoteuser@remotehost:/my/base/path/foo/ /local/path/to/store/copy/
# Shouldn't be necessary, but make sure.
export PATH=$PATH:/bin:/usr/bin:/sbin:/usr/sbin
PROG=$(basename $0)
LOGFILE=/path/to/logs/rsync-snap.$(date +%Y%m%d).$$.log
MAILTO=you@yourdomain.org
HOST=$(hostname -s)
#build a new list of args for rsync, with an alternate source (eg: a snapshot)
ARGS=""
# This is the volume we want to snapshot. if we look at all args and
# don't find a volume we like, we'll bail.
snap=""
# So we don't have to do this for everything. All stderr to logfile.
exec 2>>${LOGFILE}
#log a few bookkeeping items in the case of error/mailout
echo "Date: $(date +%c)" >&2
echo "Host: ${HOST}" >&2
echo "Pid: $$ (grep '$PROG' /var/log/misc | grep $$)" >&2
echo "Logfile is: ${LOGFILE}" >&2
# On exit trap, mail the log, etc...
trap "cat ${LOGFILE} | mail -s 'Error during Rsync Snapshot' ${MAILTO}" EXIT
# Bail on error, now that we've got a trap setup...
set -e
function dosyslog {
logger -p user.notice -t $PROG "(pid: $$) $1"
}
function log {
dosyslog "$1"
echo "$(date +%c): $1" >&2
}
function logdie {
dosyslog "$1"
echo $1 >&2
# Since we have the EXIT handler in place, this should trigger mail
# message.
exit 1
}
# On with the show...
# Look at all the args and find one that matches volumes we want to
# snapshot instead of syning directly. Ignore all other args.
for arg in "$@"; do
case "$arg" in
# /tmp allows for testing without using critical volumes.
/my/base/path/*|/tmp*)
snap="$arg"
case "$arg" in
/tmp*)
devbase=/dev/${HOST}
base=temp
;;
*)
devbase=/dev/homes
base=$(basename $snap)
;;
esac
SNAPNAME=snap_${base}
# We need an individual name for the snap dev in case one volume
# bails before the mount and snapshot are cleaned up. this
# allows the script using this to still have a reasonable chance
# at syncing any remaining volumes.
SNAPDEV=${devbase}/${SNAPNAME}
ORIGDEV=${devbase}/${base}
# This is where we'll mount the snapshot that is actually taken.
SNAPMNT=/mnt/${SNAPNAME}
# Instead of just appending SNAPMNT, we munge the original
# volume name in a way that will preservce an existing trailing
# / if it was there.
ARGS="${ARGS}${SNAPMNT}"
# Requires bash v3+, but that's bog standard now.
if [[ $arg =~ .*/ ]]; then
ARGS="${ARGS}/"
fi
ARGS="${ARGS} "
;;
# Pass other args untouched.
*) ARGS="${ARGS}${arg} ";;
esac
done
# We're feeling helpful today.
cat << EOM >&2
If you recieve this by mail, it is quite likely that there is a
leftover mountpoint (${SNAPMNT}) or snapshotted Logical Volume
(${SNAPDEV}).
To remedy:
if [[ \$(mount | grep ${SNAPMNT}) ]]; then
umount ${SNAPMNT}
fi
if [ -L "${SNAPDEV}" ]; then
lvremove ${SNAPDEV}
fi
The following was logged during the rsync run:
EOM
# Log this before testing $snap so that we have more to work with in
# the log file.
log "Original Arguments: $*" >&2
if [ -z "$snap" ]; then
# It's _VERY_ important to die here as we don't want to set about
# creating/deleting snapshots of things we're unsure of!!
logdie "We didn't find a device to snapshot in the args passed to rsync."
fi
log "Modified Arguments: ${ARGS}"
# Our friendlier name for the dev is actually a symlink into
# /dev/mapper/...
if [ -L "${SNAPDEV}" ]; then
logdie "Snapshot device already exists. Aborting."
fi
if [ -n "$(mount | grep "${SNAPMNT}")" ]; then
logdie "Something is already mounted on ${SNAPMNT}. Aborting."
fi
[ -d "${SNAPMNT}" ] || mkdir -p ${SNAPMNT} >&2
# Any non-rsync stdout must be redirected or else the rsync
# transaction will fail (typically with protocol mismatch errors,
# etc).
lvcreate -L 10G -s -n ${SNAPNAME} ${ORIGDEV} >&2
log "Created lvm snapshot ${SNAPDEV} from ${ORIGDEV}"
mount ${SNAPDEV} ${SNAPMNT} >&2
log "Mounted lvm snapshot ${SNAPDEV} on ${SNAPMNT}."
# It is imperative that stdout for rsync _not_ be touched/redirected
# or the transaction will stall and eventually timeout.
rsync ${ARGS}
log "Finished rsync of snapshotted volume."
umount ${SNAPMNT} >&2
log "Unmounted lvm snapshot ${SNAPDEV} from ${SNAPMNT}."
lvremove -f ${SNAPDEV} >&2
log "Removed lvm snapshot ${SNAPDEV}."
# For debugging, enable errors at various points. Uncomment the
# command below to get a dump of full logs, etc.
# logdie "Forced error...there are still active snapshots and mounts."
# Turn off this trap so we don't get mailed every time.
trap - EXIT
rm -f ${LOGFILE}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment