#!/bin/bash
#
# bacula_warn_on_zero.sh
#
# ------------------------------------------------------------------------------
#
# waa - 20171116 - First release
#                  Send an email alert when files or bytes are zero for backup
#                  jobs that have terminated "OK", or "OK -- with warnings"
#
# waa - 20171116 - Change log moved to bottom of script.
#
# ------------------------------------------------------------------------------
#
# Copyright (c) 2017, William A. Arlofski waa-at-revpol-dot-com
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1.  Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# 2.  Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# ------------------------------------------------------------------------------


# System variables
# ----------------
server="Bacula Server"
admin="bacula-admin@example.com"
bcbin="/usr/sbin/bconsole"
bcconfig="/etc/bacula/include/bconsole.conf"
sendmail="/usr/sbin/sendmail"
sendjoblog="yes"	# Include the full joblog in email? Requires "catalog = all" in Messages {} resource.
exit_non_zero="no"	# If there are zero files or zero bytes, should the script exit with non-zero error level?


# --------------------------------------------------
# Nothing should need to be modified below this line
# --------------------------------------------------


# =================================================================================
# -----------------------------------------------
# Example Backup Job snippet to call this script:
# -----------------------------------------------
#
# Job {
# 	Name = Catalog
# 	Client = bacula-fd
# 	JobDefs = Defaults
# 	FileSet = Catalog
# 	Level = Incremental
# 	Schedule = Catalog
# 	Priority = 20
# 	WriteBootstrap = "/var/lib/bacula/bootstrap_files/%n_%i.bsr"
#
# 	RunScript {
# 		RunsWhen = after 
# 		RunsOnClient = no
#     Command = "/path/to/bacula_warn_on_zero.sh %i debug zero_incremental_ok"
#   }
#}
# =================================================================================


