488 lines
No EOL
14 KiB
Bash
Executable file
488 lines
No EOL
14 KiB
Bash
Executable file
#!/bin/bash
|
|
#
|
|
# egbackup, copyright 2005 egressive limited, www.egressive.com
|
|
#
|
|
# this script runs a series of daily backup files, one after another
|
|
#
|
|
#==========================================
|
|
# The Variables - these must be define
|
|
# before they're referenced
|
|
#==========================================
|
|
VERSION="0.1"
|
|
#EGBU_DIR=/etc/egscripts/egbackup
|
|
EGBU_NAME=`basename $0`
|
|
#echo $EGBU_NAME
|
|
EGBU_DIR=/etc/egscripts/egbackup
|
|
EGBU_CMD=$EGBU_DIR/egbackup
|
|
EGBU_CONF_DIR=$EGBU_DIR/conf
|
|
SITE_CONF=$EGBU_CONF_DIR/site.conf
|
|
BU_EXCLUDES=$EGBU_CONF_DIR/.excludes
|
|
#
|
|
# this provides values for MACHINE_NAME and EMAIL
|
|
. $SITE_CONF
|
|
#
|
|
LOGS=/var/log
|
|
LOG=$LOGS/egbackup.log
|
|
# required programs
|
|
MAIL=`which mail`
|
|
GREP=`which grep`
|
|
LS=`which ls`
|
|
ID=`which id`
|
|
DATE=`which date`
|
|
DF=`which df`
|
|
GZIP=`which gzip`
|
|
RM=`which rm`
|
|
LN=`which ln`
|
|
CUT=`which cut`
|
|
TOUCH=`which touch`
|
|
# determine today's date
|
|
TODAY=`$DATE '+%Y-%m-%d-%a'`
|
|
# determine today's date
|
|
NOW=`$DATE '+%H-%M-%S'`
|
|
# a timestamp for logging purposes
|
|
TIMESTAMP=`$DATE '+%Y-%m-%d %H:%M.%S'`
|
|
# temporary holding point for email
|
|
TMP_DIR=/tmp
|
|
TMP_EMAIL=$TMP_DIR/$EGBU_NAME"_tmp_email_"$TODAY.$TIME
|
|
#
|
|
# for pruning old backups
|
|
#
|
|
# build list of possible pruning candidates
|
|
BU_SUFFIX="tgz"
|
|
LIST_SUFFIX="list"
|
|
#
|
|
# tar command
|
|
TAR=`which tar`
|
|
# tar command options
|
|
# c = create
|
|
# v = verbose (for the purpose of recording a list of files)
|
|
# f = save to file rather than pipe
|
|
# z = compress with gzip
|
|
# W = verify archive against file system on completion
|
|
#FLAGS=" cvfzW "
|
|
FLAGS=" cvfz "
|
|
# wc (word count) command
|
|
WC=`which wc`
|
|
# awk command
|
|
AWK=`which awk`
|
|
# don't worry, this is just used to determine if the
|
|
# disk is installed - it's not going to change the partition table!
|
|
FDISK=`which fdisk`
|
|
#
|
|
#==========================================
|
|
# The Functions - these have to be defined
|
|
# before they're called! Note, refer
|
|
# to them without the "()" and provide
|
|
# up to one argument, referred to as "$@"
|
|
# in the function...
|
|
#==========================================
|
|
#
|
|
# function to direct a message...
|
|
message() {
|
|
#
|
|
# a timestamp for logging purposes
|
|
timestamp
|
|
if test -w $LOG ; then
|
|
echo "$EGBU_NAME: $TIMESTAMP $@" >> $LOG
|
|
fi
|
|
if test -w $TMP_EMAIL ; then
|
|
echo "$EGBU_NAME: $TIMESTAMP $@" >> $TMP_EMAIL
|
|
fi
|
|
verbose "$TIMESTAMP $@"
|
|
}
|
|
#
|
|
# function to direct a message...
|
|
verbose() {
|
|
if test $VERBOSE ; then
|
|
echo "$@"
|
|
fi
|
|
}
|
|
#
|
|
# insert a blank line into the log and on the console
|
|
insert_blank() {
|
|
echo "" >> $TMP_EMAIL
|
|
verbose ""
|
|
}
|
|
#
|
|
# update date and time info..
|
|
today() {
|
|
# determine today's date
|
|
TODAY=`$DATE '+%Y-%m-%d-%a'`
|
|
}
|
|
now() {
|
|
# determine today's date
|
|
NOW=`$DATE '+%H-%M-%S'`
|
|
}
|
|
timestamp() {
|
|
# a timestamp for logging purposes
|
|
TIMESTAMP=`date '+%Y-%m-%d %H:%M.%S'`
|
|
}
|
|
#
|
|
# create the temporary email file
|
|
create_tmp_email() {
|
|
if test -d $TMP_DIR ; then
|
|
if test -w $TMP_DIR ; then
|
|
touch $TMP_EMAIL 2>&1
|
|
else
|
|
error "Email tmp directory $TMP_DIR is not writable"
|
|
fi
|
|
else
|
|
error "Email tmp directory $TMP_DIR does not exist"
|
|
fi
|
|
|
|
if test -w $TMP_EMAIL ; then
|
|
message "created temporary email $TMP_EMAIL"
|
|
else
|
|
error "Failed to create temporary email $TMP_EMAIL"
|
|
fi
|
|
}
|
|
#
|
|
# send the contents of the temporary file to the
|
|
# designated report recipient
|
|
send_email_report() {
|
|
if test -f $TMP_EMAIL ; then
|
|
message "sending email report to $EMAIL_TO"
|
|
if test $ERROR_STATUS == 1 ; then
|
|
EMAIL_SUBJ="[ERROR] $MACHINE_NAME Backup - $PERIOD"
|
|
else
|
|
EMAIL_SUBJ="[SUCCESS] $MACHINE_NAME Backup Report - $PERIOD"
|
|
fi
|
|
# check space again to see how close things are to full
|
|
|
|
message "Printing disk space for reference purposes:"
|
|
DISK_SPACE=`$DF`
|
|
message "$DISK_SPACE"
|
|
# send it to each email address in the list...
|
|
for EMAIL_ADD in $EMAIL_TO
|
|
do
|
|
RES=`$MAIL -s "$EMAIL_SUBJ" $EMAIL_ADD < $TMP_EMAIL`
|
|
if test -z $RES ; then
|
|
if test $ERROR_STATUS == 1 ; then
|
|
message "Error email report successfully sent to $EMAIL_ADD"
|
|
else
|
|
message "Email report successfully sent to $EMAIL_ADD"
|
|
fi
|
|
else
|
|
if ! test $ERROR_STATUS == 1 ; then
|
|
error "Email report send to $EMAIL_ADD failed with this message: $RES"
|
|
fi
|
|
fi
|
|
done
|
|
if test -w $TMP_EMAIL ; then
|
|
$RM $TMP_EMAIL 2>&1
|
|
else
|
|
if ! test $ERROR_STATUS == 1 ; then
|
|
error "Failed to remove email message file, $TMP_EMAIL: permission denied."
|
|
fi
|
|
fi
|
|
if test -f $TMP_EMAIL ; then
|
|
error "Failed to remove email message file $TMP_EMAIL for an unknown reason"
|
|
else
|
|
message "successfully removed temporary email $TMP_EMAIL"
|
|
fi
|
|
else
|
|
if ! test $ERROR_STATUS == 1 ; then
|
|
error "Email message file, $TMP_EMAIL, does not exist."
|
|
fi
|
|
fi
|
|
}
|
|
#
|
|
# build list of backup configuration files
|
|
get_confs() {
|
|
CONFS=`$LS -1 $EGBU_CONF_DIR/$CONF_ROOT*.conf 2>&1`
|
|
TEST=`echo $CONFS | $GREP -c "No such file or directory" -`
|
|
#TEST2=`echo $CONFS | $GREP -c "$EGBU_CONF_DIR" -`
|
|
if test $TEST == 1 ; then
|
|
error "No configuration files found with root $CONF_ROOT in $EGBU_CONF_DIR"
|
|
fi
|
|
#echo "CONFS = $CONFS"
|
|
}
|
|
#
|
|
# function to direct an error...
|
|
error() {
|
|
#
|
|
# recognise that the system experienced
|
|
# an error and send out a special email, and halt
|
|
# the program!
|
|
timestamp
|
|
ERROR_STATUS=1
|
|
#
|
|
# get current user details
|
|
USER_DETAILS=`$ID`
|
|
if test -w $LOG ; then
|
|
echo "$EGBU_NAME: **ERROR** $TIMESTAMP $@" >> $LOG
|
|
echo "$EGBU_NAME: user details - $USER_DETAILS" >> $LOG
|
|
fi
|
|
if test -w $TMP_EMAIL ; then
|
|
echo "$EGBU_NAME: **ERROR** $TIMESTAMP $@" >> $TMP_EMAIL
|
|
echo " user details: $USER_DETAILS" >> $LOG
|
|
fi
|
|
verbose "$TIMESTAMP **ERROR** $@"
|
|
verbose " user details: $USER_DETAILS"
|
|
send_email_report
|
|
exit 1
|
|
}
|
|
#
|
|
# build excludes list
|
|
build_excludes() {
|
|
message "building excludes"
|
|
# the following is required to avoid expanding wildcards...
|
|
# see man bash, search on GLOBIGNORE, for more info
|
|
GLOBIGNORE=*
|
|
if test -f $BU_EXCLUDES ; then
|
|
rm $BU_EXCLUDES
|
|
fi
|
|
$TOUCH $BU_EXCLUDES
|
|
for PAT in $EXCLUDE
|
|
do
|
|
echo $PAT >> $BU_EXCLUDES
|
|
done
|
|
echo "*/$NO_BACKUP_DIR/*" >> $BU_EXCLUDES
|
|
|
|
# EXCLUDES=
|
|
# for PAT in $EXCLUDE
|
|
# do
|
|
# EXCLUDES="$EXCLUDES --exclude=\"$PAT\""
|
|
# done
|
|
# EXCLUDES="$EXCLUDES --exclude=\"*/$NO_BACKUP_DIR\""
|
|
#
|
|
# echo */$NO_BACKUP_DIR >> $EXCLUDES_FILE
|
|
|
|
# also required - see above
|
|
GLOBIGNORE=
|
|
#echo "Excludes = $EXCLUDES"
|
|
}
|
|
#
|
|
# delete old backups
|
|
delete_old() {
|
|
#
|
|
if test -n $BU_FROOT && test -n $BU_TO_KEEP ; then
|
|
# pattern to search for to build the list...
|
|
PATTERN="$BU_DIR/$BU_FROOT.*"
|
|
# build the list, with the suffix...
|
|
PRUNEABLES=`$LS -1t $PATTERN.$BU_SUFFIX 2>&1`
|
|
#echo "PRUNEABLES=$PRUNEABLES"
|
|
TEST=`echo $PRUNEABLES | $GREP -c "No such file or directory" -`
|
|
if ! test $TEST == 1 ; then
|
|
message "pruning older files based on $PATTERN.$BU_SUFFIX"
|
|
message "keeping last $BU_TO_KEEP backups"
|
|
#
|
|
# set counter
|
|
NUM=0
|
|
# go through the list of files and remove those we don't want
|
|
for PRUNEABLE in $PRUNEABLES
|
|
do
|
|
NUM=$(($NUM + 1))
|
|
if test $NUM -gt $BU_TO_KEEP ; then
|
|
message "deleting $PRUNEABLE and associated list files"
|
|
BASE=`basename $PRUNEABLE .$BU_SUFFIX`
|
|
if test $DRY_RUN == 1 ; then
|
|
message "would delete $BU_DIR/$BASE.$BU_SUFFIX and associated list file"
|
|
else
|
|
if [ -e "$BU_DIR/$BASE.$BU_SUFFIX" ]; then
|
|
$RM $BU_DIR/$BASE.$BU_SUFFIX 2>&1
|
|
elif [ -e "$BU_DIR/$BASE.$LIST_SUFFIX" ]; then
|
|
$RM $BU_DIR/$BASE.$LIST_SUFFIX 2>&1
|
|
elif [ -e "$BU_DIR/$BASE.$LIST_SUFFIX.gz" ]; then
|
|
$RM $BU_DIR/$BASE.$LIST_SUFFIX.gz 2>&1
|
|
fi
|
|
fi
|
|
else
|
|
message "keeping $PRUNEABLE"
|
|
fi
|
|
done
|
|
else
|
|
message "no pruneable files found based on pattern: $PATTERN.$BU_SUFFIX"
|
|
fi
|
|
else
|
|
message "keeping older backups, missing backup_root_filename..."
|
|
fi
|
|
}
|
|
#
|
|
#
|
|
check_space() {
|
|
RES=`$DF -h`
|
|
#message "$RES"
|
|
TEST=0
|
|
PCENT="100%"
|
|
TEST=`echo "$RES" | $GREP -c "$PCENT"`
|
|
if test $TEST == 1 ; then
|
|
PART_LINE=`echo "$RES" | $GREP "$PCENT"`
|
|
# get the device name which is showing $PCENT
|
|
TEST=`echo $PART_LINE | $CUT -f 1 -d ' '`
|
|
# if it has a partition number in the name, it's probably a block
|
|
# device rather than, say, a CDROM...
|
|
TEST2=`echo $TEST | $GREP -c [0-9]`
|
|
echo "Test2 = $TEST2"
|
|
if test $TEST2 == 1 ; then
|
|
error "Partition $TEST is full - detail: $PART_LINE"
|
|
else
|
|
message "false alarm - it's only the $TEST volume..."
|
|
fi
|
|
fi
|
|
}
|
|
#
|
|
# this is where we do all the backup-related stuff - what ebu used to do...
|
|
do_backup() {
|
|
#
|
|
# set up filenames for backup files
|
|
today # update date stamp
|
|
now # update time stamp
|
|
BU_FILE="$BU_FROOT.$TODAY.$NOW.$BU_SUFFIX"
|
|
BU_LIST="$BU_FROOT.$TODAY.$NOW.$LIST_SUFFIX"
|
|
message "will create backup files: $BU_FILE, $BU_LIST"
|
|
#
|
|
delete_old # delete old backups
|
|
check_space # see how much space is left in backup area
|
|
build_excludes # build list of files to exclude
|
|
BU_CONF_NAME=`basename $BU_CONF`
|
|
#echo "CURRENT_LINK = $CURRENT_LINK"
|
|
if test -d $BU_DIR ; then
|
|
if ! test -w $BU_DIR ; then
|
|
error "Backup directory, $BU_DIR, not writable"
|
|
fi
|
|
else
|
|
error "Backup directory, $BU_DIR, doesn't exist"
|
|
fi
|
|
#BU_CMD="nice -n $NICE $TAR $FLAGS $BU_DIR/$BU_FILE $EXCLUDES $FILES"
|
|
BU_CMD="nice -n $NICE $TAR $FLAGS $BU_DIR/$BU_FILE -X $BU_EXCLUDES $FILES"
|
|
#echo "DR=$DRY_RUN"
|
|
if test $DRY_RUN == 1 ; then
|
|
message "dry run, not doing backup"
|
|
message "if we were, we'd run $BU_CMD"
|
|
else
|
|
if test ! $CURRENT_LINK = "none" ; then
|
|
message "removing link: $BU_DIR/$CURRENT_LINK"
|
|
# first remove any existing link
|
|
if [ -h "$BU_DIR/$CURRENT_LINK.$BU_SUFFIX" ]; then
|
|
$RM $BU_DIR/$CURRENT_LINK.$BU_SUFFIX 2>&1
|
|
fi
|
|
fi
|
|
message "Executing backup command: $BU_CMD"
|
|
RES=`echo $GLOBIGNORE > $BU_DIR/$BU_LIST; $BU_CMD >> $BU_DIR/$BU_LIST`
|
|
if test "$?" -ne "0" ; then
|
|
message "-------Backup ended with error, error: $? ----------"
|
|
error "TAR EXIT CODE: $?"
|
|
else
|
|
message "+++++++Backup $BU_FILE successfully finished++++++++"
|
|
NUM_FILES=`$WC $BU_DIR/$BU_LIST | $AWK '{print $1}'`
|
|
message "Backed up $NUM_FILES files..."
|
|
BYTES=`$LS -l $BU_DIR/$BU_FILE | $AWK '{print $5;}'`
|
|
KBYTES=$(($BYTES/1024))
|
|
MBYTES=$(($KBYTES/1024))
|
|
message "Backup size: $MBYTES MB ($KBYTES K)"
|
|
# echo "success: $NUM_FILES backed up $NUM_FILES in $MBYTES MB ($KBYTES K)" > $STAT_DIR/$NAME-$BU_CONF_NAME
|
|
# if the variable $CURRENT_LINK is defined in the .conf file, make
|
|
# a link to it.
|
|
if test ! $CURRENT_LINK = "none" ; then
|
|
message "Linking $BU_DIR/$CURRENT_LINK to $BU_DIR/$BU_FILE..."
|
|
# create a new link to the current archive
|
|
RES=`$LN -f $BU_DIR/$BU_FILE $BU_DIR/$CURRENT_LINK.$BU_SUFFIX 2>&1`
|
|
if test "$?" -ne "0" ; then
|
|
error "Linking $BU_DIR/$BU_FILE to $BU_DIR/$CURRENT_LINK.$BU_SUFFIX failed"
|
|
fi
|
|
fi
|
|
if test -f "$BU_DIR/$BU_LIST.gz" ; then
|
|
$RM $BU_DIR/$BU_LIST.gz
|
|
fi
|
|
$GZIP $BU_DIR/$BU_LIST
|
|
fi
|
|
fi
|
|
}
|
|
#========================================
|
|
# The Error Checking
|
|
#========================================
|
|
#
|
|
# first, check that all the necessary files and stuff are there and usable
|
|
#
|
|
# if the log doesn't exist, create it
|
|
if ! test -f $LOG ; then
|
|
message "creating non-existent log file $LOG"
|
|
RET=`touch $LOG 2>&1 /dev/null`
|
|
TEST=`echo $RET | $GREP -c "Permission denied" -`
|
|
if test $TEST == 1 ; then
|
|
error "Failed to create log file $LOG, user cannot write to that directory"
|
|
fi
|
|
fi
|
|
# checking whether it's there an is writable
|
|
if ! test -w $LOG ; then
|
|
error "Log file $LOG not writable"
|
|
fi
|
|
|
|
#========================================
|
|
# The Functional Part of the Script
|
|
#========================================
|
|
# set variable defaults
|
|
ERROR_STATUS=0 # initially assume no errors
|
|
VERBOSE=0 # initially assume quiet output
|
|
CONF_ROOT=1 # set it to something we know so we can test if it's changed
|
|
DRY_RUN=0 # assume this is not a dry run...
|
|
CURRENT_LINK="none"
|
|
MODE=help
|
|
# process any arguments
|
|
while test $# -ne 0 ; do # while there are arguments
|
|
message "argument: $1"
|
|
case $1 in
|
|
--verbose|-v)
|
|
verbose "setting verbostity to ON"
|
|
VERBOSE=1
|
|
;;
|
|
--dryrun)
|
|
verbose "setting dryrun to ON"
|
|
DRY_RUN=1
|
|
;;
|
|
*)
|
|
TEST=`echo $1 | $GREP -c '-'`
|
|
if ! test $TEST == 1 ; then
|
|
CONF_ROOT=$1
|
|
PERIOD=$CONF_ROOT
|
|
fi
|
|
;;
|
|
esac
|
|
shift
|
|
done
|
|
if test $CONF_ROOT == 1 ; then
|
|
MODE=help
|
|
verbose "showing help, because we didn't get a CONF_ROOT value"
|
|
else
|
|
MODE=run
|
|
fi
|
|
#
|
|
# set things in motion based on the script arguments
|
|
case $MODE in
|
|
run)
|
|
create_tmp_email
|
|
get_confs
|
|
# run backup for each file
|
|
for BU_CONF in $CONFS
|
|
do
|
|
message "do backup with conf: $BU_CONF"
|
|
. $BU_CONF # grab the current variables for this conf file...
|
|
do_backup
|
|
# put a space in the email to separate tasks
|
|
insert_blank
|
|
done
|
|
#
|
|
# send the resulting email
|
|
send_email_report
|
|
;;
|
|
help)
|
|
if test $CONF_ROOT; then
|
|
echo "error: please specify a period for egbackup"
|
|
fi
|
|
echo ""
|
|
echo "$EGBU_NAME, version $VERSION, copyright 2005 Egressive Ltd, www.egressive.com"
|
|
echo "==================="
|
|
echo "usage: $EGBU_NAME period"
|
|
echo " where 'period' is one of hourly, daily, weekly, monthly, yearly, etc."
|
|
echo " corresponding to named configurations, e.g. daily-backup.conf, in the "
|
|
echo " conf dir, currently set to $EGBU_CONF_DIR"
|
|
echo "== other options =="
|
|
echo "-v or --verbose - give extra feedback on progress to stdout"
|
|
echo "-c or --configroot config_filename - root for configuration filenames"
|
|
echo "--dryrun - do everything else, but *don't* delete anything or run the backup"
|
|
echo ""
|
|
;;
|
|
esac
|
|
|
|
exit 0 |