#!/bin/bash #------------------------------------------------------------------------------# # vi: set sw=4 ts=4 ai: ("set modeline" in ~/.exrc) # #------------------------------------------------------------------------------# # Program : makebackup # # # # Author : Ton Kersten Groesbeek, The Netherlands # # # # Date : 26-03-2008 Time : 11:35 # # # # Description : Create a backup of all specified directories # # # # Parameters : None # # # # Pre reqs : Rsync, the backupit tool and Perl # # # # Exit codes : 0 -> OK # # <> 0 -> !OK # # # # Updates : None (yet) # #------------------------------------------------------------------------------# #------------------------------------------------------------------------------# # V e r s i o n i n f o r m a t i o n # #------------------------------------------------------------------------------# # $Id:: makebackup 51 2015-03-25 11:08:39 tonk $: # # $Revision:: 51 $: # # $Author:: Ton Kersten $: # # $Date:: 2015-03-25 11:08:39 +0200 (Wed, 25 Mar 2015) $: # #------------------------------------------------------------------------------# # E n d o f v e r s i o n i n f o r m a t i o n # #------------------------------------------------------------------------------# #------------------------------------------------------------------------------# # Determine the program name and the 'running directory' # #------------------------------------------------------------------------------# IAM="${0##*/}" CRD="$( [[ "${0:0:2}" = "./" ]] && { printf "${PWD}/${0#./}" } || { printf "${0}" })" CRD="${CRD%/*}" CUR="${PWD}" #------------------------------------------------------------------------------# # Save the shell settings # #------------------------------------------------------------------------------# SETA=0; [[ ${-} = *a* ]] && SETA=1 SETE=0; [[ ${-} = *e* ]] && SETE=1 SETU=0; [[ ${-} = *u* ]] && SETU=1 SETX=0; [[ ${-} = *x* ]] && SETX=1 #------------------------------------------------------------------------------# # Set and unset the needed shell settings # #------------------------------------------------------------------------------# set +o noclobber # Overwrite existing files, if needed # set -o nounset # Do not allow uninitialized variables # set +o errexit # No returncode checking # #------------------------------------------------------------------------------# # Define the date/time function # #------------------------------------------------------------------------------# Now() { date '+%Y-%m-%d %H:%M:%S' return 0 } #------------------------------------------------------------------------------# # Calculate h:m:s from seconds # #------------------------------------------------------------------------------# hms() { s="${1}" h=$(( ${s}/3600 )) s=$(( ${s} - ( ${h} * 3600) )) m=$(( ${s} / 60)) s=$(( ${s} -( ${m} * 60 ) )) printf "%d:%02d:%02d" ${h} ${m} ${s} } #------------------------------------------------------------------------------# # Send a line to syslog (if $1 = '-f' the line is forced to syslog) # #------------------------------------------------------------------------------# syslog() { if [[ x"${1:-}" = x"-f" ]] then shift echo "${*:-}" | logger -t "${IAM}" -p "${SYSLOGFAC:-local4.info}" return fi if [[ x"${SYSLOG:-0}" = x"1" ]] then echo "${*:-}" | logger -t "${IAM}" -p "${SYSLOGFAC:-local4.info}" fi } #------------------------------------------------------------------------------# # Check the configfile # #------------------------------------------------------------------------------# CONFIGFILE="${1:-${CRD}/${IAM}.conf}" if [[ ${#} = 0 ]] then LOGFILE="/var/log/${IAM}.log" else LOGFILE="/var/log/${IAM}-$(basename ${CONFIGFILE} | sed 's!\.conf.*$!!').log" fi #------------------------------------------------------------------------------# # Start the logging # #------------------------------------------------------------------------------# exec 6>&1 # Save stdout into fd6 # exec 7>&2 # Save stderr into fd7 # exec > ${LOGFILE} exec 2>&1 #------------------------------------------------------------------------------# # Define constants and variables # #------------------------------------------------------------------------------# VER="1.$(awk '/^# \$Revision::/ { print $3 }' ${0})" PATH="${PATH}:/bin:/sbin" PATH="${PATH}:/usr/bin:/usr/sbin" PATH="${PATH}:/usr/local/bin:/usr/local/sbin" PATH="${PATH}:${CRD}" BCK="${CRD}/backupit" PID="/var/run/${IAM}.pid" HOSTNAME="$(hostname)" WD="80" # Screen width # s="$(printf "%${WD}s" "")"; s="${s// /-}" # Single dash line # d="$(printf "%${WD}s" "")"; d="${d// /=}" # Double dash line # typeset -i RC=0 typeset -i BCKS=0 #------------------------------------------------------------------------------# # Setup trapping to remove the PID file # #------------------------------------------------------------------------------# trap "rm -f ${PID}" EXIT #------------------------------------------------------------------------------# # Announce the start of the program # #------------------------------------------------------------------------------# BCKSTART=$(date '+%s') BCKSTARTTIME=$(Now) echo "${d}" echo echo "$(Now) -> Starting ${IAM} version ${VER}" echo echo "${d}" echo echo " System backup at ${HOSTNAME} ended BACKUPSTATUS" echo echo "${d}" echo syslog -f "Starting ${IAM}" #------------------------------------------------------------------------------# # Do we already have a PID file??? If not, create it. # #------------------------------------------------------------------------------# if [[ -f ${PID} ]] then syslog -f "PID file ${PID} present. Skipping backup" exit 0 fi echo ${$} > ${PID} #------------------------------------------------------------------------------# # Read the config file # #------------------------------------------------------------------------------# if [[ -r "${CONFIGFILE}" ]] then . "${CONFIGFILE}" || { echo "Error processing config '${CONFIGFILE}'!" syslog -f "Error processing config '${CONFIGFILE}'!" rm -f ${PID} exit 1 } else echo "Could not find config '${CONFIGFILE}'!" >&2 syslog -f "Could not find config '${CONFIGFILE}'!" rm -f ${PID} exit 1 fi #------------------------------------------------------------------------------# # Set verbose and debug # #------------------------------------------------------------------------------# [[ "${VERBOSE:-0}" = "1" ]] && VERBOSE="-v" || VERBOSE="" [[ "${DEBUG:-0}" = "1" ]] && DEBUG="-d" || DEBUG="" [[ x"${DEBUG}" != x"" ]] && set -x #------------------------------------------------------------------------------# # Set defaults for unset stuff # #------------------------------------------------------------------------------# FAILS=0 MAX="${MAX:-99}" BACKUPMYSQL="${BACKUPMYSQL:-no}" SYSLOG="${SYSLOG:-1}" SYSLOGFAC="${SYSLOGFAC:-local4.info}" MAILTO="${MAILTO:-root@localhost}" PREBACKUP="${PREBACKUP:-}" POSTBACKUP="${POSTBACKUP:-}" #------------------------------------------------------------------------------# # Define the language settings (not allways needed) # #------------------------------------------------------------------------------# export LANG=${LANG:-C} export LC_ALL=${LC_ALL:-C} #------------------------------------------------------------------------------# # Run a command before we begin # #------------------------------------------------------------------------------# if [[ x"${PREBACKUP:-}" != x"" ]] then syslog "Starting pre backup command" echo "${d}" echo "Prebackup command output." echo eval ${PREBACKUP} 2>&1 > /tmp/${IAM}_${$} THISRC=${?} sed 's/^/ /' /tmp/${IAM}_${$} if [[ ( ${THISRC} != 0 ) && ( ${SYSLOG:-0} = 1 ) ]] then sed "s/^/${SHRT[${CNT}]:-} : /" /tmp/${IAM}_${$} | \ logger -t "${IAM}" -p "${SYSLOGFAC:-local4.info}" fi echo echo "Returncode for the prebackup command: ${THISRC}" echo echo "${d}" echo syslog "Prebackup command ended with returncode ${THISRC}" rm -f /tmp/${IAM}_${$} if [[ ${THISRC} != 0 ]] then echo "Exit because of failed 'pre' command" syslog "Exit because of failed 'pre' command" exit ${THISRC} fi fi #------------------------------------------------------------------------------# # Backup MySQL if this is requested # #------------------------------------------------------------------------------# if [[ x"${BACKUPMYSQL:-no}" = x"yes" ]] then syslog "Starting MySQL backup" if [[ -x ${CRD}/mysqlbackup ]] then echo "$(Now) -> ${IAM}: Starting MySQL backup on host '$(hostname)'" syslog "Starting MySQL backup on host '$(hostname)'" ${CRD}/mysqlbackup echo "$(Now) -> ${IAM}: Finished MySQL backup on host '$(hostname)'" syslog "Finished MySQL backup on host '$(hostname)'" else echo "Cannot find MySQL backup tool '${CRD}/mysqlbackup'" >&2 echo "MySQL backup will be skipped!!" >&2 syslog "Cannot find MySQL backup tool '${CRD}/mysqlbackup'" syslog "MySQL backup will be skipped!!" fi syslog "Finished MySQL backup" fi #------------------------------------------------------------------------------# # Check if the 'backupit' program exists. If not, there is no way to backup # #------------------------------------------------------------------------------# if [[ ! -x "${BCK}" ]] then echo "The backup program '${BCK}' cannot be found!" >&2 echo "There is no way to create a backup." >&2 echo "The program will be stopped." >&2 syslog "The backup program '${BCK}' cannot be found!" syslog "There is no way to create a backup." syslog "The program will be stopped. (RC=1)" rm -f ${PID} exit 1 fi #------------------------------------------------------------------------------# # Create the backups for all defined sources and destinations and remove the # # ones that are obsolete # #------------------------------------------------------------------------------# TOTALCYCLESTART=$(date '+%s') TOTALDISKUSED=0 for CNT in $(seq 1 ${BCKS}) do syslog "Backup cycle for '${SHRT[${CNT}]:-}' starting..." HOSTCYCLESTART=$(date '+%s') for DST in ${DSTS[${CNT}]} do CYCLESTART=$(date '+%s') CYCLESTARTTIME=$(Now) #----------------------------------------------------------------------# # Debug per host # #----------------------------------------------------------------------# if [[ x"${DBG[${CNT}]:-0}" = x"1" ]] then set -x DEBUG="-d" else set +x DEBUG="" fi #----------------------------------------------------------------------# # Remove old backups if needed # #----------------------------------------------------------------------# if [[ x"${REMOVE[${CNT}]:-}" = x"REMOVE_OBSOLETE_BACKUP" ]] then if [[ -d ${DST} ]] then #--------------------------------------------------------------# # Make sure we do not remove the root # #--------------------------------------------------------------# if [[ x"${DST}" = x"/" ]] then echo "I will NOT remove the root for destination '${DST}'." >&2 syslog "I will NOT remove the root for destination '${DST}'." continue fi echo "Removing the old backups for destination '${DST}'." >&2 syslog "Removing the old backups for destination '${DST}'." rm -rf ${DST} continue fi fi #----------------------------------------------------------------------# # Create the destination directory if it's not there yet # #----------------------------------------------------------------------# [[ ! -d ${DST} ]] && { mkdir -p ${DST} chmod 700 ${DST} } #----------------------------------------------------------------------# # Create the backup source string # #----------------------------------------------------------------------# SRC="" SRCTXT="" for dir in ${DIRS[${CNT}]} do SRC="${SRC} --source='${dir}'" SRCTXT="${SRCTXT} ${dir}" done #----------------------------------------------------------------------# # Create the backup exclude string (may be empty) # #----------------------------------------------------------------------# EXC="" EXCTXT="" for exc in ${EXCL[${CNT}]:-} do EXC="${EXC} --exclude='${exc}'" EXCTXT="${EXCTXT} ${exc}" done [[ x"${EXCTXT}" = x"" ]] && EXCTXT="None" #----------------------------------------------------------------------# # Find out the number of cycles to keep # #----------------------------------------------------------------------# CYCLES=${CYCL[${CNT}]:-${MAX}} #----------------------------------------------------------------------# # Start with a summary of what to do # #----------------------------------------------------------------------# set ${SRCTXT}; SRC1="${1}"; shift; SRCR="${@:-}" set ${EXCTXT}; EXC1="${1}"; shift; EXCR="${@:-}" echo echo echo " Starting backup cycle" echo " ${s:4}" echo " Description : "$(echo ${DESC[${CNT}]}) echo " Sources : "${SRC1} for f in ${SRCR} do echo " "${f} done echo " Excludes : "${EXC1} for f in ${EXCR} do echo " "${f} done echo " Destination : "${DST} echo " Number of cycles : "${CYCLES} echo " Monthly backups : "${MONTHLY:-no} echo " Monthly cycles : "${MONTHLYMAX:-12} echo " Cycle start time : "$(Now) echo " ${s:4}" echo echo syslog "${SHRT[${CNT}]:-} : Starting backup cycle for '${DESC[${CNT}]}'" #----------------------------------------------------------------------# # Do the backup # #----------------------------------------------------------------------# if [[ x"${RSYNCOPTS:-}" != x"" ]] then RSYNCOPTS="--rsyncopts='${RSYNCOPTS}'" else RSYNCOPTS="" fi ${BCK} \ ${VERBOSE} \ ${DEBUG} \ ${RSYNCOPTS} \ ${SRC} \ ${EXC} \ --max=${CYCLES} \ --target=${DST} 2>&1 > /tmp/${IAM}_${$} THISRC=${?} #----------------------------------------------------------------------# # Create the monthly backups? # #----------------------------------------------------------------------# if [[ x"${MONTHLY:-no}" = x"yes" ]] then if [[ x"$(date '+%d')" = x"01" ]] then cd ${DST}/latest pwd if [[ ${?} = 0 ]] then echo " Starting monthly backup cycle" syslog "${SHRT[${CNT}]:-} : Starting monthly backup cycle'" ${BCK} \ ${VERBOSE} \ ${DEBUG} \ ${RSYNCOPTS} \ --max=${MONTHLYMAX} \ --source="." \ --target=${DST}/_monthly_ 2>&1 >> /tmp/${IAM}_${$} MONTHLYRC=${?} if [[ ${MONTHLYRC} != 0 ]] then echo " Monthly backup failed with RC=${MONTHLYRC}" syslog "${SHRT[${CNT}]:-} : Monthly backup failed with RC=${MONTHLYRC}" fi cd - >/dev/null 2>&1 fi fi fi THISRC=$(( ${THISRC} + ${MONTHLYRC:-0} )) #----------------------------------------------------------------------# # Parse the logfile and send it to the standard logging # #----------------------------------------------------------------------# sed 's/^/ /' /tmp/${IAM}_${$} #----------------------------------------------------------------------# # Send the logging to syslog if needed and wanted # #----------------------------------------------------------------------# if [[ ( ${THISRC} != 0 ) && ( ${SYSLOG:-0} = 1 ) ]] then sed "s/^/${SHRT[${CNT}]:-} : /" /tmp/${IAM}_${$} | \ logger -t "${IAM}" -p "${SYSLOGFAC:-local4.info}" fi #----------------------------------------------------------------------# # And, if things fail, increase the failure number # #----------------------------------------------------------------------# if [[ ( ${THISRC} != 0 ) ]] then FAILS=$(( ${FAILS} + 1 )) FAILHOST[${FAILS}]="${SHRT[${CNT}]:-}" FAILRC[${FAILS}]="${THISRC}" fi #----------------------------------------------------------------------# # Create a nice summary # #----------------------------------------------------------------------# RC=${RC}+${THISRC} CYCLEEND=$(date '+%s') CYCLEENDTIME=$(Now) if [[ -e ${DST}/latest/ ]] then USEDH="$(du -hs ${DST}/latest/ 2>/dev/null | awk '{ print $1 " (" $2 ")" }')" USEDC="$(du -bs ${DST}/latest/ 2>/dev/null | awk '{ print $1 }')" else USEDH="Unknown" USEDC="0" fi if [[ -e ${DST} ]] then FREEH="$(df -hP ${DST} 2>/dev/null | awk ' !/^F/ { print $4 }')" FREEC="$(df -P ${DST} 2>/dev/null | awk ' !/^F/ { print $4 }')" else FREEH="Unknown" FREEC="0" fi RUN=$(hms $(( ${CYCLEEND} - ${CYCLESTART} )) ) TOTALDISKUSED=$(( ${TOTALDISKUSED} + ${USEDC} )) echo echo echo " Cycle statistics" echo " ${s:4}" echo " Returncode : "${THISRC} echo " Used disk space : "${USEDH} echo " Free disk space : "${FREEH} echo " Cycle end time : "$(Now) echo " Cycle run time : "${RUN} echo " ${s:4}" echo echo SUM="" SUM="${SUM} START=${CYCLESTARTTIME};" SUM="${SUM} END=${CYCLEENDTIME};" SUM="${SUM} RUN=${RUN};" SUM="${SUM} RC=${THISRC};" SUM="${SUM} USED=${USEDH};" SUM="${SUM} FREE=${FREEH};" syslog "BckHost = ${SHRT[${CNT}]:-} : Backup_sources = ${SRCTXT}" syslog "BckHost = ${SHRT[${CNT}]:-} : Backup_destination = ${DST}" syslog "BckHost = ${SHRT[${CNT}]:-} : Returncode = ${THISRC}" syslog "BckHost = ${SHRT[${CNT}]:-} : Monthly returncode = ${MONTHLYRC:-0}" syslog "BckHost = ${SHRT[${CNT}]:-} : Used_disk_space = ${USEDC}" syslog "BckHost = ${SHRT[${CNT}]:-} : Free_disk_space = ${FREEC}" syslog "BckHost = ${SHRT[${CNT}]:-} : Cycle_run_time = ${RUN}" syslog "BckHost = ${SHRT[${CNT}]:-} : Summary = ${SUM}" syslog "BckHost = ${SHRT[${CNT}]:-} : Finished backup cycle for '${DESC[${CNT}]}'" rm -f /tmp/${IAM}_${$} syslog "Backup cycle for '${SHRT[${CNT}]:-}' to '${DST}' finished... (Run time: ${RUN})" done echo echo "${d}" echo HOSTCYCLEEND=$(date '+%s') HOSTRUN=$(hms $(( ${HOSTCYCLEEND} - ${HOSTCYCLESTART} )) ) syslog "Run_Time_${SHRT[${CNT}]:-} = ${HOSTRUN}" done #------------------------------------------------------------------------------# # Statistics in syslog # #------------------------------------------------------------------------------# TOTALCYCLEEND=$(date '+%s') TOTALRUN=$(hms $(( ${TOTALCYCLEEND} - ${TOTALCYCLESTART} )) ) syslog "Total = Finished : Run_time = ${TOTALRUN}" syslog "Total = Finished : Total_disk_used = ${TOTALDISKUSED})" #------------------------------------------------------------------------------# # Check if it all went well # #------------------------------------------------------------------------------# if [[ ${RC} = 0 ]] then echo "$(Now) -> Backup ended OK!" BCKSTATUS="OK! Returncode = 0" else if [[ ${RC} = 24 ]] then echo "$(Now) -> Backup ended OK!" BCKSTATUS="OK! (Some files vanished). Returncode = 24" else echo "$(Now) -> Backup ended NOT OK! Returncode = ${RC}" BCKSTATUS="NOT OK! Returncode = ${RC}" fi fi #------------------------------------------------------------------------------# # Run a command after we are done # #------------------------------------------------------------------------------# if [[ x"${POSTBACKUP:-}" != x"" ]] then syslog "Starting post backup command" echo "${d}" echo "Postbackup command output." echo eval ${POSTBACKUP} 2>&1 > /tmp/${IAM}_${$} THISRC=${?} sed 's/^/ /' /tmp/${IAM}_${$} if [[ ( ${THISRC} != 0 ) && ( ${SYSLOG:-0} = 1 ) ]] then sed "s/^/${SHRT[${CNT}]:-} : /" /tmp/${IAM}_${$} | \ logger -t "${IAM}" -p "${SYSLOGFAC:-local4.info}" fi echo echo "Returncode for the postbackup command: ${THISRC}" echo echo "${d}" echo syslog "Postbackup command ended with returncode ${THISRC}" rm -f /tmp/${IAM}_${$} fi #------------------------------------------------------------------------------# # Log and display the summary # #------------------------------------------------------------------------------# BCKEND=$(date '+%s') BCKENDTIME=$(Now) RUN=$(hms $(( ${BCKEND} - ${BCKSTART} )) ) SUM="" SUM="${SUM} START=${BCKSTARTTIME};" SUM="${SUM} END=${BCKENDTIME};" SUM="${SUM} RUN=${RUN};" SUM="${SUM} RC=${RC};" SUM="${SUM} STATUS=${BCKSTATUS};" echo echo "Total run time : "${RUN} echo "${d}" syslog "Total run time: "${RUN} syslog "Ended on '${HOSTNAME}' (RC=${BCKSTATUS})" syslog "Summary : ${SUM}" syslog "Ending ${IAM}" #------------------------------------------------------------------------------# # And, if things failed, show that as well # #------------------------------------------------------------------------------# if [[ ${FAILS} != 0 ]] then typeset -i i=0 echo echo "Failed backups" echo "${d}" syslog "Failed backups:" while [[ ${i} < ${FAILS} ]] do i=$(( ${i} + 1 )) echo " RC=${FAILRC[${i}]} for host ${FAILHOST[${i}]}" syslog " RC=${FAILRC[${i}]} for host ${FAILHOST[${i}]}" done echo "${d}" fi #------------------------------------------------------------------------------# # Clear the log redirection and insert the program status into the log # #------------------------------------------------------------------------------# exec 1>&6 6>&- # Restore stdout and close fd6 # exec 2>&7 7>&- # Restore stderr and close fd7 # sed -i.bck "s/BACKUPSTATUS/${BCKSTATUS}/" ${LOGFILE} rm ${LOGFILE}.bck #------------------------------------------------------------------------------# # Mail the logging if requested # #------------------------------------------------------------------------------# case "${MAILTO}" in stdout) #----------------------------------------------------------------------# # Send the logging to stdout # #----------------------------------------------------------------------# cat ${LOGFILE} ;; syslog) #----------------------------------------------------------------------# # Send the logging to syslog # #----------------------------------------------------------------------# cat ${LOGFILE} | logger -t "${IAM}" -p "${SYSLOGFAC:-local4.info}" ;; *) #----------------------------------------------------------------------# # Send the logging through mail # #----------------------------------------------------------------------# SUBJ="Logging of '${IAM}' on '${HOSTNAME}' at $(Now) (RC=${BCKSTATUS})" cat ${LOGFILE} | mail -s "${SUBJ}" ${MAILTO} ;; esac #------------------------------------------------------------------------------# # That's all, folks! # #------------------------------------------------------------------------------# rm -f ${PID} exit ${RC}