#!/bin/bash
# $Id: backup,v 2.20 2009-03-28 22:03:44 jlarsen Exp $
# Copyright 2004 through 2009 John R Larsen - john@larsen-family.us
# Free for personal use.  Contact author for commercial use.
#
# Description: bash script used in automated backups.  Use "backup -hs"
# for setup description and how the script works.
#

# Use a trap to handle termination
trap 'echo "Exiting for SIGTERM or SIGINT"; exit' SIGTERM SIGINT

#-------------------------------------------------------------------------------
# get_version.  Function that parses script version and date info from the RCS Id line.
function get_version () {
   SCRIPT_VER="v`echo '$Id: backup,v 2.20 2009-03-28 22:03:44 jlarsen Exp $' | awk '{print $3}'`"
   SCRIPT_DATE="`echo '$Id: backup,v 2.20 2009-03-28 22:03:44 jlarsen Exp $' | awk '{print $4}'`"
} # get_version


#------------------------------------------------------------------------------
# display_help: Function to display the help screen
function display_help () {
local func_name=display_help
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside display_help";fi
cat << EOF
----- backup ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------
usage:
backup {delta | daily | weekly | rsync} [ options ]

delta    Full backup on baseline day of month.  Delta archives for all other days.
daily    Perform a daily backup.  This freshens the archive with any changes.
weekly   Perform a weekly backup.  This creates time stamped archive files.
rsync    Perform a rsync backup.  This uses rsync to duplicate directory trees.

Options:
-bd DD   Baseline Day for delta mode. Defaults to 01.  Must include leading zero.
-d 0xN   Debug flags value.  Use the "-hd" option to see how to use them.
-dd N    Delta Days.  Number of days included in a delta archive.  Default is 1.
-e adr   Email address (Default is none)
         (Separate multiple email addresses with commas and no spaces)
-f file  Name of File containing directories to backup (default: backup.dirlist)
-fs n    FailSafe recursion level (default: $FAIL_SAFE)
-h       Help screen
-hd      Help Debug. This describes how the builtin debugging works.
-hs      Help setup. This gives detailed operating and setup instructions.
-test    Do everything but zip the files.  This shows what would have been done.

         You MUST modify these scripts for your situation!
-mc      Make template backup.conf file
-md      Make template backup.dirlist script
-mdf     Make template backup.df script
-me      Make template backup.exit_cmds script
-mw      Make template backup.webpage script

EOF
} # display_help


#------------------------------------------------------------------------------
# display_debug_help:  Function to display debug help.
function display_debug_help () {
local func_name=display_debug_help
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside display_debug_help";fi
cat << EOF | more
----- Debug Help ($SCRIPT_VER $SCRIPT_DATE) ---------------------------------------
Debug is enabled in one of three ways in decending order of precedence: 

   "-d 0xNNNN" on command line
   BACKUP_DEBUG_FLAGS environment variable
	"debug_flags" file in same directory as backup

Each debug line is qualified with a construct like the following:

if [ \$((\$D & 0x1)) -ne 0 ]; then echo "[\$LINENO]\$func_name:\$\$> Debug message or action";fi

The expression on the left of -ne is a bash construct.  The $((expr)) gets
evaluated and returns a value.  The expression I'm using is "$D & 0xNN".  The
& performs a bitwise AND and returns the result.  If it is zero (no matching
bits) then the debug line is skipped.  If it is non zero then the debug line
is performed.  Each debug line can have multiple bits that turn it on or just
a single bit.  The pattern "0xNN" is the collection of bits that turn the 
debug on.  The action of each bit is defined below.

00000000 - No debug

00000001 - Enable all the "Inside function name" debug lines
00000002 - process_command_line verbose output
00000004 - 
00000008 - 

00000010 - main: Display environment when starting up
00000020 - recurse_tree: Show pwd
00000040 - recurse_tree: Show pwd and ls -1A `pwd`
00000080 - read_file: Show contents of LINES array

80000000 - Don't output the "Debug flags changed" messages in "set_debug_level"
EOF
} # display_debug_help


#------------------------------------------------------------------------------
# display_setup_help: Function to display the setup help screen
function display_setup_help () {
local func_name=display_setup_help
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside display_setup_help";fi
cat << EOF
DESCRIPTION:
This script performs a backup, either daily, weekly, or delta.  Archives are
zipfiles.  A daily backup freshens the same archive every day.  A weekly backup
creates a new archive that has the date in the archive filename.  A delta
backup creates a full archive on the baseline day of the month and delta archives
the other days.  The file "backup.dirlist" contains a list of starting
directories to backup.  "backup" starts in a directory and recurses the
directory structure from that point looking for files named "backup.list".
When backup finds an instance of backup.list in a directory, it uses it as
input to the zip program to create an archive.  Once the archive is made,
backup exits that directory and continues recursing through the rest of the
directory structure.  Once the entire directory structure has been searched
backup moves on to the next starting directory defined in backup.dirlist.
(rsync capability was added after v2.14.  See rsync description below.)

ARCHIVE NAMING:
"backup" uses the full path to the directory where backup.list is found to create
the archive name.  It substitutes a period for slashes and a circumflex ^ for 
spaces.  It is easy to tell from the archive filename where the archive started.
For example, say that "backup" finds backup.list in the following directory and
that the date is 1 Sep 2004:

/mnt/cool-beast/c/Program Files/Microsoft Office/templates files

"backup" creates daily archives with the filenames like the following:
   mnt.cool-beast.c.Program^Files.Microsoft^Office.templates^files.zip

"backup" creates weekly and delta archives with filenames like the following:
   mnt.cool-beast.c.Program^Files.Microsoft^Office.templates^files__2004-Sep-01.zip

Spaces in filenames cause all sorts of problems.  A pox upon Microsoft.  "backup"
creates these filenames so that scp can be used to transfer files.  "backup" can
perform automated offsite backups.  (See backup.exit_cmds below.)  scp doesn't know
how to handle filenames with spaces or parentheses in them.  That's why this
method of creating names was chosen.


EMAIL:
"backup" sends email to email addresses defined on the command line using the -e
option.  Multiple email addresses can be used by separating them with a comma and
no space.  The email reports all aspects of the backup including date, times,
archive sizes, and beginning and ending disk usage.  A single email is sent at the
end of the entire backup operation.


WEBPAGE UPDATE:
"backup" can update a webpage.  If the file "backup.webpage" exists in the same
directory as the backup script, it is used to perform the update.  Use "backup -mw"
to create a template of backup.webpage that can be edited for your situation.
Complete instructions are included in the file.  If this file exists, it is used
to updated the webpage just before each archive operation begins.  That makes it
possible to monitor progress on long archive operations by looking at the webpage.
The webpage is essentially a duplicate of the contents of the email.


DISK USAGE:
"backup" reports beginning and ending disk usage.  backup uses the "df" command
unless it finds the file "backup.df" in the same directory as the backup script.
In a situation with lots of auto mounted drives, the output of "df" can be very
lengthy.  You can use backup.df to perform df only on the drives that you're
interested in.  Use "backup -mdf" to create a template version of this file that you
can edit for your situation.


CRON OPERATION:
It is intended that backup be run as a cron task.  Typical crontab lines are shown
below.  The first line runs a daily backup Monday through Saturday starting at 3:01
AM.  The second line runs a weekly backup Sunday morning:

01 03 * * 1-6 /root/backup/backup daily -f /root/backup/daily.dirlist -e user@domain.com
01 03 * * 0 /root/backup/backup weekly -f /root/backup/weekly.dirlist -e user@domain.com


ERROR CHECKING:
"backup" can be run in "test mode" by putting -test on the command line.  In this
mode it does everything except the actual time consuming archive operation.  It
also outputs a diagnostic message when it finds each control file.  This makes it
possible to verify that the backup configuration operates as expected.


CONTROL FILES:
Operation of "backup" is controlled by several files.  These files are described
below:

backup.dirlist:
This file defines the starting directories, archive storage directories, and
commands to execute before starting the backup operation.  The format of this file
is described in the file itself.  Use "backup -md" to create a template file that
you can edit for your situation.  Examples of "commands to execute" before a backup
operation starts might be like starting a ClearCase view to make the starting
directory visible, or mounting a drive.  Spaces in directory names are permissible.
There is no need to put double quotes around the names.  "backup" takes care of that.

backup.list:
When backup finds this file in a directory, it stops recursing past that point, and
uses its contents to define which files zip will archive.  Each line of this file
defines a directory or file that should be included in the archive.  A single "."
in the file directs zip to recursively archive everything in the current directory.
This is the easiest way to archive a whole directory structure.  If you only want
to archive some of the files or directories in the current directory, then put each
name on a separate line in the file.  This is useful if you want to include files
in a directory that isn't a subdirectory of the current directory.

backup.enter_cmds:
When backup finds this file it executes the commands in the file.  The pwd used is
the directory where the file is located.  An example of using this file is to
remove temporary files in a directory before archiving.  For example, the Quicken
program makes temporary files that start with tilda "~".  These files mess up the
zip program.  Putting a command like "rm -f /home/jlarsen/quicken/\~*" in
backup.enter would remove all the temp files starting with tilda.

backup.exit_cmds:
When backup finds this file it executes the commands in the file just before
leaving the directory.  When this script is called, $1 holds the path to the
archive directory and $2 holds the name of the archive just made.  If these values
are null then no archive was made in the directory where backup.exit_cmds is located.
This file can be used to do an automatic upload to an offsite location.  Use "backup 
-me" to create a template file that can be modified for your situation.

backup.no_recurse:
When backup finds this file it exits the directory immediately doing nothing else.
This is useful if there is a directory that you don't want backup to recurse into.
For example, there are many hidden directories in a user's home directory
(directories that start with a single dot ".") that have huge subdirectories.
Putting backup.no_recurse keeps backup from wasting time and resources.

backup.webpage:
This file is described in "WEBPAGE UPDATE" above.

backup.df:
This file is described in "DISK USAGE" above.

backup.email:
This file is generated each time backup runs.  It is overwritten each time.

backup.html:
This file is generated each time backup runs.  It is the file that is copied over to
the webserver.

RSYNC DESCRIPTION:
This section explains how backup works when "rsync" mode is used.  The script
works the same as far as emails and webpage updates are concerned.  The backup.dirlist
file is exactly the same except that it is possible to put a remote host in the
backup directory line.  In rsync mode the backup directory in the backup.dirlist
file is considered the root.  When backup finds the backup.rsync file in a directory
it uses that as the starting point.  The full path is maintained in the backup.
For example, if you are running backup on a computer named "screamer" and you are
backing up to a remote computer named "zippy", you could put a backup directory
line in backup.dirlist of "zippy:/backups/screamer".  When backup reads this line
it would then look for a host entry of "zippy" in the backup.conf file.  backup uses
the settings for "zippy" to do the remote rsync backup.  Now, if on screamer backup
found a "backup.rsync" file in the /var/mail directory backup would use rsync to copy
all the files in /var/mail over to zippy into the /backups/screamer/var/mail directory.
In rsync mode backup.list, backup.enter_cmds, and backup.exit_cmds files are ignored.  
backup.no_recurse files behave the same way as described above.

backup.conf:
This file is used in rsync mode and contains ssh "host" definitions for remote
sites where backups are stored.  The format of this file is described in the 
file itself.  Use "backup -mc" to create a template file that you can edit for 
your situation.  

backup.rsync:
This file is looked for when backup is run in "rsync" mode.  When found, backup 
stops recursing past that point.  It uses the parameters in the backup.conf
file to perform an rsync copy.  The destination directory on the remote host 
comes from the backup.dirlist file.  The local directory is replicated in the
backup directory.  

CONFIGURING SSH CONNECTIONS:
Use the "ssh-keygen" utility to generate an rsa public/private key pair.  Name
the key something like "id_rsa.client_name" so it is easily identifiable.  Do
not give the key a pass phrase.  The ssh connections all require a
public/private key.  The ssh client's public RSA key must be copied to the
~/.ssh/authorized_keys file on the ssh server.  This key must not have a
passphrase as that would require entering a password, which can't be done in a
cron job.  The corresponding private key must be on the client in the location
specified in the backup.conf files.  Directory and file permissions are
important.  The RSA private key file must only be readable by the owner of the
file, ie. "chmod 400 filename".  The directory must only be writeable by the
owner, ie. 700.  Use chmod to set permissions accordingly.

There are ssh host definition sections in backup.conf.  Once the information is
entered it is time to verify that ssh connections can be made.  As an example
say that there is a host definition "zippy".  Verify that a connection can be
made using the "zippy" host definition in the backup.conf file.  On the
command line use this command:

    ssh -F backup.conf zippy

The above command verifies the settings in the backup.conf file for host zippy.
You will be warned that the ssh server's key doesn't exist and you will be
asked if you want to add it.  Answer yes.  Once this key is in the known_hosts
file you won't be asked for it anymore.  Type exit to return to the ssh client
shell.  Repeat the above configuration for all the host definitions in
backup.conf.

SUPPORTED PLATFORMS:
This script has been used successfully on the following systems:
Solaris 8 using bash v2.03.0(1)
Solaris 9 using bash v2.05.0(1)
Mandrake Linux 9.1 using bash v2.05b.0(1)
cygwin 1.5.15 and later using bash v2.05b.0(1) and later
Ubuntu 8.10 Linux 2.6.27-11-generic using bash v3.29(1)

Copyright: 2004 through 2009 by John R Larsen
Free for personal use.  Contact the author for commercial use.
http://larsen-family.us            john@larsen-family.us

EOF
} # display_setup_help


