#!/bin/bash # Original author: Pim van Pelt # Taken from OSS publication upstream: # debit.bit.nl:/debian/dists/stable/main/binary-i386/bit-cron-1.9-20060627.02.deb # and modified for use at Google. # # Author: Pim van Pelt # This is the master cron file format. It is designed to be quiet # and not shout anything at all to stdout/stderr in normal use. # # Usage: bitcron [-n] /path/to/crondefinition.cron [arg [arg ..]] # # This program eats either one or two arguments from the commandline. # If -n is given as the first argument, output is sent to stdout rather # than MAILTO or ESCALATE_MAILTO (this is a `dryrun'). The next argument # is a filename consisting of the cron definition. The file is sourced # and executed, but within a framework that makes crons much easier to # live with. You SHOULD have the filename of your crondefinition end # in ".cron", so it is apparent to other people that this is indeed # a cron definition. All other arguments are passed to the crontab's # main function, called bitcron_main; see below. # # The cron definition file consists of the following shell variables: # # PATH: Optional. You can set a standard path for your cron to search # programs in. Note that cron sets a minimal path, so you probably need # this if you are going to call programs from weird places. # Example: PATH=$PATH # # AUTHOR: Mandatory. Your full name, so we can track the author in case # something goes horribly wrong. # Example: AUTHOR="Pim van Pelt" # # NAME: Mandatory. The name of your script. This MUST be a terse # descriptive name consisting of [A-Za-z0-9\-\_], and in particular # no whitespace. It's used in logs and mails. # Example: NAME="example" # # MASTERLOG: Optional. If this is set, the transcript of the log is copied # to this file upon completion. If it is not set, the transcript is silently # deleted. Please make sure that this filename is writable for the calling # user, and that its name reveals who wrote it (including $NAME is a good # idea. # Example: MASTERLOG="/var/log/cron-${NAME}.log" # # MAILTO: Optional. If this is set, the transcript of the log is mailed to # this user using /usr/sbin/sendmail -t, in the event of no warnings, # errors, or fatal errors, aka 'if all went well'. If it is not set, the # mail is not sent on succesful completion of this cronjob. Please use # with care if your cron runs often. # Example: MAILTO="pim+noise@google.com" # # ESCALATE_MAILTO: Mandatory. If this is set, the transcript of the log # is mailed to this user using /usr/sbin/sendmail -t, in the event of # any warning, error or fatal error, aka 'if anything borked'. Please # make sure that this address is not a single person, but rather a role # account. This ensures better closure and continuity. # Example: ESCALATE_MAILTO="maps-alerts@google.com" # # Variables your script MUST NOT change are: # ERR, WRN, HOSTNAME, FATAL, FQDN # # Your cron-definition does not shebang. It begins with these variables. # It then has at least one function called bitcron_main(), in which you write # your cron just like you normally would, but each step is prepended by a # short oneliner describing what you are doing and appended with a line # stating you are done doing it, followed by an empty line. # # In your bitcron_main() script, you have three functions at your disposal: # warning $TEXT # error $TEXT # fatal $TEXT # # where warning() prints the TEXT and increments the warning counter, error() # increments the error counter and fatal() increments the error counter and # immediately halts execution of the cron. # # For example: # bitcron_main() # { # echo "Running rsync" # rsync blabla || warning "Could not rsync" # echo "Done (rsync)" # echo "" # # echo "Doing something crucial" # /usr/local/bin/crucial.sh blabla || \ # fatal "Could not do the crucial stuff, aborting!" # echo "Done (crucial)" # echo "" # # echo "Succesfully executed the cron" # } # # In the event of a fatal error (retval of crucial.sh != 0), the line # 'echo "Done (crucial)"' will not be executed, rather the cronscript # stops and mails to the escalation mail address that there was a serious # error which needs human intervention. # # Return values of bitcron depend on the presence of errors or warnings. # If there are no errors nor warnings, 0 is returned to the calling shell. # If there are errors, 127 is returned. If there are no errors, but there # are warnings, 126 is returned. If you somewhere set the RETVAL variable # (which is optional), that value is returned instead. # # Please note that your bitcron_main() function MUST NOT use exit. This messes up # the clean execution of this script, prohibiting it from mailing or # returning a decent exitcode to the calling shell. # # BITCRON_SEARCHPATH="/usr/local/cronscripts/ /etc/cronscripts/ ./" BITCRON_SEARCHPATH="~/cronscripts/ ./" BITCRON_REPORTTOOL="/usr/sbin/sendmail -t" function fatal() { echo "" echo "===== FATAL =====" echo $1 echo "===== FATAL =====" ERR=$(($ERR + 1)) FATAL=1; bitcron_end } function error() { echo "" echo "===== ERROR =====" echo $1 echo "===== ERROR =====" ERR=$(($ERR + 1)) } function warning() { echo "" echo "===== WARNING =====" echo $1 echo "===== WARNING =====" WRN=$(($WRN + 1)) } function bitcron_report() { if [ $WRN -eq 0 -a $ERR -eq 0 ]; then if [ ! -z "$MAILTO" ]; then ( echo "From: $HOSTNAME $NAME <`whoami`@$FQDN>" echo "To: $MAILTO" echo "Subject: bitcron $NAME - succesful" echo "Date: `date '+%a, %d %b %Y %T %z'`" echo "Errors-To: $MAILTO" echo "Reply-To: $MAILTO" echo "X-bitcron: $NAME" echo "X-Precedence: automatic" echo "" echo "Dear reader," echo "" echo "This is the $0 program on $HOSTNAME informing" echo "you I have succesfully executed the bitcron $NAME." echo "" echo "The log that I have for this cronjob follows:" cat $LOG echo "" echo "This mail is purely FYI, user intervention is not required." echo "" echo "-- " echo "Cheers," echo " $HOSTNAME" ) | ${BITCRON_REPORTTOOL} fi else ( echo "From: $HOSTNAME $NAME <`whoami`@$FQDN>" echo "To: $ESCALATE_MAILTO" if [ ! -z "$MAILTO" ]; then echo "CC: $MAILTO" fi if [ $FATAL -eq 0 ]; then echo "Subject: bitcron $NAME - $ERR error(s), $WRN warning(s)" else echo "Subject: bitcron $NAME - FATAL - $ERR error(s), $WRN warning(s)" fi echo "Date: `date '+%a, %d %b %Y %T %z'`" echo "Errors-To: $ESCALATE_MAILTO" echo "Reply-To: $ESCALATE_MAILTO" echo "X-Precedence: automatic" echo "X-bitcron: $NAME" echo "X-Errors: $ERR" echo "X-Warnings: $WRN" echo "" echo "Dear reader," echo "" echo "This is the $0 program on $HOSTNAME informing" echo "you that there was a problem running the bitcron $NAME." echo "" echo "The logfile that lead up to this:" cat $LOG echo "" echo "I hope you can deal with this situation ASAP." echo "" echo "-- " echo "Cheers," echo " $HOSTNAME" ) | ${BITCRON_REPORTTOOL} fi } function bitcron_begin() { if [ -z "$MASTERLOG" ]; then LOG="/tmp/bitcron-$NAME.$$" else LOG="${MASTERLOG}.$$" fi rm -f -- $LOG exec 3>&1 4>&2 1>$LOG 2>&1 WRN=0 ERR=0 FATAL=0 FQDN=$(hostname) HOSTNAME=$(hostname -s) if [ "A`echo $FQDN | grep '\.'`" = "A" ]; then FQDN="${FQDN}.corp.google.com" fi echo "Beginning $NAME cronjob at `date`" echo "" } function bitcron_end() { echo "" echo "Done with $NAME cronjob at `date`" echo "" echo "There were $ERR error(s) and $WRN warning(s)" if [ $FATAL -ne 0 ]; then echo "This script had a fatal error!" fi exec 1>&3 2>&4 bitcron_report if [ ! -z "$MASTERLOG" ]; then cp -f -- "$LOG" "$MASTERLOG" fi rm -f -- $LOG >/dev/null 2>&1 if [ ! -z $RETVAL ]; then exit $RETVAL elif [ $ERR -ne 0 ]; then exit 127 elif [ $WRN -ne 0 ]; then exit 126 fi exit 0 } function bitcron_usage() { echo "" echo "Usage: bitcron [-n] /path/to/crondefinition.cron [arg [arg ..]]" echo "" echo "Please read the bitcron script. It has documentation at the" echo "top of the file. Look in $0 for more details." echo "-- Author: Pim van Pelt " } # Here goes! if [ $# -lt 1 ]; then bitcron_usage exit 128 fi # See if we are in dryrun mode. if [ $1 = "-n" ]; then BITCRON_REPORTTOOL="/bin/cat" shift fi BITCRON_DEFINITION=$1 shift; # # Search through paths if the cron definition was not an absolute path. # if [ ${BITCRON_DEFINITION%%/*} ]; then for BITCRON_DIR in ${BITCRON_SEARCHPATH}; do if [ -r ${BITCRON_DIR}${BITCRON_DEFINITION} ]; then break; fi done else BITCRON_DIR="" fi if [ ! -r ${BITCRON_DIR}${BITCRON_DEFINITION} ]; then echo "bitcron: Unreadable cron definition: ${BITCRON_DIR}${BITCRON_DEFINITION}" if [ ${BITCRON_DEFINITION%%/*} ]; then echo "I tried in: ${BITCRON_SEARCHPATH}" fi bitcron_usage exit 128 fi . ${BITCRON_DIR}${BITCRON_DEFINITION} if [ -z "$NAME" ]; then echo "bitcron: Missing mandatory variable NAME in ${BITCRON_DIR}${BITCRON_DEFINITION}" bitcron_usage exit 128 fi if [ -z "$AUTHOR" ]; then echo "bitcron: Missing mandatory variable AUTHOR in ${BITCRON_DIR}${BITCRON_DEFINITION}" bitcron_usage exit 128 fi if [ -z "$ESCALATE_MAILTO" ]; then echo "bitcron: Missing mandatory variable ESCALATE_MAILTO in ${BITCRON_DIR}${BITCRON_DEFINITION}" bitcron_usage exit 128 fi bitcron_begin bitcron_main $* bitcron_end