#!/bin/bash
# synbak - universal backup system
# Written by Ugo Viti <ugo.viti@initzero.it> - Copyright 2005 InitZero S.r.l.
# Home Page: http://www.initzero.it/products/opensource/synbak
#
# Copyright (C) 2003, 2005 InitZero S.rl.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#

# System Informations
synbak_package="synbak"						# synbak package name
synbak_version="1.0.7"						# synbak release version
synbak_version_date="20060819"					# synbak release date
synbak_description="Synbak - Universal Backup System"			# synbak description
synbak_copyright="Copyright ©2003-2006 InitZero S.r.l."			# synbak copyright
synbak_author="Ugo Viti - ugo.viti@initzero.it"				# synbak author

# System Directories
sys_dir="/usr/lib/${synbak_package}"					# synbak system wide base dir
sys_dir_locale="/usr/share/locale"						# locale translations dir
sys_dir_doc="/usr/share/doc/synbak-1.0.7"							# documents dir

sys_dir_lib="${sys_dir}"						# library dir
sys_dir_method="${sys_dir}/method"					# backup methods dir
sys_dir_report="${sys_dir}/report"					# reports procedure dir

# System Files
sys_file_functions="${sys_dir_lib}/functions.sh"			# synbak functions
sys_file_conf_example="${sys_dir_doc}/examples/example.conf"			# synbak example config file

# User Directories
usr_dir="${HOME}/.synbak"						# synbak user config dir

# User Files
usr_file_conf_example="${usr_dir}/$(basename ${sys_file_conf_example})"	# synbak example config file


########################################################################
## don't edit anything bellow

# load locale language interpreter and use it if gettext.sh exist
# else revert to english only mode
gettext_bash="/usr/bin/gettext.sh"
if [ -f "${gettext_bash}" ]
  then
    source ${gettext_bash}
    export TEXTDOMAIN="${synbak_package}"
    export TEXTDOMAINDIR="${sys_dir_locale}"
    msg(){
      eval_gettext "$@"
    }
  else
    msg(){
      eval "echo -n \"$@\" | sed -e 's/\\\$/\$/g'"
    }
fi

# init of some useful system wide variables
usr_init_variables() {
 # Add other paths
 PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin

 synbak_server="$(hostname)"
 today="$(date +"%Y%m%d")"
 zerodayago="$(date --date="0 day ago" +"%Y%m%d")"
 onedayago="$(date --date="1 day ago" +"%Y%m%d")"
 twodayago="$(date --date="2 day ago" +"%Y%m%d")"
 threedayago="$(date --date="3 day ago" +"%Y%m%d")"
 fourdayago="$(date --date="4 day ago" +"%Y%m%d")"
 fivedayago="$(date --date="5 day ago" +"%Y%m%d")"
 sixdayago="$(date --date="6 day ago" +"%Y%m%d")"
 sevendayago="$(date --date="7 day ago" +"%Y%m%d")"
 eightdayago="$(date --date="8 day ago" +"%Y%m%d")"
 ninedayago="$(date --date="9 day ago" +"%Y%m%d")"
 tendayago="$(date --date="10 day ago" +"%Y%m%d")"
}