#------------------------------------------------------------------------------
# set_debug_level:  Function that sets the $D variable based on a file, an
#environment variable, or a command line variable. See display_debug_help.
function set_debug_level () {
local func_name=set_debug_level

# Note: Command line -d overrides environment variable BACKUP_DEBUG_FLAGS if it exists, 
# which overrides debug_flags file if it exists.
# Check one time if BACKUP_DEBUG_FLAGS env variable exists and use it if it does
if [ "${ENV_VAR_TESTED:=FALSE}" = "FALSE" ]; then
	ENV_VAR_TESTED=TRUE
	D=${BACKUP_DEBUG_FLAGS:-0}
	if [ "$D" != "0" ] && [ $(($D & 0x80000000)) -eq 0 ]; then echo "[$LINENO]$$> ${func_name:-startup}: set_debug_level: Debug flags changed to: $D"; fi
fi

# Check the existence of a file named "debug_flags" and use it if conditions are right
if [ -e debug_flags ]; then
	if [ "${DEBUG_FLAGS_FILE:=FIRST_READ}" = "FIRST_READ" ]; then
		# Getting here means the debug_flags file has never been read.
		DEBUG_FLAGS_FILE=`cat -s debug_flags`
		if [ "$D" = "0" ]; then
			# Getting here means $D was zero and the debug_flags contents should be used instead since env variable doesn't exist or has value of 0
			D=$DEBUG_FLAGS_FILE
			if [ "$D" != "0" ] && [ $(($D & 0x80000000)) -eq 0 ]; then echo "[$LINENO]$$> ${func_name:-startup}: set_debug_level: Debug flags changed to: $D"; fi
		fi
	else
		# Read debug_flags file and update $D if the file has changed
		NEW_DEBUG_FLAGS_FILE=`cat -s debug_flags`
		if [ $NEW_DEBUG_FLAGS_FILE != $DEBUG_FLAGS_FILE ]; then
			DEBUG_FLAGS_FILE=$NEW_DEBUG_FLAGS_FILE
			D=$DEBUG_FLAGS_FILE
			if [ $(($D & 0x80000000)) -eq 0 ]; then
				echo "[$LINENO]$$> ${func_name:-startup}: set_debug_level: Debug flags changed to: $D"
			fi
		fi
	fi
fi
} # set_debug_level


#-------------------------------------------------------------------------------
# setup_env.  Function that determines what OS the script is running on and
# setups environment variables accordingly.
function setup_env () {
local func_name=setup_env
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside setup_env";fi

# Figure out which OS you're running on and setup program paths accordingly
OS_TYPE=`uname -a | awk '{print $3}'`
case $OS_TYPE in
	5.*) # Solaris
      export DF="/usr/ucb/df"
      export HOST=`/usr/ucb/hostname`
      export LS=/bin/ls
      export MAIL=/usr/ucb/mail
      export SCP=/usr/local/bin/scp
      export SSH=/usr/local/bin/ssh
      export TEE=/usr/bin/tee
      export RSYNC_PGM=/usr/local/bin/rsync
      W=/usr/bin/w
      if [ -e /usr/bin/zip ]; then
         ZIP_PGM=/usr/bin/zip
      else
         echo "ERROR [$LINENO]$func_name> Missing zip."
         exit 1
      fi
		;;

	2.4*) # Mandrake Linux 8.x and 9.x, Redhat Linux Work Station Enterprise 3
      export DF="/bin/df -k"
      export HOST=`/bin/hostname`
      export LS=/bin/ls
      export MAIL=/bin/mail
      export SCP=/usr/bin/scp
      export SSH=/usr/bin/ssh
      export TEE=/usr/bin/tee
      export RSYNC_PGM=/usr/bin/rsync
      W=/usr/bin/w
      if [ -e /usr/bin/zip ]; then
         ZIP_PGM=/usr/bin/zip
      else
         echo "ERROR [$LINENO]$func_name> Missing zip."
         exit 1
      fi
		;;

	2.6*) # Mandrake Linux 10.x, Ubuntu 8.10
      export DF="/bin/df -k"
      export HOST=`/bin/hostname`
      export LS=/bin/ls
      export MAIL=/usr/bin/mail
      export SCP=/usr/bin/scp
      export SSH=/usr/bin/ssh
      export TEE=/usr/bin/tee
      export RSYNC_PGM=/usr/bin/rsync
      W=/usr/bin/w
      if [ -e /usr/bin/zip ]; then
         ZIP_PGM=/usr/bin/zip
      else
         echo "ERROR [$LINENO]$func_name> Missing zip."
         exit 1
      fi
		;;

	1.5*) # cygwin
      export TEE=/usr/bin/tee

      # Base cygwin doesn't include many packages required for backup to work.
      # Check that these packages have been installed in their default locations.
      # Make sure the openssh package has been installed
      if [ -e /usr/bin/scp ]; then
         export SCP=/usr/bin/scp
         export SSH=/usr/bin/ssh
      else
         echo "ERROR [$LINENO]$func_name> Missing scp.  Install the openssh package."
         exit 1
      fi

      # Make sure the email package has been installed
      if [ -e /usr/bin/email ]; then
         MAIL=/usr/bin/email
      else
         echo "ERROR [$LINENO]$func_name> Missing email.  Install the email package."
         exit 1
      fi

      # Make sure the zip package has been installed
      if [ -e /usr/bin/zip ]; then
         ZIP_PGM=/usr/bin/zip
      else
         echo "ERROR [$LINENO]$func_name> Missing zip.  Install the zip package."
         exit 1
      fi

      # Make sure the rsync package has been installed
      if [ -e /usr/bin/rsync ]; then
         RSYNC_PGM=/usr/bin/rsync
      else
         echo "ERROR [$LINENO]$func_name> Missing rsync.  Install the rsync package."
         exit 1
      fi

      export DF="/usr/bin/df -k"
      export HOST=`/usr/bin/hostname`
      export LS=/usr/bin/ls
      W=/usr/bin/w
		;;

	*) # Unknown OS
		echo "ERROR [$LINENO]$func_name> Unknown OS"
		exit 1
		;;
