#!/bin/bash
# $Id: diskusage,v 1.11 2005-08-03 19:41:34 jlarsen Exp $
# Copyright 2004 John R Larsen - theClaw56@larsen-family.us
# Free for personal use.  Contact author for commercial use.
#
# Description: bash script used to capture disk usage and update a webpage or
# send email with the results.  Use "diskusage -h" for help.
#

#-------------------------------------------------------------------------------
# get_version.  Function that parses script version and date info from the RCS Id line.
function get_version () {
   SCRIPT_VER="v`echo '$Id: diskusage,v 1.11 2005-08-03 19:41:34 jlarsen Exp $' | awk '{print $3}'`"
   SCRIPT_DATE="`echo '$Id: diskusage,v 1.11 2005-08-03 19:41:34 jlarsen Exp $' | awk '{print $4}'`"
} # get_version


#------------------------------------------------------------------------------
# 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) ---------------------------------------
Debug is enabled in one of three ways in decending order of precedence: 

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

Debug output can be changed "on the fly" by changing the contents of
"debug_flags", which is read each time pt goes through its while loop.

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

80000000 - Don't output the "Debug flags changed" messages in "set_debug_level"
EOF
} # display_debug_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 DISKUSAGE_DEBUG_FLAGS if it exists, 
# which overrides debug_flags file if it exists.
# Check one time if DISKUSAGE_DEBUG_FLAGS env variable exists and use it if it does
if [ "${ENV_VAR_TESTED:=FALSE}" = "FALSE" ]; then
	ENV_VAR_TESTED=TRUE
	D=${DISKUSAGE_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.8*) # Solaris 8
      export DF="/usr/ucb/df"
      export DU="/usr/bin/du"
      export HOST=`/usr/ucb/hostname`
      export LS=/bin/ls
      export MAIL=/usr/ucb/mail
      export SCP=/opt/bin/scp
      export TEE=/usr/bin/tee
		;;

	2.4*) # Mandrake Linux 8.x and 9.x, Redhat Linux Work Station Enterprise 3
      export DF="/bin/df -k"
      export DU="/usr/bin/du"
      export HOST=`/bin/hostname`
      export LS=/bin/ls
      export MAIL=/bin/mail
      export SCP=/usr/bin/scp
      export TEE=/usr/bin/tee
		;;

	2.6*) # Mandrake Linux 10.x
      export DF="/bin/df -k"
      export DU="/usr/bin/du"
      export HOST=`/bin/hostname`
      export LS=/bin/ls
      export MAIL=/bin/mail
      export SCP=/usr/bin/scp
      export TEE=/usr/bin/tee
		;;

	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
      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

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

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

# Set the default values of all environment variables here
NUM_REPORTS=10
export WORKING_DIR=`pwd`
export CURRENT_DATA=$WORKING_DIR/current_data
export HTML_FILE=$WORKING_DIR/diskusage.html
# The default for EMAIL_ADDRESS can be overwritten using the -e command line option
EMAIL_ADDRESS=""

YES=1
NO=0
TRUE=1
FALSE=0
} # 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 "CURRENT_DATA:         $CURRENT_DATA"
	echo "HTML_FILE:            $HTML_FILE"
	echo "NUM_REPORTS           $NUM_REPORTS"
	echo "-----------------------------------------------------------"
	return 0
} # display_env


#------------------------------------------------------------------------------
# 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
----- diskusage ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------
usage:
diskusage [ options ]

Options:
-d 0xN   Debug flags value.  Use the "-hd" option to see how to use them.
-e adr   Email address (Default is none)
         (Separate multiple email addresses with commas and no spaces)
-h       Help screen
-hd      Help Debug. This describes how the builtin debugging works.
-hs      Help setup. This gives detailed operating and setup instructions.
-n num   Number of previous reports to include (Default: $NUM_REPORTS)

         You MUST modify these scripts for your situation!
-mdf     Make template diskusage.df script
-mw      Make template diskusage.webpage script

EOF
} # display_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 captures the disk usage and reports it via email or by updating a webpage.


EMAIL:
"diskusage" 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.  


WEBPAGE UPDATE:
"diskusage" can update a webpage.  If the file "diskusage.webpage" exists in the same
directory as the diskusage script, it is used to perform the update.  Use "diskusage -mw"
to create a template of diskusage.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 each time diskusage runs.


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


CRON OPERATION:
It is intended that diskusage be run as a cron task.  A typical crontab line is shown
below.  

01 03 * * * /root/diskusage/diskusage -e theClaw56@comcast.net


SUPPORTED PLATFORMS:
This script has been used successfully on the following systems:
Solaris 8 using bash v2.03.0(1)
Mandrake Linux 9.1 using bash v2.05b.0(1)

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

EOF
} # display_setup_help