# temp file management
usr_make_tmp_files() {

  # the main synbak temp directory
  tmp_name="synbak-$(date +%s)-$RANDOM-$system-$method"

  # a better way to manage temp directories
  dir_tmp="$(mktemp -d -t "${tmp_name}" 2>/dev/null)"

  # for old system that doesn't understand 'mktemp -t' option
  if [ $? -eq 1 ]
    then
      dir_tmp="/tmp/${tmp_name}"
      mkdir -p "${dir_tmp}"
  fi

  if [ -d "${dir_tmp}" ] && [ -w "${dir_tmp}" ]
    then
      cd "${dir_tmp}"
    else
      msg "the directory '\${dir_tmp}' doesn't exist" >&2
      echo >&2
      exit 1
  fi

  file_synbak_server="${dir_tmp}/synbak_server"
  file_system="${dir_tmp}/system"
  file_method="${dir_tmp}/method"
  file_method_option="${dir_tmp}/option"
  file_method_type="${dir_tmp}/type"
  file_log_output="${dir_tmp}/output"
  file_log_errors="${dir_tmp}/errors"
  file_log_stats="${dir_tmp}/stats"
  file_stats_files_backup="${dir_tmp}/stats_files_backup"
  file_stats_files_total="${dir_tmp}/stats_files_total"
  file_size_source="${dir_tmp}/size_source"
  file_size_destination="${dir_tmp}/size_destination"
  file_size_backup="${dir_tmp}/size"
  file_status_backup="${dir_tmp}/status"
  file_status_host="${dir_tmp}/status_host"
  file_status_config_field_import="${dir_tmp}/status_config_field_import"
  file_list_source="${dir_tmp}/list_source"
  file_list_destination="${dir_tmp}/list_destination"
  file_list_exclude="${dir_tmp}/list_exclude"
  file_list_unbackable="${dir_tmp}/list_unbackable"
  file_list_backup="${dir_tmp}/list_backup"
  file_list_backup_keep="${dir_tmp}/list_backup_keep"
  file_list_backup_erase="${dir_tmp}/list_backup_erase"
  file_time_begin="${dir_tmp}/begin"
  file_time_end="${dir_tmp}/end"
  file_synbak_vars="${dir_tmp}/synbak_vars"

  dir_mnt="${dir_tmp}/mnt"

  touch "${file_synbak_server}"		\
	"${file_system}"		\
	"${file_method}"		\
	"${file_method_option}"		\
	"${file_method_type}"		\
	"${file_synbak_vars}"	 	\
	"${file_log_output}"		\
	"${file_log_errors}"		\
	"${file_log_stats}"		\
	"${file_stats_files_backup}"	\
	"${file_stats_files_total}"	\
	"${file_status_backup}"		\
	"${file_status_host}"		\
	"${file_size_source}"		\
	"${file_size_destination}"	\
	"${file_size_backup}"		\
	"${file_list_source}"		\
	"${file_list_destination}"	\
	"${file_list_exclude}"		\
	"${file_list_unbackable}"	\
	"${file_list_backup}"		\
	"${file_list_backup_keep}"	\
	"${file_list_backup_erase}"	\
	"${file_time_begin}"		\
	"${file_time_end}"

  # save the method and the system name into temp files
  echo "${synbak_server}" > "${file_synbak_server}"
  echo "${system}" > "${file_system}"
  echo "${method}" > "${file_method}"

  # save other mandatory variables
  status_host=0
  echo "${status_host}" > "${file_status_host}"
}

# command prompt menu
Usage(){
  local progname=$0
  ex=$1
  shift
  msg "\${synbak_description}
\${synbak_copyright}
Written by \${synbak_author}
version \${synbak_version} - \${synbak_version_date}

Usage: \$progname [<options>]

Option:  Argument:         Description:
-----------------------------------------------
  -s     <system name>     the system name of backup
  -m     <method name>     the method to use for backing up your data
  -r     <report name>     manage only a report and not a backup (optional)
  -S     <system options>  additional system options (optional)
  -M     <method options>  additional method options (optional)
  -R     <report options>  additional report options (optional)
  -v                       show Synbak version
  -h                       show this help screen

example: \$progname -s webserver -m rsync
" >&2
  echo >&2 "$@"
  exit $ex
}

# DEC OSF AKA Digital UNIX does not seem to return a value in OPTIND if
# there are no command line params, so protect us against that ...
if [ $# = 0 ]; then
  Usage 2 $(msg "Please enter a command line parameter!") >&2
fi

while getopts ":s:m:r:S:M:R:hv" option
do
  case $option in
   s) # system name
      system="${OPTARG}"
      #echo system: $system
      ;;
   m) # backup method
      method="${OPTARG}"
      #echo method: $method
      ;;
   r) # backup report
      report="${OPTARG}"
      #echo report: $report
      ;;
   S) # system name
      system_option="${OPTARG}"
      #echo system: $system
      ;;
   M) # backup method
      method_option="${OPTARG}"
      #echo method: $method
      ;;
   R) # backup report
      report_option="${OPTARG}"
      #echo report: $report
      ;;
   v) # show version
      msg "version \${synbak_version} - \${synbak_version_date}"
      echo
      exit 0
      ;;
   h) # show help screen
      Usage
      ;;

   *) # any other switch
      Usage 2 $(msg "Invalid switch specified - abort.") >&2
      ;;
   esac
done

shift $(($OPTIND - 1))