esac


# Set the default values of all environment variables here
export WORKING_DIR=`pwd`
export BACKUP_DATE=`date +%Y\-%b\-%d`
export BACKUP_YYMMDDHHMM=`date +%y%m%d-%H%M` \
export YEAR=`date +%Y`
export MONTH=`date +%m`
export DAY=`date +%d`
export BASELINE_DAY="01"
export EMAIL_FILE=$WORKING_DIR/backup.email
export HTML_FILE=$WORKING_DIR/backup.html
export CALL_LEVEL=0
export BACKUP_TYPE=NOT_SET
export BACKUP_DIR=~
export TEST_MODE="FALSE"
BACKUP_LIST=backup.dirlist
# The default for EMAIL_ADDRESS can be overwritten using the -e command line option
EMAIL_ADDRESS=""
# The maximum number or recursion levels is set by FAIL_SAFE; can be set using -fs option
export FAIL_SAFE=50
# Set variable that tells zip what extensions to simply store with 0% compression
export ZIPOPT="-n .gif:.zip:.wav:.mp3:.tar:.tgz:.jpg"

# Set variable with options that the rsync program uses
# rsync has a very large number of options.  Use "man rsync" to understand those used here.
#export RSYNC_OPT="-auvzR"
export RSYNC_OPT="-rltDuvzRs --rsync-path=/usr/bin/rsync"
# -r, --recursive             recurse into directories
# -l, --links                 copy symlinks as symlinks
# -t, --times                 preserve modification times
# -D                          same as --devices --specials
# -u, --update                skip files that are newer on the receiver
# -v, --verbose               increase verbosity
# -z, --compress              compress file data during the transfer
# -R, --relative              use relative path names
# -s, --protect-args          no space-splitting; wildcard chars only
#     --rsync-path            put in path to rsync on remote host

export RSYNC_FILTER="--filter='exclude Temporary Internet Files' --filter='exclude Application Data' --filter='exclude Application Data' --filter='exclude Cookies'"

# Set the rsync I/O timeout value used
TIMEOUT_VAL=60

# Set the number of rsync retries
RETRY_LIMIT=3

YES=1
NO=0
TRUE=1
FALSE=0

# Need to figure out what yesterday's day, month, and year were for delta mode
yesterday $DAY $MONTH $YEAR
export ZIP_DAY=$OLD_DAY
export ZIP_MONTH=$OLD_MONTH
export ZIP_YEAR=$OLD_YEAR

} # setup_env


#------------------------------------------------------------------------------
# display_env
# Function to display the values of environment variables
function display_env () {
	local func_name=display_env
	if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside display_env";fi
	echo "----- CURRENT ENVIRONMENT VARIABLE VALUES -----------------"
	echo "HOST:                 $HOST"
	echo "EMAIL_ADDRESS:        $EMAIL_ADDRESS"
	echo "WORKING_DIR:          $WORKING_DIR"
	echo "BACKUP_DATE:          $BACKUP_DATE"
	echo "BACKUP_YYMMDDHHMM:    $BACKUP_YYMMDDHHMM"
	echo "YEAR:                 $YEAR"
	echo "MONTH;                $MONTH"
	echo "DAY:                  $DAY"
	echo "OLD_YEAR:             $OLD_YEAR"
	echo "OLD_MONTH;            $OLD_MONTH"
	echo "OLD_DAY:              $OLD_DAY"
	echo "ZIP_YEAR:             $ZIP_YEAR"
	echo "ZIP_MONTH;            $ZIP_MONTH"
	echo "ZIP_DAY:              $ZIP_DAY"
	echo "BASELINE_DAY:         $BASELINE_DAY"
	echo "DELTA_DAYS:           $DELTA_DAYS"
	echo "EMAIL_FILE:           $EMAIL_FILE"
	echo "HTML_FILE:            $HTML_FILE"
	echo "CALL_LEVEL:           $CALL_LEVEL"
	echo "BACKUP_TYPE:          $BACKUP_TYPE"
	echo "BACKUP_DIR:           $BACKUP_DIR"
	echo "BACKUP_LIST:          $BACKUP_LIST"
	echo "FAIL_SAFE:            $FAIL_SAFE"
	echo "TEST_MODE:            $TEST_MODE"
	echo "ZIPOPT:               $ZIPOPT"
	echo "RSYNC_OPT:            $RSYNC_OPT"
	echo "RSYNC_PGM:            $RSYNC_PGM"
	echo "-----------------------------------------------------------"
	return 0
} # display_env


#------------------------------------------------------------------------------
# yesterday.  Function that provides a previous date in DD MM YYYY format.  Inputs are:
# $1: DD
# $2: MM
# $3: YYYY
# $4: Number of days to go back (If NULL then only go back one day)
#
# The output of the function is returned in the following three variables:
# OLD_DAY
# OLD_MONTH
# OLD_YEAR
#
# If no arguments are passed then OLD_DAY, OLD_MONTH, and OLD_YEAR will have yesterday's date.
function yesterday () {
DAY=${1:-`date +%d`}
MONTH=${2:-`date +%m`}
YEAR=${3:-`date +%Y`}
NUM_DAYS=${4:-1}
OLD_DAY=$DAY
OLD_MONTH=$MONTH
OLD_YEAR=$YEAR

# Verify $DAY is in valid range and has correct format
if [[ "$DAY" < "01" ]] || [[ "$DAY" > "31" ]] || [[ ${#DAY} -ne 2 ]]; then
   echo "ERROR[$LINENO]$func_name> DAY: $DAY has wrong format or is out of range"
   return 1
fi

# Verify $MONTH is in valid range and has correct format
if [[ "$MONTH" < "01" ]] || [[ "$MONTH" > "12" ]] || [[ ${#MONTH} -ne 2 ]]; then
   echo "ERROR[$LINENO]$func_name> MONTH: $MONTH has wrong format or is out of range"
   return 1
fi

# Verify $YEAR is in valid range and has correct format
if [[ "$YEAR" < "1970" ]] || [[ "$YEAR" > "9999" ]] || [[ ${#YEAR} -ne 4 ]]; then
   echo "ERROR[$LINENO]$func_name> YEAR: $YEAR has wrong format or is out of range"
   return 1
fi

   function _yesterday () {
   local _day=$1
   local _month=$2
   local _year=$3
   if [ "$_day" = "01" ]; then
      case $_month in
         01)
            OLD_DAY=31
            OLD_MONTH=12
            let OLD_YEAR=_year-1
            ;;

         02)
            OLD_DAY=31
            OLD_MONTH=01
            ;;

         03)
            let LEAP=_year%4
            if [[ $LEAP -eq 0 ]]; then
               OLD_DAY=29
            else
               OLD_DAY=28
            fi
            OLD_MONTH=02
            ;;

         04)
            OLD_DAY=31
            OLD_MONTH=03
            ;;

         05)
            OLD_DAY=30
            OLD_MONTH=04
            ;;

         06)
            OLD_DAY=31
            OLD_MONTH=05
            ;;

         07)
            OLD_DAY=30
            OLD_MONTH=06
            ;;

         08)
            OLD_DAY=31
            OLD_MONTH=07
            ;;

         09)
            OLD_DAY=31
            OLD_MONTH=08
            ;;

         10)
            OLD_DAY=30
            OLD_MONTH=09
            ;;

         11)
            OLD_DAY=31
            OLD_MONTH=10
            ;;

         12)
            OLD_DAY=30
            OLD_MONTH=11
            ;;

      esac
   else
      case $_day in
         02)
            OLD_DAY=01
            ;;

         03)
            OLD_DAY=02
            ;;

         04)
            OLD_DAY=03
            ;;

         05)
            OLD_DAY=04
            ;;

         06)
            OLD_DAY=05
            ;;

         07)
            OLD_DAY=06
            ;;

         08)
            OLD_DAY=07
            ;;

         09)
            OLD_DAY=08
            ;;

         10)
            OLD_DAY=09
            ;;

         *)
            let OLD_DAY=_day-1
            ;;

      esac
   fi
   } # _yesterday

   # Backup the number of days requested
   local count=0
   while [ $count -lt $NUM_DAYS ]; do
      _yesterday $OLD_DAY $OLD_MONTH $OLD_YEAR
      let count=count+1
   done
} # yesterday