#------------------------------------------------------------------------------
# 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
		-d) #debug mode
		   # Command line -d overrides environment variable DISKUSAGE_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 ;;

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

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

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

		-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;;

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

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

		-n) #number of previous reports to keep
			if [ $(($D & 0x2)) -ne 0 ]; then echo "[$LINENO]$func_name:$$> In case -n"; fi
			NUM_REPORTS=$2
			shift ;;

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

	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 diskusage.webpage exists then the web page is created and diskusage.webpage
# is called with the name of the web page in $1.  The diskusage.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 "diskusage -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 diskusage.webpage exists and exit if it doesn't
if [ ! -e $WORKING_DIR/diskusage.webpage ]; then return; fi

# Getting here means diskusage.webpage exists

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

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

# Append previous reports if they exist
let count=1
while [ $count -le $NUM_REPORTS ]; do
	if [ -e previous_data.$count ]; then
		echo "<h1> Previous diskusage report $count</h1>" >> $HTML_FILE
		echo "<pre>" >> $HTML_FILE
		cat previous_data.$count >> $HTML_FILE
		echo "</pre>" >> $HTML_FILE
	fi
	let count=count+1
done

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

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

} # web_page


#------------------------------------------------------------------------------
# make_template_df: Function to create a template diskusage.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 >| diskusage.df
#!/bin/bash
# Copyright 2004 John R Larsen - theClaw56@larsen-family.us
#----- diskusage ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------
# This is the diskusage.df script called by "diskusage" to 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 "diskusage" 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.
#
# Note that you can actually put any commands in this script for which you want to capture
# the output.  For example, you could also put in the "w" command to get a current listing
# of users and machine state.
#
# The following environment variables are exported by diskusage and can be used in this script:
#    DF     The full path to the df program on the host where the diskusage is running
#    DU     The full path to the du program on the host where the diskusage is running
#
\$DF
EOF

# Set execute permissions
chmod 755 diskusage.df

} # make_template_df


#------------------------------------------------------------------------------
# make_template_copy: Function to create a template diskusage.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 >| diskusage.webpage
#!/bin/bash
# Copyright 2004 John R Larsen - theClaw56@larsen-family.us
#----- diskusage ($SCRIPT_VER $SCRIPT_DATE) --------------------------------------------
# This is the diskusage.webpage script called by "diskusage" 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 diskusage and all the web pages are in the same 
# directory on the webserver.
#
# The following environment variables are exported by diskusage and can be used in this script:
#    HOST   Name of the host performing the diskusage
#    SCP    The full path to the scp program on the host where the script 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 script, 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/diskusage/\${HOST}_diskusage.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}_diskusage.html 2>/dev/null
EOF

# Set execute permissions
chmod 755 diskusage.webpage

} # make_template_copy


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

setup_env
process_command_line $*

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

# Bump all the previous reports by one
let count=$NUM_REPORTS-1
new_num=$NUM_REPORTS
while [ $count -gt 0 ]; do
	if [ -e previous_data.$count ]; then
		mv previous_data.$count previous_data.$new_num
	fi
	let count=count-1
	let new_num=new_num-1
done

# Save the previous report if it exists
if [ -e $CURRENT_DATA ]; then
	mv $CURRENT_DATA previous_data.1
fi

# Capture current disk usage
echo "------------------------------- diskusage $SCRIPT_VER $SCRIPT_DATE --------" >| $CURRENT_DATA
echo "Disk usage report generated by $HOST" >> $CURRENT_DATA
echo "Email sent to: $EMAIL_ADDRESS " >> $CURRENT_DATA
echo "Script started at: `date +%Y\-%b\-%d\ %H\:%M\:%S\ %a`" >> $CURRENT_DATA
echo "----------------------------------------------------------------" >> $CURRENT_DATA
echo "Disk usage:" >> $CURRENT_DATA
if [ -e $WORKING_DIR/diskusage.df ]; then
	source $WORKING_DIR/diskusage.df >> $CURRENT_DATA
else
	echo "`$DF`" >> $CURRENT_DATA
fi

# Return to the starting directory
cd $WORKING_DIR

# Send the email if EMAIL_ADDRESS isn't null
if [ "$EMAIL_ADDRESS" != "" ]; then

	# Create the body of the email from current data and previous data
	echo "CURRENT DISK USAGE:" >| diskusage.email
	cat $CURRENT_DATA >> diskusage.email

   # Append previous reports if they exist
	let count=1
	while [ $count -le $NUM_REPORTS ]; do
		if [ -e previous_data.$count ]; then
			echo "" >> diskusage.email
			echo "" >> diskusage.email
			echo "PREVIOUS DISK USAGE REPORT $count:" >> diskusage.email
			cat previous_data.$count >> diskusage.email
		fi
		let count=count+1
	done

	# Send the email 
	cat diskusage.email | $MAIL -s "diskusage: generated by $HOST" $EMAIL_ADDRESS
fi

# Copy the webpage over if enabled
web_page

} # main

set_working_dir
set_debug_level
get_version
main $*