# Environment sanity checks
sys_check_env() {
# First check if the synbak's main function file exist and execute it
if [ -f "${sys_file_functions}" ]
  then
    # Load the main synbak environment libraries file
    . "${sys_file_functions}";
  else
    msg "Can't find the '\${sys_file_functions}' file" >&2
    echo >&2
    exit 1;
fi

# check if system directories exist
for dir in      ${sys_dir}                \
                ${sys_dir_lib}            \
                ${sys_dir_locale}         \
                ${sys_dir_method}         \
                ${sys_dir_report}
  do
    check_dir ${dir}
    [ $? -ne 0 ] && exit 1;
    unset dir
  done

# check if system files exist
for file in     ${sys_file_functions}     \
                ${sys_file_locale}        \
                ${sys_file_conf_example}
  do
    check_file ${file}
    [ $? -ne 0 ] && exit 1;
    unset file
  done

# Check if no args are given
if [ "${system}" = "" ]; then
  Usage 1 $(msg "No 'system name' specified - abort.") >&2
fi


if [ "${method}" = "" ]; then
  Usage 1 $(msg "No 'backup method' specified - abort.") >&2
fi


# check if the typed method exist, if not, show installed methods available
if [ ! -d ${sys_dir_method}/${method} ]
  then
    msg "'\${method}' backup method doesn't exist!" >&2
    echo >&2
    echo >&2
    msg "Valid methods are:" >&2
    echo >&2
    echo >&2
    show_available_methods
    exit 1
fi

# check if the typed report exist, if not, show installed report available
if [[ "${report}" != "" && ! -d ${sys_dir_report}/${report} ]]
  then
    msg "'\${report}' backup report doesn't exist!" >&2
    echo >&2
    echo >&2
    msg "Valid reports are:" >&2
    echo >&2
    echo >&2
    show_available_reports
    exit 1
fi



## misc runtime variables

# define the current method directories and files
sys_dir_method="${sys_dir_method}/${method}"				# method directory
sys_file_method="${sys_dir_method}/${method}.sh"			# method procedure
sys_file_method_nonerrors_strings="${sys_dir_method}/nonerrors.strings"	# method output nonerrors strings

# check if method files exist
for file in	${sys_file_method}		\
		${sys_file_method_nonerrors_strings}
  do
    check_file ${file}
    [ $? -ne 0 ] && exit 1;
    unset file
  done
}

usr_check_env() {
  # define user config file full path
  usr_file_conf="${usr_dir}/${method}/${system}.conf"

  show_new_backup_reminder() {
    msg "If this is a new backup system please remember to copy the '\${usr_file_conf_example}' config file in the '\${usr_dir}/\${method}' directory

example:
cp \${usr_file_conf_example} \${usr_dir}/\${method}/\${system}.conf
" >&2
  }

  # verify if exist the synbak user config dir, if not, create it
  check_dir ${usr_dir}/${method}

  # if the user config dir doesn't exist create it with the default structure
  if [ $? -ne 0 ]
    then
      mkdir -p ${usr_dir}/${method}
      cp -f ${sys_file_conf_example} ${usr_dir}/
      msg "i just created it for you." >&2
      echo >&2
      echo >&2
      show_new_backup_reminder
      exit 1;
  fi

  # always copy the example config file to user synbak dir
  cp -f ${sys_file_conf_example} ${usr_dir}/

  # check if the typed system config file exist
  if [ ! -f ${usr_file_conf} ]
    then
      msg "'\${usr_file_conf}' system config file doesn't exist!" >&2
      echo >&2
      echo >&2
      show_new_backup_reminder
      exit 1
  fi
}

###@@@
###@@@ backup procedures
###@@@

# Let's begin the dance!
usr_init_variables		# valorize the main variables
sys_check_env			# check if the operating system enviropment is OK
usr_make_tmp_files		# create the temp files
usr_check_env			# check if the user e nviropment is OK
usr_config_field_import_default	# import the default fields
save_time_begin			# save the begin time before make any backup

# if the report switch has arguments, then show if they are valid
if [ "${report}" != "" ]
  then
    report_run "$report" "$report_option"
    exit $?
fi

# make the backup and the default reports
if [ "${report_stdout}" = "no" ] || [ "${report_remote_uri_down}" = "no" ]
  then
    usr_make_backup 2>&1 | save_output > /dev/null # don't show the log on stdout

    # Exit and print the output if the backup field import fail
    [ $(cat ${file_status_config_field_import}) -ne 0 ] && cat "${file_log_output}" && exit 1

    # if an error occur and the config field report_stdout_on_errors=yes, print the complete text report
    if [ "${report_stdout}" = "yes" ] || [[ $(check_status_backup >/dev/null 2>&1 ; echo $?) -eq 1 && "${report_stdout_on_errors}" = "yes" ]]
      then
        # Print some useful info
        #echo "check_status_backup     = $(check_status_backup >/dev/null 2>&1 ; echo $?)"
        #echo "report_stdout_on_errors = ${report_stdout_on_errors}"
        #echo "file_status_host        = $(cat ${file_status_host})"
        #echo "report_remote_uri_down  = ${report_remote_uri_down}"

	if [ -z "${report_remote_uri_down}" ] || [ "${report_remote_uri_down}" = "yes" ]
	  then
            cat ${file_log_output}
	elif [[ "${report_remote_uri_down}" = "no"  && "$(cat ${file_status_host})" = "0" ]]
	  then
            cat ${file_log_output}
        fi
    fi
  else
    usr_make_backup 2>&1 | save_output
fi

#   echo file_config_field_import_status=${file_config_field_import_status}

# save some useful information (WARNING! use this only for debug porpouse, because this command dump passwords also!!!)
#set > "${file_synbak_vars}"

# If we no error occur, then erase all temp files and directories
[ $(check_status_backup >/dev/null 2>&1 ; echo $?) -eq 0 ] && rm -rf "${dir_tmp}" && exit 0 || exit 1