#------------------------------------------------------------------------------
# process_command_line
function process_command_line () {
local func_name=process_command_line
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside process_command_line";fi
while [ $# -ne 0 ]
	do
	case $1 in
		-bd) #baseline day - Day the full backup is made.  Defaults to 01.
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -bd"; fi
			BASELINE_DAY=$2
         # Verify day is within range and has the correct format of DD including the leading zero
         if [[ "$BASELINE_DAY" < "01" ]] || [[ "$BASELINE_DAY" > "31" ]] || [[ ${#BASELINE_DAY} -ne 2 ]]; then
            echo "ERROR[$LINENO]$func_name> Baseline date: $2 has wrong format or is out of range"
            exit 1
         fi
			shift ;;

		-d) #debug mode
		   # Command line -d overrides environment variable BACKUP_DEBUG_FLAGS if it exists, which overrides debug_flags file if it exists
			D=$2
			if [ "$D" != "0" ] && [ $(($D & 0x80000000)) -eq 0 ]; then echo "[$LINENO]$func_name:$$> Debug flags changed to: $D"; fi
			if [ $(($D & 0x200000)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -d"; fi 
			shift ;;

		daily) #performing daily backup
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case daily"; fi
			BACKUP_TYPE=DAILY
			;;

		delta) #Delta mode
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case delta"; fi
			BACKUP_TYPE=DELTA
			;;

		rsync) #rsync mode
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case rsync"; fi
			BACKUP_TYPE=RSYNC
			;;

		-dd) #delta days - Number of days included in a delta backup.  Default is 1.
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -dd"; fi
			DELTA_DAYS=$2
         # Verify the value is positive and not zero
         if [[ "$DELTA_DAYS" < "01" ]]; then
            echo "ERROR[$LINENO]$func_name> Delta days: $2 can't be less than 1"
            exit 1
         fi
         # Recalculate the starting date for the delta archive
         yesterday $DAY $MONTH $YEAR $DELTA_DAYS
         export ZIP_DAY=$OLD_DAY
         export ZIP_MONTH=$OLD_MONTH
         export ZIP_YEAR=$OLD_YEAR
			shift ;;

		-e) #email address
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -e"; fi
			EMAIL_ADDRESS=$2
			shift ;;

		-f) # list of backups to perform
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -f"; fi
			BACKUP_LIST=$2
			shift ;;

		-fs) # fail safe level
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -fs"; fi
			FAIL_SAFE=$2
			shift ;;

		-h) #show help
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -h"; fi
			display_help
			exit 0;;

		-hd) #show debug help
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -hd"; fi
			display_debug_help
			exit 0;;

		-hs) #show setup help
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -hs"; fi
			display_help >| /tmp/$$.tmp
			display_setup_help >> /tmp/$$.tmp
			cat /tmp/$$.tmp | more
			rm -f /tmp/$$.tmp
			exit 0;;

		-mc) #make template backup.conf file
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -mc"; fi
			make_template_conf
			echo "Making template backup.conf file.  You MUST modify this for your use."
			exit 0;;

		-md) #make template backup.dirlist script
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -md"; fi
			make_template_dirlist
			echo "Making template backup.dirlist script.  You MUST modify this for your use."
			exit 0;;

		-mdf) #make template backup.df script
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -mdf"; fi
			make_template_df
			echo "Making template backup.df script.  You MUST modify this for your use."
			exit 0;;

		-me) #make template backup.exit_cmds script
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -me"; fi
			make_template_exit_cmds
			echo "Making template backup.exit_cmds script.  You MUST modify this for your use."
			exit 0;;

		-mw) #make template backup.webpage script
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -mw"; fi
			make_template_webpage
			echo "Making template backup.webpage script.  You MUST modify this for your use."
			exit 0;;

		-test) # Don't zip files, but do everything else
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -test"; fi
			TEST_MODE=TRUE
			echo ""
			echo "----- backup ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------"
			echo "Running in test mode.  Archives aren't made."
			echo ""
			;;

		weekly) #performing weekly backup (default)
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case weekly"; fi
			BACKUP_TYPE=WEEKLY
			;;

		*) # Unrecognized option
			display_help
			echo "ERROR[$LINENO]$func_name> Unrecognized command line option: $1"
			exit 1
			;;

	esac

   # Don't shift command line if processing at last argument
	if [ $# -ne 0 ]; then
		shift
	fi
done
} # process_command_line


#------------------------------------------------------------------------------
# web_page. Function that creates a web page and copies it somewhere periodically.
# If the file backup.webpage exists then the web page is created and backup.webpage
# is called with the name of the web page in $1.  The backup.webpage file is unique
# and must be self contained to copy the file where it needs to be.  This can
# be to another drive on the LAN or could be using scp.  Use "backup -mw" to make
# a template that can be modified for your needs.
function web_page () {
local func_name=web_page
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside web_page";fi

# Check if backup.webpage exists and exit if it doesn't
if [ ! -e $WORKING_DIR/backup.webpage ]; then return; fi

# Getting here means backup.webpage exists

# Build the web page
cat << EOF >| $HTML_FILE
<HTML>
<HEAD>
   <TITLE>$BACKUP_TYPE backup report:  $HOST</TITLE>
</HEAD>
<BODY>
   <h1> $BACKUP_TYPE backup report:  $HOST</h1>
   <h2> Time: `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a`</h2>
<pre>
EOF

cat $EMAIL_FILE >> $HTML_FILE
echo "</pre>" >> $HTML_FILE


echo "</BODY> </HTML>" >> $HTML_FILE

# Use the backup.webpage script in the back ground to transfer the web page where it needs to go.
$WORKING_DIR/backup.webpage $HTML_FILE &

} # web_page


#------------------------------------------------------------------------------
# make_template_df: Function to create a template backup.df script
function make_template_df () {
local func_name=make_template_df
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside make_template_df";fi

cat << EOF >| backup.df
#!/bin/bash
# Copyright 2004 through 2009 John R Larsen - john@larsen-family.us
#----- backup ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------
# This is the backup.df script called by "backup" to update get a listing of disk usage.
# You don't need this script if the normal output of df is acceptable.  If this script
# doesn't exist then "backup" will use df and capture its output.  If you want to limit
# the output of df to a few specific directories then use this script.
#
# The following environment variables are exported by backup and can be used in this script:
#    DF     The full path to the df program on the host where the backup is running
#
\$DF
EOF

# Set execute permissions
chmod 755 backup.df

} # make_template_df


