#! /bin/bash
#
# Create a full backup of dynamic web site based on apache and MySQL
# Push it on remote server by scp
# Manage backup file aging

# Full documentation on www.fridu.org -> documentation section (wiki)
 
# Author Dominique Le Foll  dominig@fridu.co.uk
# License GNU
# July 2006
# V 1.1
#

#-------------------------------------------------------------------------
#
# Constant and Variables
#
#-------------------------------------------------------------------------
# Site name as printed on reports
readonly SITENAME="my-great-site.eu"
# space separated list of receiver of mail report 
readonly REPORT_TO="admin@mydomain.eu"
# Number of day to keep archived data
readonly DAYS_ARCHIVED="15"

# Can be a list of dir separated by spaces
readonly APACHE_CONF_DIRS="/etc/apache2"
readonly APACHE_DATA_DIRS="/raid1/www/htdocs /raid1/www/htdocs" 
readonly MYSQL_CONF_DIRS="/etc/mysql"
readonly MYSQL_DATA_DIRS="/var/lib/mysql /raid1/mysql"

# backup and log path
readonly TAR_PATH="/export/bibliotheques/www.backup"

# Tag for syslog
readonly LOGGER_TAG="Fridu.org-Bakup"

# Parameters required to scp tar and log file on a remote server
# keys are generated by ssh-keygen -q -f ~/.ssh/id_remote_backup -t rsa
# passphrase is not supported
# Don't forget to activate clean up script on remote server
# leave empty $REMOTE_HOST="" to deactivate remote copy
readonly REMOTE_HOST="my-backup-server-ip"
readonly REMOTE_USER="root"
# Remote dir MUST already exist
readonly REMOTE_DIR="/tmp"
readonly ID_KEY_FILE="/root/.ssh/id_remote_backup"

# commands Path inclusion is advised to overcome issue with $PATH in cron job
readonly LOGGER="/usr/bin/logger"
readonly TAR="/bin/tar"
readonly MYSQL_INIT="/etc/init.d/mysql"
readonly MAIL="/usr/bin/mail"
readonly GZIP="/bin/gzip"
readonly FIND="/usr/bin/find"
readonly MD="/bin/mkdir"
readonly RM="/bin/rm"
readonly SCP="/usr/bin/scp"

#---------------------------------------------------------------------
#
# You should NOT have to change anything beyond this point
#
# except if you really know what you do don't change the code
# Validating and debugging complex script is tougher than it seems
#
#---------------------------------------------------------------------

declare  Date=""
declare  PidValue=""
declare  TarFile=""
declare  UserLogFile=""
declare  Command=""

#-----------------------------------------------------------------------
#
# Functions
#
#-----------------------------------------------------------------------

# Sanity check

function checkExec() {
  if ! [ -x $1 ]; then
        echo >&2 $0 Error $1 is not a valid check exec path in $0.
        # try to log even if logger may not be available or in the PATH
        logger -p 0 -i $PidValue -t $LOGGER_TAG "$SITENAME backup failled $0 Error $1 is not a valid check exec path in $0"
        exit 5
  fi
}

function checkDir() {
  if ! [ -d $1 ]; then
        echo >&2 $0 Error $1 is not a valid directory check path in $0.
        # log
        logger -p 0 -i $PidValue -t $LOGGER_TAG "$SITENAME backup failled $0 Error $1 is not a valid directory check path in $0"
        exit 5
  fi
}

function sanityCheck() {
  for Exec in "$LOGGER $TAR $MYSQL_INIT $MAIL $GZIP $FIND $MD $RM $SCP"
    do
      checkExec $Exec
    done
  for Dirs in "$APACHE_CONF_DIRS $APACHE_DATA_DIRS $MYSQL_CONF_DIRS $MYSQL_DATA_DIRS"
    do
    for Dir in $Dirs
      do
        checkDir $Dir
      done
    done
}

# Add time stamp
function timeStamp() {
   date -u +"%Y.%m.%d-%H:%M:%S-UTC $@"
}

# user log
function userLog() {
  echo `timeStamp "pid: $PidValue $*"` >> $UserLogFile 2>&1
}

# Syslog + Userlog
function log() {
  $LOGGER -t $LOGGER_TAG $*
  userLog "$*"
}

# stderr + syslog + userlog
function warn() {
  log "-s $*"
}

# report major error by email
function reportFailure() {
  log "$* mail sent to $REPORT_TO"
  $MAIL -s "$*" $REPORT_TO << END_OF_MAIL_BODY
    `cat $UserLogFile`
END_OF_MAIL_BODY
}

