#!/bin/sh # # 2014/11/14 - Initial script creation # # Based on snapxfer script by pds from zpool.org # https://zpool.org/zfs-snapshots-and-remote-replication/ # # Also main use zfSnap (https://github.com/zfsnap/zfsnap/wiki/zfSnap) # Must take the 1.x version (tree legacy). Too many changes in the 2.0.0 beta. # Local ZFS filesystems that will have a snapshot DATASETS="datastore/vm" # Destination host for snapshot replication # The user must have enought rights to use `zfs command` DHOST="root@192.168.0.100" # Output logfile LOGFILE="/var/log/snapsend/snapsend.log" # Temp file for mail content TEMP_MAIL="/var/log/snapsend/temp.mail" # Tools that help implement snapshot logic (and transfer soon according the pull-requests) ZSNAP="/usr/local/bin/zfSnap.sh" # Administrator's email MAILADMIN="admin1@localhost,admin2@localhost" # List of all ZFS snapshot SNAP_LIST='' REMOTE_SNAP_LIST='' # The prefix to add to the previous snapshot's name which is now useless PREFIXSNAP='done_' ###################################################################### # Functions # ###################################################################### # Returns 0 if snapshot exists SnapExists() { SNAP_LIST="${SNAP_LIST:-`zfs list -H -o name -t snapshot`}" local i for i in $SNAP_LIST; do [ "$1" = "$i" ] && return 0 done return 1 } # Returns 0 if snapshot exists on the remote host RemoteSnapExist() { REMOTE_SNAP_LIST="${REMOTE_SNAP_LIST:-`ssh ${1} zfs list -H -o name -t snapshot`}" local i for i in $REMOTE_SNAP_LIST; do [ "$2" = "$i" ] && return 0 done return 1 } ###################################################################### # Main logic # ###################################################################### interval=$1 usage() { if [ "X${interval}" = "X" ]; then printf 'Usage: %s (async|hourly|daily|weekly|purge)\n' $(basename $0) printf '\n' printf '* Asynchronous DR snapshots are kept for 1 hour\n' printf '* Hourly snapshots are kept for 1 day\n' printf '* Daily snapshots are kept for one week\n' printf '* Weekly snapshots are kept for one month\n' printf '\n' exit 1 fi } if [ ! -f "${LOGFILE}" ]; then mkdir -p $(dirname ${LOGFILE}) touch "${LOGFILE}" touch "${TEMP_MAIL}" fi case "${interval}" in 'async' ) for dset in $DATASETS do # Take snapshots for asynchronouss DR purposes $ZSNAP -v -s -S -a 1h $dset >> $LOGFILE # Get the last snapshot name on localhost LOCALSNAP=$(zfs list -H -o name -t snapshot -r ${dset} | tail -n1 | cut -d@ -f2) #printf 'Local snapshot: %s\n' ${LOCALSNAP} # Get the last snapshot name on remote host $DHOST DHOSTSNAP=$(ssh ${DHOST} zfs list -H -o name -t snapshot -r ${dset} | tail -n1 | cut -d@ -f2) #printf 'Remote snapshot: %s\n' ${DHOSTSNAP} # Test if $DHOSTSNAP exist on the local system # Recompose the full snapshot name with $dset@$DHOSTSNAP if SnapExists "${dset}@${DHOSTSNAP}"; then #printf '%s exist on the local system\n' ${DHOSTSNAP} printf '%s@%s will be send to %s to replace the old snapshot: %s' ${dset} ${LOCALSNAP} ${DHOST} ${DHOSTSNAP} >> $LOGFILE # Send the snapshot the remote host # zfs send -I datastore/vm@2014-11-17_11.26.22--1h datastore/vm@2014-11-18_14.19.10--1h | ssh nec02.ipr.univ-rennes1.fr zfs recv datastore/vm zfs send -I "${DHOSTSNAP}" "${dset}@${LOCALSNAP}" | ssh "${DHOST}" zfs recv "${dset}" printf ' ... DONE\n' >> $LOGFILE else # Mail admin printf 'ERROR Connection SSH or snapshot >%s< does not exist on the local system\n' ${DHOSTSNAP} | mail ${MAILADMIN} -s "snapsend_error $(hostname -f)" printf 'ERROR snapshot %s for %s does not exist on the local system\n' ${DHOSTSNAP} ${dset} >> $LOGFILE exit 1 fi # Test if the snapshot was successfully receive on the remote host if RemoteSnapExist "${DHOST}" "${dset}@${LOCALSNAP}"; then printf 'SUCCESS: the snapshot %s for %s exist on the remote host (%s)\n' ${LOCALSNAP} ${dset} ${DHOST} >> $LOGFILE # Rename the useless snapshot for the next purge zfs rename ${dset}@${DHOSTSNAP} ${dset}@${PREFIXSNAP}${DHOSTSNAP} else # Mail admin printf 'ERROR snapshot %s for %s does not exist on the remote host (%s)\n' ${LOCALSNAP} ${dset} ${DHOST} | mail ${MAILADMIN} -s "snapsend_error $(hostname -f)" printf 'ERROR snapshot %s for %s does not exist on the remote host (%s)\n' ${LOCALSNAP} ${dset} ${DHOST} >> $LOGFILE exit 1 fi done printf '\n' >> $LOGFILE ;; 'hourly' ) # take snapshots, keep for one day for dset in $DATASETS do $ZSNAP -v -s -S -a 1d $dset >> $LOGFILE done printf '\n' >> $LOGFILE ;; 'daily' ) # take snapshots, keep for one week for dset in $DATASETS do $ZSNAP -v -s -S -a 1w $dset >> $TEMP_MAIL done # Purge snapshots according to TTL $ZSNAP -v -s -S -d -p ${PREFIXSNAP} >> $TEMP_MAIL printf '\n' >> $TEMP_MAIL # Send email to admin cat $TEMP_MAIL | mail ${MAILADMIN} -s "Daily snapshot $(hostname -f)" # Send the previous log to the main logfile cat ${TEMP_MAIL} >> ${LOGFILE} rm -f ${TEMP_MAIL} ;; 'weekly' ) # take snapshots, keep for one month for dset in $DATASETS do $ZSNAP -v -s -S -a 1m $dset >> $LOGFILE done printf '\n' >> $LOGFILE ;; 'purge' ) # purge snapshots according to TTL $ZSNAP -v -s -S -d ;; * ) usage ;; esac exit 0