#------------------------------------------------------------------------------
# make_template_exit_cmds: Function to create a template backup.exit_cmds script
function make_template_exit_cmds () {
local func_name=make_template_exit_cmds
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside make_template_exit_cmds";fi
cat << EOF >| backup.exit_cmds
#!/bin/bash
# Copyright 2004 through 2009 John R Larsen - john@larsen-family.us
#----- backup ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------

# IMPORTANT:  YOU MUST MODIFY THIS FILE FOR YOUR CONFIGURATION

########## START OF USER CONFIGURABLE VARIABLES ###########################
#
# Enter name of the remote host on the next line so it shows up in emails:
REMOTE_HOST=remote_host_name

# Full path to the destination directory on the remote host:
DESTINATION_DIR=/path/to/destination/dir/on/remote/host

# Full path to and name of the ssh configuration file:
SSH_CONFIG_FILE=/path/to/ssh/config/file

# Host name in the ssh config file
SSH_HOST_NAME=name_of_ssh_host_in_config_file

# Uncomment and use the next line to override backup's EMAIL_ADDRESS if desired:
#EMAIL_ADDRESS=

########## END OF USER CONFIGURABLE VARIABLES #############################


# SCRIPT DESCRIPTION:
# This is the backup.exit_cmds script called by "backup".  It is executed in the 
# background so that "backup" continues in parallel.  It contains any commands
# that should be performed before "backup" leaves a directory.  The example given here uses
# this script to make an offsite copy of the archive just made.  These are passed in:
# \$1 - Path to the archive just made  (Null if no archive made)
# \$2 - Name of the archive file  (Null if no archive made)

# Abort if no filename passed in
if [ "\$2" == "" ]; then
	echo "ERROR: No filename passed in.  pwd = \`pwd\`" | \$TEE -a \$EMAIL_FILE
	exit 1
fi

# The following environment variables are exported by backup and can be used in this script:
#    EMAIL_ADDRESS  Email addresses to which email will be sent
#    EMAIL_FILE     Full path to the email file that will be sent at the end of backup
#    HOST           Name of the host performing the backup
#    LS             Full path to the ls program
#    MAIL           Full path to the email program
#    SCP            Full path to the scp program on the host where "backup" is running
#    TEE            Full path to the tee program
#    TEST_MODE      Set to "TRUE" if operating in test mode

# SCP copy.  Use this to transfer a file to a remote machine using scp.  
# The "-F" option on the scp line tells scp to use the named ssh configuration file.  
# Below are the settings that might be in such a file.  See manpage for ssh_config(5) 
# for complete descriptions:

#   Host host_entry
#   HostName host_name_or_IP_address
#   Port = 22
#   UserKnownHostsFile = /path/to/file/known_hosts
#   User = login_name
#   IdentityFile = /path/to/private/rsa/key/file/id_rsa.name

# Using the settings above and the private key (with no pass phrase) the transfer can be
# done with no need to enter a password.  The lines below are an example of performing 
# an scp transfer and sending an email informing of the success or failure of the
# transfer.

#------------------------------------------------------------------------------
# send_email: function to send an email after scp operation has completed
# \$1 - SUCCESS or ERROR
# \$2 - Name of file being copied
# \$3 - Directory where file was located
function send_email () {
	# Create the text of the email that will be sent
	echo "----- backup (\$SCRIPT_VER \$SCRIPT_DATE) --------------------------------------------" >| /tmp/\$\$.email
	if [ "\$1" == "SUCCESS" ]; then
		echo "SUCCESS:          \$HOST to \$REMOTE_HOST copy succeeded" >> /tmp/\$\$.email
	else
		echo "ERROR:            \$HOST to \$REMOTE_HOST copy failed" >> /tmp/\$\$.email
	fi
	echo "File:             \$2" >> /tmp/\$\$.email
	echo "Source Dir:       \$3" >> /tmp/\$\$.email
	echo "Destination Dir:  \$DESTINATION_DIR" >> /tmp/\$\$.email
	echo "Transfer Ended:   \$STOP_TIME" >> /tmp/\$\$.email
	echo "Transfer Started: \$START_TIME" >> /tmp/\$\$.email
	echo "Email Addresses:  \$EMAIL_ADDRESS" >> /tmp/\$\$.email
	echo "This Script:      \`pwd\`/backup.exit_cmds" >> /tmp/\$\$.email
	echo "File Stats:" >> /tmp/\$\$.email
	echo "\$SIZE_DATE_NAME" >> /tmp/\$\$.email
	
	# Send the email
	cat /tmp/\$\$.email | \$MAIL -s "backup: \$1: \$HOST to \$REMOTE_HOST" \$EMAIL_ADDRESS

	# Cleanup the temp file
	rm -f /tmp/\$\$.email
} # send_email

# Capture the start time of the transfer
START_TIME=\`date +%Y\-%b\-%d\ %H\:%M\:%S\ %a\`

if [ "\$EMAIL_ADDRESS" == "" ]; then
   # Try to put an entry into EMAIL_FILE.  This might not make it if "backup" ends before this script starts up.
	echo "\$START_TIME: \$HOST to \$REMOTE_HOST copy of \$2 starting" >> \$EMAIL_FILE
fi

if [ "\$TEST_MODE" == "TRUE" ]; then
	# In test mode create a simple test file and send it over.
   # This will verify that scp works but won't take as much time.

	# Create a test file to send
	echo "----- backup (\$SCRIPT_VER \$SCRIPT_DATE) --------------------------------------------" >| \$\$.test
	echo "TESTING:          \$HOST to \$REMOTE_HOST scp transfer" >> \$\$.test
	echo "File:             \$2.test" >> \$\$.test
	echo "Source Dir:       \$1" >> \$\$.test
	echo "Destination Dir:  \$DESTINATION_DIR" >> \$\$.test
	echo "Transfer Started: \$START_TIME" >> \$\$.test
	echo "Email Addresses:  \$EMAIL_ADDRESS" >> \$\$.test
	echo "This Script:      \`pwd\`/backup.exit_cmds" >> \$\$.test

	# Uncomment first line to trouble shoot scp transfer problems
   #\$SCP -v -F "\$SSH_CONFIG_FILE" "\$\$.test" \$SSH_HOST_NAME:"\$DESTINATION_DIR/\$2.test"
	\$SCP -F "\$SSH_CONFIG_FILE" "\$\$.test" \$SSH_HOST_NAME:"\$DESTINATION_DIR/\$2.test" 2>/dev/null

   # Capture the result of the scp transfer to see if an error occurred
	RESULT=\$?

	# In test mode use passed in values as the file path and name
	SIZE_DATE_NAME="   test mode:  \$1/\$2.test"

	# Clean up the temp file used
	rm -f \$\$.test
else
	# Uncomment first line to trouble shoot scp transfer problems
   #\$SCP -v -F "\$SSH_CONFIG_FILE" "\$1/\$2" \$SSH_HOST_NAME:"\$DESTINATION_DIR/\$2"
	\$SCP -F "\$SSH_CONFIG_FILE" "\$1/\$2" \$SSH_HOST_NAME:"\$DESTINATION_DIR/\$2" 2>/dev/null

   # Capture the result of the scp transfer to see if an error occurred
	RESULT=\$?

	# The next line removes permissions field, the numeric field, and the owner field
	SIZE_DATE_NAME=\`\$LS -lo "\$1/\$2" | sed  -e 's/[^ ]* *[^ ]* *[^ ]*//'\`
fi

# Capture the ending time of the transfer
STOP_TIME=\`date +%Y\-%b\-%d\ %H\:%M\:%S\ %a\`

# If EMAIL_ADDRESS isn't null then an email should be sent.  Include success or failure status.
if [ "\$EMAIL_ADDRESS" == "" ]; then
   if [ \$RESULT -eq 0 ]; then
		# Try to put an info line in EMAIL_FILE
		echo "SUCCESS: \$HOST to \$REMOTE_HOST copy of \$2 succeeded" >> \$EMAIL_FILE
   else
		# Try to put an info line in EMAIL_FILE
		echo "ERROR: \$HOST to \$REMOTE_HOST copy of \$2 failed" >> \$EMAIL_FILE
   fi
else
   if [ \$RESULT -eq 0 ]; then
      send_email SUCCESS "\$2" "\$1"
   else
      send_email ERROR "\$2" "\$1"
   fi
fi

if [ "\$EMAIL_ADDRESS" == "" ]; then
   # Try to put an info line into EMAIL_FILE.  This won't make it into the email or webpage if the "backup"
   # script ends before this script finishes its transfer.
	echo "\$STOP_TIME: \$HOST to \$REMOTE_HOST copy of \$2 ended" >> \$EMAIL_FILE
fi
EOF

# Set execute permissions
chmod 755 backup.exit_cmds

} # make_template_exit_cmds


#------------------------------------------------------------------------------
# make_template_dirlist: Function to create a template backup.dirlist script
function make_template_dirlist () {
local func_name=make_template_dirlist
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside make_template_dirlist";fi
cat << EOF >| backup.dirlist
# Copyright 2004 through 2009 John R Larsen - john@larsen-family.us
#----- backup ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------
# This is the backup.dirlist file that directs backup's operation.  The format
# of this file is important.  Any line with a # as the first character is a
# comment line and is ignored.  Each entry has four lines, which are
# described below:
# 
# Line 1: startup commands
# The first line contains commands that need to be executed before cd'ing to
# the start directory.  Put all commands on one line separating them with
# semicolons.  The starting directory might not be available without these
# commands.  For example, if the directory is in a ClearCase view, the view
# might need to be started before the directory is available.  If no commands
# are needed, then leave this line blank.
# 
# Line 2: starting directory
# The second line is the starting directory.  "backup" starts here and
# recurses the entire directory structure looking for the file "backup.list".
# If found, the contents of backup.list are used to tell "backup" what to
# archive.  (Use "backup -h" to read more about files backup.list,
# backup.enter_cmds, backup.exit_cmds, and backup.no_recurse)
# Spaces in directory names are permissible.  There is no need to put 
# double quotes around the names.  "backup" takes care of that.
# 
# Line 3: archive directory
# The third line is the directory where archives are stored.
# Spaces in directory names are permissible.  There is no need to put 
# double quotes around the names.  "backup" takes care of that.  A remote
# host name can be used for rsync operation.
# 
# Line 4: ending commands
# The fourth line contains commands that need to be executed after the
# backup completes.  Put all commands on one line separating them with
# semicolons.  If no commands are needed, then leave this line blank.
# 
# To make this file easier to read, use a dashed line to separate entries.
# DON'T separate entry lines with comment lines.  Keep all four lines
# together.  When the script finds the first non comment line it assumes the
# next two lines complete the set of four lines.
# 
# IMPORTANT: Make sure there are NO trailing spaces or slashes, else the
# backup will fail.  
# 
# !!! TEST YOUR FILE !!!
# Test your dirlist file using "backup daily -test -f filename" and carefully
# look at the generated email output for correct path names.  "backup" will
# warn you of any trailing slashes or spaces.
# 
# Below is a commented example of an entry:
#----------------------------------------------------------------------------
# commands; to execute; before; the backup; begins
# /path/to/directory/to/start/in
# /path/to/directory/where/archives/are/stored
# commands; to execute; after; the backup; ends
EOF

} # make_template_dirlist


#------------------------------------------------------------------------------
# make_template_copy: Function to create a template backup.webpage script
function make_template_webpage () {
local func_name=make_template_webpage
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside make_template_webpage";fi
cat << EOF >| backup.webpage
#!/bin/bash
# Copyright 2004 through 2009 John R Larsen - john@larsen-family.us
#----- backup ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------
# This is the backup.webpage script called by "backup" to update the website.  The 
# name of the html file is passed in \$1 so that it can be renamed as needed.  This is
# required if multiple sites are reporting backups and all the web pages are in the same 
# directory on the webserver.
#
# The following environment variables are exported by backup and can be used in this script:
#    HOST   Name of the host performing the backup
#    SCP    The full path to the scp program on the host where the test is running
#
# The copying method will vary depending on how to access the web server.  If it 
# is on the same LAN as the machine running the test, then a simple "cp" command
# might work.  If the webserver is on a remote machine then it may be necessary
# to use "scp" to do the copy using already established public/private RSA key
# access.  Examples of both are given below.  

# LAN copy.  This simple line copies the file to the correct webserver directory.
#cp -f \$1 /var/www/html/backups/\${HOST}_backup-\$BACKUP_TYPE.html

# SCP copy.  Use this line if transferring the file to a remote webserver.  The "-F" option
# tells scp to use the named ssh configuration file.  Below are the settings that might
# be in such a file.  See manpage for ssh_config(5) for complete descriptions:
# 
#   Host loader
#   HostName linux-beast
#   Port = 2222
#   UserKnownHostsFile = /home/jlarsen/ssh_tunnel/linux-beast/known_hosts
#   User = jlarsen
#   IdentityFile = /home/jlarsen/ssh_tunnel/linux-beast/id_rsa.fhc-beast
#
# Using the settings above and the private key (with no pass phrase) the transfer can be
# done by a cron job.
#\$SCP -F /path/to/ssh/config/file \$1 loader:/path/to/webserver/directory/\${HOST}_backup-\$BACKUP_TYPE.html 2>/dev/null
EOF

# Set execute permissions
chmod 755 backup.webpage

} # make_template_copy


#------------------------------------------------------------------------------
# make_template_conf: Function to create a template backup.conf file
function make_template_conf () {
local func_name=make_template_conf
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside make_template_conf";fi
cat << EOF >| backup.conf
# Copyright 2004 through 2009 John R Larsen - john@larsen-family.us
#----- backup ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------
# This is the backup.conf file used by "backup" in rsync mode.  It contains "host"
# definitions used for remote rsync operations.  Each "host" specified in the 
# backup.dirlist file must have an entry in this file to work.  Repeat all of the
# entries for each "host" used in your backup.dirlist file.  The directory where the
# private key is located must be accessible only to the owner.  Group and world
# cannot have access.  The key itself must also only be accessible to the owner.
# 
Host = name_used_in_backup_dirlist
HostName = fully_qualified_domain_name
Port = 22
UserKnownHostsFile = /path/to/known_hosts
User = username
IdentityFile = /path/to/private/key/rsa.private
EOF

} # make_template_conf


#------------------------------------------------------------------------------
# read_file: Function that reads a file line by line into array $LINES[].
# The number of lines in the file (and size of array) is put in $NUM_LINES.
# $1 - File to input
function read_file () {
	local func_name=read_file
	if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside $func_name  \$1: $1";fi
	
	# Remove any ^M in case the file is in DOS format which is evil
	sed -e 's///g' < $1 >| /tmp/read_file.$$

	# Save the current value of the IFS variable
	old_ifs=$IFS
	IFS=

	# Use the "read" command to load up the array from the passed file
	NUM_LINES=0
	while
		read LINES[$NUM_LINES]
	do
		let NUM_LINES=NUM_LINES+1
	done </tmp/read_file.$$

   # Restore the original value for IFS
	IFS=$old_ifs

   # Output contents of LINES array if debugging is turned on
	if [ $(($D & 0x80)) -ne 0 ]; then 
		echo "[$LINENO]$func_name> Contents of \$LINES[]"
		local counter=0
		while [ $counter -lt $NUM_LINES ]; do
			echo "LINES[$counter]: ${LINES[$counter]}"
			let counter=counter+1
		done
	fi

	# Cleanup by removing the temporary read_file.$$
	rm -f /tmp/read_file.$$

} # read_file


#------------------------------------------------------------------------------
# recurse_tree: Function that recurses through the directory tree starting
# with directory passed in $1.
function recurse_tree () {
	local func_name=recurse_tree
	cd "$1"
   if [ $? -ne 0 ]; then
      # cd returns non zero value if permission denied to enter directory.  This can happen
      # on a cygwin system where the user might not be running as administrator
      return 0
   fi
	if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside recurse_tree  \$1: $1";fi
	#sleep 1

	# Display recurse level and pwd if debug turned on
	if [ $(($D & 0x20)) -ne 0 ]; then 
		echo "[$LINENO]$func_name> `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a` CALL_LEVEL: $CALL_LEVEL  pwd: `pwd`"
	fi

	# Show contents of directory if debug is turned on
	if [ $(($D & 0x40)) -ne 0 ]; then 
		echo "[$LINENO]$func_name> ls `pwd`"
		$LS -AF "`pwd`"
	fi

	# If file backup.no_recurse exists in the current directory and CALL_LEVEL isn't zero
	# then return.  CALL_LEVEL equal to zero means this is the top level directory.  Not
	# checking for backup.no_recurse file allows having an entry in backup.dirlist that
   # starts recursing in an arbitrary directory even if backup.no_recurse is there.  This
	# makes it possible to have a directory marked backup.no_recurse for a higher level
	# starting point but still use it as a starting directory for a subsequent entry in
	# a backup.dirlist file.
	if [ -e backup.no_recurse ] && [ $CALL_LEVEL -gt 0 ]; then
		if [ $TEST_MODE == "TRUE" ]; then
			echo "backup.no_recurse file found in: `pwd`" | $TEE -a $EMAIL_FILE
		fi
		return 0
	fi

	# Check if recurse level has hit the fail safe value
	if [ $CALL_LEVEL -ge $FAIL_SAFE ]; then
		echo "ERROR [$LINENO]$func_name> Hit FAIL_SAFE level: $FAIL_SAFE  in this directory: `pwd`" | $TEE -a $EMAIL_FILE
		return 1
	fi

	# If file backup.enter_cmds exists in the current directory then source it
	if [ -e backup.enter_cmds ]; then
		# Strip off ^M in case file is in evil DOS format
		sed -e 's///g' < backup.enter_cmds >| enter_cmds
		mv enter_cmds backup.enter_cmds
		if [ $TEST_MODE == "TRUE" ]; then
			echo "backup.enter_cmds file found in: `pwd`" | $TEE -a $EMAIL_FILE
		fi
		source backup.enter_cmds >> $EMAIL_FILE
	fi

	# Check if file "backup.list" exists
	if [ -e backup.list ]; then
		if [ $TEST_MODE == "TRUE" ]; then
			echo "backup.list file found in:       `pwd`" | $TEE -a $EMAIL_FILE
		fi

		# Add an entry to the email file
		echo "----------------------------------------------------------------" >> $EMAIL_FILE
		echo "Backup started at: `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a`" >> $EMAIL_FILE
		echo "Backup file stored in:  $BACKUP_DIR" >> $EMAIL_FILE
		echo "Starting directory:     `pwd`" >> $EMAIL_FILE

		# Remove the first slash in the path, replace all the rest with dots, and replace spaces with circumflex ^
		FILENAME=`pwd | sed  -e 's/\///' -e 's/\//./g' -e 's/ /^/g'`
		# Check backup type
      case $BACKUP_TYPE in
         DAILY)
            FILENAME="${FILENAME}.zip"
            # Capture info about existing backup file for later comparison
            if [ -e "$BACKUP_DIR/$FILENAME" ]; then
               OLD_SIZE_DATE_NAME=`$LS -lo "$BACKUP_DIR/$FILENAME" | sed  -e 's/[^ ]* *[^ ]* *[^ ]*//'`
            fi
            ;;

         WEEKLY)
            FILENAME="${FILENAME}__${BACKUP_DATE}.zip"
            echo "Created:"  >> $EMAIL_FILE
            ;;

         DELTA)
            FILENAME="${FILENAME}__${BACKUP_DATE}.zip"
            ;;

         *) # Unknown backup type!
            echo "ERROR [$LINENO]$func_name> Unknown backup mode detected: $BACKUP_TYPE" | $TEE -a $EMAIL_FILE
            exit 1
            ;;
      esac

		if [ $TEST_MODE == "TRUE" ]; then
         if [ "$BACKUP_TYPE" == "DELTA" ]; then
            SIZE_DATE_NAME="   test mode:  $BACKUP_DIR/$YEAR/$MONTH/$DAY/$FILENAME"
         else
            SIZE_DATE_NAME="   test mode:  $BACKUP_DIR/$FILENAME"
         fi
		else
         # Update the webpage before each backup so incremental progress can be detected during long backups
			web_page

			# Create the zipfile using backup.list to determine files included.  Note that zip doesn't care if
			# backup.list is in unix or dos format, so there is no need to remove ^M from backup.list.
         if [ "$BACKUP_TYPE" == "DELTA" ]; then
            if [ "$DAY" == "$BASELINE_DAY" ]; then
               # Make a full backup on the first day of the month (or day specified by -bd option)
               cat backup.list | $ZIP_PGM -urqy "$BACKUP_DIR/$YEAR/$MONTH/$DAY/$FILENAME" -@
               # The next line removes permissions field, the numeric field, and the owner field
               SIZE_DATE_NAME=`$LS -lo "$BACKUP_DIR/$YEAR/$MONTH/$DAY/$FILENAME" | sed  -e 's/[^ ]* *[^ ]* *[^ ]*//'`
            else
               # Make a delta backup on the other days of the month
               cat backup.list | $ZIP_PGM -urqy -t ${ZIP_MONTH}${ZIP_DAY}${ZIP_YEAR} "$BACKUP_DIR/$YEAR/$MONTH/$DAY/$FILENAME" -@
               if [ -e "$BACKUP_DIR/$YEAR/$MONTH/$DAY/$FILENAME" ]; then
                  # The next line removes permissions field, the numeric field, and the owner field
                  echo "Created:"  >> $EMAIL_FILE
                  SIZE_DATE_NAME=`$LS -lo "$BACKUP_DIR/$YEAR/$MONTH/$DAY/$FILENAME" | sed  -e 's/[^ ]* *[^ ]* *[^ ]*//'`
               else
                  SIZE_DATE_NAME="   No backup file created"
               fi
            fi
         else
            # daily or weekly mode
            cat backup.list | $ZIP_PGM -urqy "$BACKUP_DIR/$FILENAME" -@
            SIZE_DATE_NAME=`$LS -lo "$BACKUP_DIR/$FILENAME" | sed  -e 's/[^ ]* *[^ ]* *[^ ]*//'`
            if [ "$BACKUP_TYPE" == "DAILY" ]; then
               # Determine if the archive was updated or not and output to email accordingly
               if [[ "$OLD_SIZE_DATE_NAME" == "$SIZE_DATE_NAME" ]]; then
                  echo "Archive not changed:" >> $EMAIL_FILE
               else
                  echo "Updated:" >> $EMAIL_FILE
               fi
            fi
         fi
		fi

		echo  "$SIZE_DATE_NAME" >> $EMAIL_FILE
		echo "Backup ended at:   `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a`" >> $EMAIL_FILE

		# If file backup.exit_cmds exists in the directory then source it
		if [ -e backup.exit_cmds ]; then
			# Strip off ^M in case file is in evil DOS format
			sed -e 's///g' < backup.exit_cmds >| exit_cmds
			mv exit_cmds backup.exit_cmds
			if [ $TEST_MODE == "TRUE" ]; then
				echo "backup.exit_cmds file found in:  `pwd`" | $TEE -a $EMAIL_FILE
            if [ "$BACKUP_TYPE" == "DELTA" ]; then
               source backup.exit_cmds "$BACKUP_DIR/$YEAR/$MONTH/$DAY" "$FILENAME" | $TEE -a $EMAIL_FILE &
            else
               source backup.exit_cmds "$BACKUP_DIR" "$FILENAME" | $TEE -a $EMAIL_FILE &
            fi
			else
            if [ "$BACKUP_TYPE" == "DELTA" ]; then
               source backup.exit_cmds "$BACKUP_DIR/$YEAR/$MONTH/$DAY" "$FILENAME" >> $EMAIL_FILE &
            else
               source backup.exit_cmds "$BACKUP_DIR" "$FILENAME" >> $EMAIL_FILE &
            fi
			fi
		fi

		return 0
	fi

	# Getting here means backup.list wasn't found and need to recurse all subdirectories
   # in this directory looking for instances of backup.list

	# Redirect directory listing to a file
	(echo "`$LS -1A .`" >/tmp/filelist) 2>/dev/null 

	# Load array from the file
	read_file /tmp/filelist

	# Test each element of the current directory and recurse into subdirectories
	local count=0
	while [ $count -lt $NUM_LINES ]; do
		if [ -d "${LINES[$count]}" ]; then
			let CALL_LEVEL=CALL_LEVEL+1
			( recurse_tree "${LINES[$count]}" )
			let CALL_LEVEL=CALL_LEVEL-1
		fi
		let count=count+1
	done

	# If file backup.exit_cmds exists in the directory then source it.  No backup operation
	# has been done at this point, so no arguments are passed to backup.exit_cmds.
	if [ -e backup.exit_cmds ]; then
		# Strip off ^M in case file is in evil DOS format
		sed -e 's///g' < backup.exit_cmds >| exit_cmds
		mv exit_cmds backup.exit_cmds
		if [ $TEST_MODE == "TRUE" ]; then
			echo "backup.exit_cmds file found in:  `pwd`" | $TEE -a $EMAIL_FILE
			source backup.exit_cmds | $TEE -a $EMAIL_FILE &
		else
			source backup.exit_cmds >> $EMAIL_FILE &
		fi
	fi

} # recurse_tree


#------------------------------------------------------------------------------
# recurse_tree_rsync: Function that recurses through the directory tree starting
# with directory passed in $1.  This function is only used for rsync backups.
function recurse_tree_rsync () {
	local func_name=recurse_tree_rsync
	cd "$1"
   if [ $? -ne 0 ]; then
      # cd returns non zero value if permission denied to enter directory.  This can happen
      # on a cygwin system where the user might not be running as administrator
      return 0
   fi
	if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside recurse_tree_rsync  \$1: $1";fi
	#sleep 1

	# Display recurse level and pwd if debug turned on
	if [ $(($D & 0x20)) -ne 0 ]; then 
		echo "[$LINENO]$func_name> `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a` CALL_LEVEL: $CALL_LEVEL  pwd: `pwd`"
	fi

	# Show contents of directory if debug is turned on
	if [ $(($D & 0x40)) -ne 0 ]; then 
		echo "[$LINENO]$func_name> ls `pwd`"
		$LS -AF "`pwd`"
	fi

	# If file backup.no_recurse exists in the current directory and CALL_LEVEL isn't zero
	# then return.  CALL_LEVEL equal to zero means this is the top level directory.  Not
	# checking for backup.no_recurse file allows having an entry in backup.dirlist that
   # starts recursing in an arbitrary directory even if backup.no_recurse is there.  This
	# makes it possible to have a directory marked backup.no_recurse for a higher level
	# starting point but still use it as a starting directory for a subsequent entry in
	# a backup.dirlist file.
	if [ -e backup.no_recurse ] && [ $CALL_LEVEL -gt 0 ]; then
		if [ $TEST_MODE == "TRUE" ]; then
			echo "backup.no_recurse file found in: `pwd`" | $TEE -a $EMAIL_FILE
		fi
		return 0
	fi

	# Check if recurse level has hit the fail safe value
	if [ $CALL_LEVEL -ge $FAIL_SAFE ]; then
		echo "ERROR [$LINENO]$func_name> Hit FAIL_SAFE level: $FAIL_SAFE  in this directory: `pwd`" | $TEE -a $EMAIL_FILE
		return 1
	fi

	# Check if file "backup.rsync" exists
	if [ -e backup.rsync ]; then
		if [ $TEST_MODE == "TRUE" ]; then
			echo "backup.rsync file found in:       `pwd`" | $TEE -a $EMAIL_FILE
		fi

		# Remove the first slash in the path, replace all the rest with dots, and replace spaces with circumflex ^
		#DIRNAME=`pwd | sed  -e 's/\///' -e 's/\//./g' -e 's/ /^/g'`
      #DIRNAME="${DIRNAME}.rsync"

		# Add an entry to the email file
		echo "----------------------------------------------------------------" >> $EMAIL_FILE
		echo "rsync backup started at: `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a`" >> $EMAIL_FILE
		echo "Source directory:       `pwd`" >> $EMAIL_FILE
		echo "Destination directory:  $BACKUP_DIR`pwd`" >> $EMAIL_FILE

		if [ $TEST_MODE == "TRUE" ]; then
         echo "   test mode:  $BACKUP_DIR`pwd`" >> $EMAIL_FILE
         DRY_RUN=--dry-run
		else
         DRY_RUN=""
         # Update the webpage before each backup so incremental progress can be detected during long backups
			web_page
      fi

      # Check if $BACKUP_DIR has a colon in it.  If it does that means the destination is a remote host
      echo $BACKUP_DIR | grep ":"
      if [ $? == 0 ]; then
         # The $BACKUP_DIR line from backup.dirlist has a host name that points to an entry in  backup.conf.
         # This allows different remote destinations to be specified for each entry in backup.dirlist.
         # rsync is fussy about how its command line is made. Need to make a temporary
         # script to source so that it handles its options correctly.

         # Need to strip off the remote host and colon to obtain a directory name
         SHORT_BACKUP_DIR=`echo $BACKUP_DIR | sed  -e 's/.*://'`

cat << EOF >| $WORKING_DIR/rsync.sh
#----- backup ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------
# This script auto generated by "backup"
$RSYNC_PGM \
$RSYNC_OPT \
--no-g \
--chmod=ugo=rwX \
--delete \
-e "$SSH -F $WORKING_DIR/backup.conf" \
$RSYNC_FILTER \
$DRY_RUN \
--backup \
--backup-dir=${SHORT_BACKUP_DIR}.$BACKUP_YYMMDDHHMM \
--timeout=$TIMEOUT_VAL \
"`pwd`/" \
"$BACKUP_DIR"
EOF
      else
         # No colon in $BACKUP_DIR means the rsync operation is to a local drive.
         # rsync is fussy about how its command line is made. Need to make a temporary
         # script to source so that it handles its options correctly.
cat << EOF >| $WORKING_DIR/rsync.sh
#----- backup ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------
# This script auto generated by "backup"
$RSYNC_PGM \
$RSYNC_OPT \
--no-g \
--chmod=ugo=rwX \
--delete \
$RSYNC_FILTER \
$DRY_RUN \
--backup \
--backup-dir=${BACKUP_DIR}.$BACKUP_YYMMDDHHMM \
--timeout=$TIMEOUT_VAL \
"`pwd`/" \
"$BACKUP_DIR"
EOF
      fi
      source $WORKING_DIR/rsync.sh >> $EMAIL_FILE
		echo "Rsync backup ended at:   `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a`" >> $EMAIL_FILE

		return 0
	fi

	# Getting here means backup.rsync wasn't found and need to recurse all subdirectories
   # in this directory looking for instances of backup.rsync

	# Redirect directory listing to a file
	(echo "`$LS -1A .`" >/tmp/filelist) 2>/dev/null 

	# Load array from the file
	read_file /tmp/filelist

	# Test each element of the current directory and recurse into subdirectories
	local count=0
	while [ $count -lt $NUM_LINES ]; do
		if [ -d "${LINES[$count]}" ]; then
			let CALL_LEVEL=CALL_LEVEL+1
			( recurse_tree_rsync "${LINES[$count]}" )
			let CALL_LEVEL=CALL_LEVEL-1
		fi
		let count=count+1
	done

} # recurse_tree_rsync


#------------------------------------------------------------------------------
# set_working_dir: Function that sets the WORKING_DIR based on $0
function set_working_dir () {
# Configure working directory
WORKING_DIR=`dirname $0`
if [ "$WORKING_DIR" = "." ]; then
	WORKING_DIR=`pwd`
fi
cd $WORKING_DIR
} # set_working_dir


#------------------------------------------------------------------------------
# main
function main () {
local func_name=main
if [ $(($D & 0x1)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> Inside main";fi

# If no arguments are passed then output the help screen and exit
if [ $# -eq 0 ]; then
	display_help
	exit 1
fi

setup_env
process_command_line $*

# Make sure backup type was on command line
if [ $BACKUP_TYPE == "NOT_SET" ]; then
	display_help
	echo "ERROR [$LINENO]$func_name> Backup type (delta, daily or weekly) not on command line."
	exit 1
fi

# Make sure $BACKUP_LIST exists
if [ ! -e $BACKUP_LIST ]; then
	display_help
	echo "ERROR [$LINENO]$func_name> File $BACKUP_LIST doesn't exist"
	exit 1
fi

# Show environment variable values if verbose level is high enough
if [ $(($D & 0x10)) -ne 0 ]; then 
	display_env
fi

# If running as root then try to remount all the drives to make sure they're all there
UID_VALUE=`id | awk '{print $1}'`
if [ "$UID_VALUE" = "uid=0(root)" ]; then
	umount -a 2>/dev/null
	mount -a 2>/dev/null
fi

# Capture current conditions
echo "------------------------------- backup $SCRIPT_VER $SCRIPT_DATE --------" >| $EMAIL_FILE
echo "$HOST backup log" >> $EMAIL_FILE
echo "Email sent to: $EMAIL_ADDRESS " >> $EMAIL_FILE
echo "Script started at: `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a`" >> $EMAIL_FILE
echo "----------------------------------------------------------------" >> $EMAIL_FILE
echo "Starting uptime, load average, and users:" >> $EMAIL_FILE
echo "`$W`" >> $EMAIL_FILE
echo "----------------------------------------------------------------" >> $EMAIL_FILE
echo "Starting disk usage:" >> $EMAIL_FILE
if [ -e $WORKING_DIR/backup.df ]; then
	source $WORKING_DIR/backup.df >> $EMAIL_FILE
	cd $WORKING_DIR
else
	echo "`$DF`" >> $EMAIL_FILE
fi

# Read in commands from $BACKUP_LIST
read_file $BACKUP_LIST

# Loop through and process all the entries in the $BACKUP_LIST
index=0
while [ $index -lt $NUM_LINES ]; do

	if [ "${LINES[$index]:0:1}" == "#" ]; then
      # Skip comment lines
		let index=index+1
	else
		# Load the three lines into correct variables
		# Execute the command line.  Need to use a temp file and source it.
		echo "${LINES[$index]}" >| /tmp/$$.command
		chmod 700 /tmp/$$.command
		/tmp/$$.command $TEE -a $EMAIL_FILE
		rm -f /tmp/$$.command

		# Get the starting directory for the backup and cd to it
		let index=index+1
		STARTING_DIR=${LINES[$index]}
		# Declare error if trailing space found in starting directory name
		length=${#STARTING_DIR}
		let length=length-1
		if [ "${STARTING_DIR:$length:1}" == " " ]; then
			echo "ERROR in $BACKUP_LIST line $index: Trailing space found in starting directory name: \"$STARTING_DIR\"" | $TEE -a $EMAIL_FILE
			# Adjust index to point to next triplet
			let index=index+3
			continue
		fi
		# Declare error if starting directory doesn't exist
		if [ ! -d "$STARTING_DIR" ]; then
			echo "ERROR in $BACKUP_LIST line $index: Specified starting directory doesn't exist: $STARTING_DIR" | $TEE -a $EMAIL_FILE
			let index=index+3
			continue
		fi
		cd "$STARTING_DIR"
		let index=index+1

		# Get the archive directory where the files are stored
		BACKUP_DIR=${LINES[$index]}
		# Declare error if trailing space found in archive directory name
		length=${#BACKUP_DIR}
		let length=length-1
		if [ "${BACKUP_DIR:$length:1}" == " " ]; then
			echo "ERROR in $BACKUP_LIST line $index: Trailing space found in archive directory name: \"$BACKUP_DIR\"" | $TEE -a $EMAIL_FILE
			# Adjust index to point to next triplet
			let index=index+2
			continue
		fi

      # Only check for archive directory if not doing an RSYNC backup
		if [ "$BACKUP_TYPE" != "RSYNC" ]; then
         # Declare error if archive directory doesn't exist
         if [ ! -d "$BACKUP_DIR" ]; then
            echo "ERROR in $BACKUP_LIST line $index: Specified archive directory doesn't exist: $BACKUP_DIR" | $TEE -a $EMAIL_FILE
            let index=index+2
            # Skip to the next starting directory in $BACKUP_LIST
            continue
         fi
      fi
		let index=index+1

      # delta mode requires subdirectories be added to $BACKUP_DIR based on day of the month
      # Files are stored in .../year/month/day.  Create directories if they don't exist.
		if [ "$BACKUP_TYPE" == "DELTA" ]; then
         mkdir -p ${BACKUP_DIR}/${YEAR}/${MONTH}/${DAY}
      fi

		# Getting here means the starting and archive directories exist and the backup can proceed.
		# Start the backup.  This must be done in a sub shell so that environment variables
		# aren't overwritten as the function recurses.
		if [ "$BACKUP_TYPE" == "RSYNC" ]; then
         # rsync backups behave differently and use their own recursive function
         ( recurse_tree_rsync "$STARTING_DIR" )
      else
         # Use this function for all the other backup types
         ( recurse_tree "$STARTING_DIR" )
      fi

		# Execute the end commands line.  Need to use a temp file and source it.
		echo "${LINES[$index]}" >| /tmp/$$.command
		chmod 700 /tmp/$$.command
		/tmp/$$.command $TEE -a $EMAIL_FILE
		rm -f /tmp/$$.command
		let index=index+1

	fi
done

echo "----------------------------------------------------------------" >> $EMAIL_FILE
echo "Ending uptime, load average, and users:" >> $EMAIL_FILE
echo "`$W`" >> $EMAIL_FILE
echo "----------------------------------------------------------------" >> $EMAIL_FILE
echo "Ending disk usage:" >> $EMAIL_FILE
if [ -e $WORKING_DIR/backup.df ]; then
	source $WORKING_DIR/backup.df >> $EMAIL_FILE
	cd $WORKING_DIR
else
	echo "`$DF`" >> $EMAIL_FILE
fi
echo "----------------------------------------------------------------" >> $EMAIL_FILE
echo "Script ended at:   `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a`" >> $EMAIL_FILE
echo "----------------------------------------------------------------" >> $EMAIL_FILE

# Return to the starting directory
cd $WORKING_DIR

# Send the email if EMAIL_ADDRESS isn't null
if [ "$EMAIL_ADDRESS" != "" ]; then
	# If an error occurred then put ERROR in the email subject line
	error_found=`cat $EMAIL_FILE | grep ERROR`
	if [ "$error_found" == "" ]; then
		cat $EMAIL_FILE | $MAIL -s "backup: $HOST" $EMAIL_ADDRESS
	else
		cat $EMAIL_FILE | $MAIL -s "backup: ERROR! $HOST" $EMAIL_ADDRESS
	fi
fi

# Copy the webpage over if enabled
web_page

} # main

set_working_dir
set_debug_level
get_version
main $*
