Skip to content

Instantly share code, notes, and snippets.

Created April 9, 2015 20:41
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/f88a20b1e4f507ac702a to your computer and use it in GitHub Desktop.
Save anonymous/f88a20b1e4f507ac702a to your computer and use it in GitHub Desktop.
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