# Super simple test to see if the first parameter is a jobid (a number)
# ---------------------------------------------------------------------
jobid=${1}
if [[ $# = 0 || ! ${jobid} =~ ^[0-9]+$ ]]; then
	echo -e "\nUSE:\n$0 jobid [debug] [zero_incremental_ok]\n"
	exit 1
fi


# Some basic checks to make sure the bconsole config and some binaries exist
# --------------------------------------------------------------------------
if [ ! -e ${bcconfig} ]; then
	echo -e "\nThe bconsole configuration file does not seem to be '${bcconfig}'."
	echo -e "Please check the setting for the variable 'bcconfig'.\n"
	exit 1
fi

if [ ! -x ${bcbin} ]; then
	echo -e "\nThe bconsole binary does not seem to be '${bcbin}', or it is not executable."
	echo -e "Please check the setting for the variable 'bcbin'.\n"
	exit 1
fi

if [ ! -x ${sendmail} ]; then
	echo -e "\nThe sendmail binary does not seem to be '${sendmail}', or it is not executable."
	echo -e "Please check the setting for the variable 'sendmail'.\n"
	exit 1
fi


# Simple log function for debugging 
# ---------------------------------
log() {
	if [ "X${debug}" = "Xdebug" ]; then
		echo -e "${1}"
	fi
}


# Check for and set the variables 'debug'
# and/or 'zero_incremental_ok'
# ---------------------------------------
while (($#)); do
	case $1 in
		zero_incremental_ok )
			log "Option 'zero_incremental_ok' is set"
			zero_incremental_ok="1"
			;;

		debug )
		debug="debug"
		log "Debug mode enabled."
		;;
	esac
	shift
done


# Just display the jobid we are checking
# --------------------------------------
log "Checking JobId: ${jobid}"


# Get the job summary and strip it down to bare essentials
# --------------------------------------------------------
summary=$(echo "list jobid=${jobid}" | ${bcbin} -n | grep "^| \+[0-9]" | tr -d '|')


# We only care about backup jobs, so we get/set the type
# ------------------------------------------------------
type=$(echo "${summary}" | awk '{print $5}')
log "JobType: ${type}"

# Is this a backup job?
# ---------------------
if [ ${type} = "B" ]; then

	name=$(echo "${summary}" | awk '{print $2}')
	log "JobName: ${name}"

	status=$(echo "${summary}" | awk '{print $9}')
	log "JobStatus: ${status}"

	# Did the job terminate "OK" (T), or "OK -- with warnings" (W)?
	# If yes, then we get the rest of the variables (level, files, bytes)
	# -------------------------------------------------------------------
	if [[ ${status} = "T" || ${status} = "W" ]]; then

		level=$(echo "${summary}" | awk '{print $6}')
		log "JobLevel: ${level}"

		files=$(echo "${summary}" | awk '{print $7}')
		log "JobFiles: ${files}"

		bytes=$(echo "${summary}" | awk '{print $8}')
		log "JobBytes: ${bytes}"

		else
			log "Job Status \"${status}\" is not \"OK\" or \"OK -- with warnings\""
			log "Nothing to report. Exiting script here."
			exit 0
	fi

	else
		log "Job type \"${type}\" is not a Backup Job, not checking anything else."
		log "Nothing to report. Exiting script here."
		exit 0
fi


# OK, we have a backup job that terminated "OK" or "OK -- with warnings", let's continue
# --------------------------------------------------------------------------------------
log "JobId \"${jobid}\" is a backup job of type \"${level}\". It terminated \"OK\" or \"OK -- with warnings\", and wrote \"${files}\" files and \"${bytes}\" bytes."
log "Continuing to process..."


# Check for zero files or bytes for Full backup jobs
# --------------------------------------------------
log "Checking if this is a Full backup job."
if [ ${level} = "F" ]; then
	log "Level is Full."
	log "Checking number of files and bytes."
	if [[ ${files} = "0" || ${bytes} =  "0" ]]; then
		log "Files and/or bytes are zero."
		log "Full backup jobs are never allowed to have zero files nor zero bytes!"
		log "Warning condition triggered."
		zero_warn="full_zero"
		else
			log "Files and bytes are both non-zero. No warning will be sent."
	fi


	# Check to see if backup job is Incremental or Differental, is not allowed
	# to have zero files nor zero bytes, but it does have zero files or zero bytes
	# ----------------------------------------------------------------------------
	elif [[ ${level} = "I" || ${level} = "D" ]]; then
		log "Level is Incremental or Differential."
		log "Checking for 'zero_incremental_ok'"
		if [ -z ${zero_incremental_ok} ]; then
			log "'zero_incremental_ok' not set. Checking for zero files or zero bytes"
			if [[ ${files} = "0" || ${bytes} = "0" ]]; then
				log "Zero files or zero bytes in job."
				log "Incremental and Differential backup jobs are not allowed to have zero files nor zero bytes!"
				log "Warning Condition triggered."
				zero_warn="inc_or_diff_zero"
			fi
			else
				log "'zero_incremental_ok' is set."
				log "Skipping zero files and zero bytes checks."
				log "No warning will be triggered"
		fi
fi


# If the backup job has zero files or bytes and this
# is not allowed, then send an email report to an admin
# -----------------------------------------------------
if [ ! -z ${zero_warn} ]; then
	log "Sending warning email to admin: \"${admin}\""


	# Email the report
	# ----------------
	(
		echo "To: ${admin}"
		echo "From: ${admin}"
		echo "Subject: Warning from ${server} - Backup job ${name} (jobid ${jobid}) was \"OK\", but wrote zero files and/or zero bytes"
		echo ""
		if [ ${sendjoblog} = "yes" ]; then
			echo "Joblog for jobid ${jobid}:"
			echo -e "\n----8<----"
			echo "llist joblog jobid=${jobid}" | ${bcbin} -n
			echo -e "----8<----\n"
			else
				echo "Set the variable 'sendjoblog' to \"yes\" to see full job log in email body."
		fi
	) | ${sendmail} -t

	if [ ${exit_non_zero} = "yes" ]; then
		exit 1
	fi

	else
		log "Not sending email report."
		exit 0
fi
# -------------
# End of script
# -------------


# ----------
# Change Log
# ----------
# ----------------------------
# William A. Arlofski
# Reverse Polarity, LLC
# helpdesk@revpol.com
# http://www.revpol.com/bacula
# ----------------------------
#
# waa - 20171116 - First release
#                - Send an email alert when files or bytes are zero for backup
#                  jobs that have terminated "OK", or "OK -- with warnings"
#                - Currently always warns on Full jobs with zero files or bytes
#                - Optional 'zero_incremental_ok' command line parameter to
#                  ignore Incremental or Differential backup jobs with zero
#                  files or zero bytes
#                - Job names with spaces will break this script. Why would you
#                  do this?
#
# -----------------------------------------------------------------------------


# I like small tabs. Use  :set list in vim to see tabbing etc
# vim: set tabstop=2:softtabstop=2:shiftwidth=2 #