function commandExecLog() {
  userLog "$*"
  $* >> $UserLogFile 2>&1
  return $?
}

# creation du user log and backup of last one
function initTarLog() {
  if [ ! -e $TAR_PATH ]; then
    # cannot run via commandExecLog as log cannot be written
    $MD -p $TAR_PATH
    if [ $? != 0 ]; then
      reportFailure "$SITENAME Creation backup-dir $TAR_PATH failled"
      exit 1
    fi
  fi
  UserLogFile=$TAR_PATH/$Date-$LOGGER_TAG.log
  commandExecLog touch $UserLogFile
  TarFile="$TAR_PATH/$Date-$LOGGER_TAG.tar"
}

# clean old backups and logs
function cleanOldBackup() {
  local NumFileToClean
  local FileToClean="$TAR_PATH/fileToClean.lst"
  local FileListToClean=""
  userLog "Clean old log and tar files"
  # ATTENTION smart quoting rules required to path all special characters (check bash reference guide)
  # direct user of -exec is problematic because of patern {}
  $FIND $TAR_PATH  -atime +$DAYS_ARCHIVED -name \*$LOGGER_TAG\*  > $FileToClean
  NumFileToClean=`wc --lines < $FileToClean`
  if [ $NumFileToClean != "0" ]; then
    FileListToClean=`cat $FileToClean`
    for File in $FileListToClean
      do
         commandExecLog "$RM $File"
      done
  else
    userLog "no old log or tar files to clean out"
  fi
}
function remoteCopyBackup() {
  commandExecLog $SCP -i $ID_KEY_FILE -p -q $TarFile.gz  $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR 
  if [ $? != 0 ]; then
    warn "remote copy of backup tar failled - Exit"
    return 1
  fi
  commandExecLog $SCP -i $ID_KEY_FILE -p -q  $UserLogFile $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR 

  if [ $? != 0 ]; then
    warn "remote copy of backup log failled - Exit"
    return 1
  fi
}

# Backup apache conf files and data
function backupWebFiles(){
  commandExecLog "$TAR --append --verbose --file $TarFile $APACHE_CONF_DIRS $APACHE_DATA_DIRS"
  if [ $? != 0 ]; then
    warn "backup apache conf and data failled - Exit"
    return 1
  fi
}

# Backup MySQL by snapshop a full MySQL data directory
function backupMySQL() {
  commandExecLog "$MYSQL_INIT stop"
  if [ $? != 0 ]; then
    warn "Stopping mySQL failled - Exit"
    # try to restart MySQL to keep the site on line
    $MYSQL_INIT restart
    return 1
  fi
  log "$SITENAME is off line"
  commandExecLog "$TAR --append --verbose --file $TarFile $MYSQL_CONF_DIRS $MYSQL_DATA_DIRS"
  if [ $? != 0 ]; then
    warn "backup MySQL conf and data failled - Exit"
    # try to restart MySQL to keep the site on line
    $MYSQL_INIT restart
    return 1
  fi

  commandExecLog "$MYSQL_INIT start" >> $UserLogFile 2>&1
  if [ $? != 0 ]; then
    warn "Starting mySQL failled - Exit"
    return 1
  fi
  log "$SITENAME is on line"
}

#-----------------------------------------------------------------------------
#
# Entry point
#
#-----------------------------------------------------------------------------
PidValue=$$
Date=`date -u +"%Y.%m.%d-%H:%M:%S-UTC:"`

# Sanity check
sanityCheck

# initialisation
initTarLog
userLog "$SITENAME backup started PID: $PidValue"
userLog "tar file is : $TarFile.gz"

# backup files

# 1) backup web page content
backupWebFiles
if [ $? != 0 ]; then
  reportFailure "$SITENAME apache backup failled"
  exit 1
fi

# 2) backup database
backupMySQL
if [ $? != 0 ]; then
  reportFailure "$SITENAME MySQL backup failled $SITENAME is probably stopped"
  exit 1
fi

# compress the log and tar file (append is incompatible with -z option)
commandExecLog "$GZIP $TarFile"

# Clean up log and old tar
cleanOldBackup

# Push backup and log on remote server
if [ "$REMOTE_HOST" != "" ]; then
  remoteCopyBackup
  if [ $? != 0 ]; then
    reportFailure "$SITENAME backup remote copy on $REMOTE_HOST:$REMOTE_DIR failled"
    exit 1
  fi
fi
# exit clean
  reportFailure "$SITENAME backup OK"
exit 0
