#!/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 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
      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
      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