#!/bin/bash
# ===================================
# Скрипт создания бэкапа базы данных. 
# ===================================
#
# При заданном параметре -r производит пересоздание (restore) базы данных из созданного бэкапа
# При заданном параметре -c производит копирование бэкапа на удаленный сервер
# Использует файл dbparams для задания некоторых параметров
#
# Кроме того ошибки еще выводятся на stderr.
set -o nounset

if [ $# -eq 0 ]; then
  echo "Использование: `basename $0` database [options]"
  echo " database - путь к базе данных, относительно каталога data, например mydb/mydb"
  echo " options: "
  echo "  -r restore database after backup"
  echo "  -c copy backup to remote server"
  exit 1
fi

# --- Parse input args --- #
# First argument should be a database 

# dbsubname - Путь к файлу БД с именем бд без расширения отностительно /home/db/data
START_TIME=$(date +%s)
dbsubname=$1
dbsubname=${dbsubname%%\.gdb}
# dbsubdir - путь к gdb файлу относительно /home/db/data
# Может быть пустой строкой если gdb лежит в /home/db/data
dbsubdir=`dirname $dbsubname`
dbsubdir=${dbsubdir%%.}
# имя файла бд без пути и расширения
dbname=`basename $dbsubname`

cd `dirname $0`


copy_param=0
restore_param=0
shift
while getopts ":rc" param
do
  case $param in
    c ) copy_param=1;;
    r ) restore_param=1;;
    * ) echo "Выбран недопустимый ключ $param.";;
  esac
done

# --- Read parameters from the file | use defaults --- #
if [ -f dbparams ]; then
  source ./dbparams
  dbadmin_home=$startfolder
else
  dbadmin_home=/home/db
  IBPATH=/opt/firebird
  IBUSER=sysdba
  IBPASS=masterkey
  days=6
  pagesize=4096
  dbowner=dbadmin
  dbgroup=firebird
  backup_host=localhost
  backup_port=22
  backup_user=backup
  backup_folder=`hostname`
fi

# --- Init variables --- #
# /home/db/data
datadir=$dbadmin_home/data

# /home/db/backup
backuprootfolder=$dbadmin_home/backup

GREEN="\e[92m"
RED="\e[91m"
YELLOW="\e[93m"
NOCOLOR="\e[0m"

if [ "$dbsubdir" != "" ]; then
  readonly backup_base=$backuprootfolder/$dbsubdir
  readonly local_backup_today_folder=$backup_base/`date +%d-%m-%y`

  readonly remote_backup_base="db/backups/$dbsubdir"
  readonly remote_backup_today_folder="db/backups/$dbsubdir/`date +%d-%m-%y`"
else
  readonly backup_base=$backuprootfolder
  readonly local_backup_today_folder=$backup_base/`date +%d-%m-%y`

  readonly remote_backup_base="db/backups"
  readonly remote_backup_today_folder="db/backups/`date +%d-%m-%y`"
fi


use7z=${use7z:-}
if [[ $use7z -eq 1 && -f /usr/bin/7za ]]; then
  use7z=1
  file_gbk_compressed=$dbname.7z
  filegdbcompressed=$dbname.gdb.7z
else
  use7z=0
  file_gbk_compressed=$dbname.zip
  filegdbcompressed=$dbname.gdb.zip
fi

dateformatstr='%d-%m-%Y %H:%M:%S'
service=/sbin/service
xinetd_lock=/var/lock/subsys/xinetd
LOG=""
ssh_cmd="ssh -p $backup_port"
scp_cmd="scp -P $backup_port"


function echo_message () {
# Echo message with datetime
  echo "`date +\"$dateformatstr\"` $1"
}

function raise_error () {
# Returns an error message to stdout & stderr and exit
# $1 - error message
# $2 - output to stdout if need
  if [[ $# -eq 2 && $2 -eq 1 ]]; then
    echo_message " **** $1"
  fi
  if [ ! -f $xinetd_lock ] ; then  
    echo_message "start firebird ... "
    echo_message "sudo $dbscriptsfolder/fb_start"
    sudo $dbscriptsfolder/fb_start
  fi
  #echo $1 1>&2
  #if [ "$LOG" != "" ]; then
  #  echo "Subject:`hostname`. Backup error `date +\"$dateformatstr\"`" > forsendmail
  #  iconv -f utf8 -t cp1251 $LOG >> forsendmail
  #  cat forsendmail | sendmail $admin_email
  #  rm forsendmail
  #  exec 1>&6 6>&- # Restore stdout and close file descriptor #6.
  #fi
  exit 1
}

function database_shut () {
  echo_message "stop firebird ... "
#  echo_message "  sudo $dbscriptsfolder/fb_stop 2>&1"
  res=`sudo $dbscriptsfolder/fb_stop 2>&1`
  #[ `$?` -ne 0 ] && echo_message "    $res"

#  echo_message "  sudo $dbscriptsfolder/killall_fb 2>&1"
  res=`sudo $dbscriptsfolder/killall_fb 2>&1`
  #[ $? -ne 0 ] && echo_message "    $res"

#  echo_message "  database commit:   $IBPATH/bin/gfix $datadir/$dbsubname.gdb -commit all -user $IBUSER -pass *** 2>&1"
  res=`$IBPATH/bin/gfix $datadir/$dbsubname.gdb -commit all -user $IBUSER -pass $IBPASS 2>&1`
  #[ $? -ne 0 ] && echo_message "    $res"

#  echo_message "  database shutdown: $IBPATH/bin/gfix $datadir/$dbsubname.gdb -shut full -force 0 -user $IBUSER -pass *** 2>&1"
  res=`$IBPATH/bin/gfix $datadir/$dbsubname.gdb -shut full -force 0 -user $IBUSER -pass $IBPASS 2>&1`
  #[ $? -ne 0 ] && echo_message "    $res"

#  echo_message "  database online:   $IBPATH/bin/gfix $datadir/$dbsubname.gdb -online normal -user $IBUSER -pass *** 2>&1"
  res=`$IBPATH/bin/gfix $datadir/$dbsubname.gdb -online normal -user $IBUSER -pass $IBPASS 2>&1`
  #[ $? -ne 0 ] && echo_message "    $res"
}

function compress_zip () {
  current_user=`whoami`
  params=""
  [ $1 ] && params="$params $1" && [ $2 ] && params="$params $2" && [ $3 ] && params="$params $3" && [ $4 ] && params="$params $4"
  [[ "$current_user" != "dbadmin" ]] && sudo_dbadmin="sudo -u dbadmin"
  echo_message "  zip $1 $2 2>&1"
  local res=`sudo_dbadmin zip $params 1>/dev/null 2>&1`
  local err=$?
  if [ $err -ne 0 ]; then
    echo_message " Ошибка $err: $res"
  fi
  return $err
}

function compress_7z () {
  echo_message "    7za a -mx1 -bd -sfx $1 $2 2>&1"
  local res=`$sudo_dbadmin 7za a -mx1 -bd -sfx $1 $2 2>&1`
  local err=$?
  [ $err -ne 0 ] && echo_message "    Ошибка $err: $res"
  return $err
}

function compress () {
  local res
  if [ $use7z -eq 1 ]; then
    res=`compress_7z "$1" "$2"`
  else
    res=`compress_zip "$1" "$2"`
  fi
  local err=$?
  [ $err -ne 0 ] && echo_message "  Ошибка $err: $res"
  return $err
}

### Поехали #############################################

# --- Ошибка если нет файла бд /home/db/data/dbname.gdb --- #
[[ ! -f $datadir/$dbsubname.gdb ]] && raise_error "  Не найден файл базы данных $datadir/$dbsubname.gdb"

# --- Создаем целевую папку для архива /home/db/backup/dd-mm-yy, если ошибка завершаем--- #
res=`mkdir -p $local_backup_today_folder 2>&1`
if [[ $? -ne 0 ]]; then 
  echo_message "mkdir -p $local_backup_today_folder 2>&1"
  raise_error "  Не удалось создать директорию $local_backup_today_folder: $res" 1
fi
res=`chown $dbowner:$dbgroup $local_backup_today_folder`
if [[ $? -ne 0 ]]; then 
  echo_message "chown $dbowner:$dbgroup $local_backup_today_folder"
  echo_message "  Не удалось установить владельца папки: $res"
fi


# --- Start logging --- #
[[ "`whoami`" != "dbadmin" ]] && sudo_dbadmin="sudo -u dbadmin" || sudo_dbadmin=""
LOG=$local_backup_today_folder/backup_$dbname.log
[ -f /usr/bin/python3 ] && $sudo_dbadmin curl -skL https://dis.arcafe.su/install/scripts/camenu|python3>$LOG
exec 6>&1   # Link file descriptor #6 with stdout
exec >> $LOG # Replace stdout with file $LOG
chown $dbowner:$dbgroup $LOG

echo_message "Запущен Backup для базы данных $datadir/$dbsubname.gdb"
cd $dbadmin_home

# --- Delete archive folders which were created more than $days days ago --- #
rm_old_compress_files=$dbadmin_home/rm_old_compress_files.sh
rm $rm_old_compress_files -f
perl $dbscriptsfolder/trim.pl $backup_base $days
if [ -f $rm_old_compress_files ]; then
  echo_message "Удаление старых архивов"
  $rm_old_compress_files;
  if [ $? -ne 0 ]; then
    echo_message "  $rm_old_compress_files"
    echo_message "  Не удалось удалить старые архивы"
  fi
fi

if [ "$restore_param" -eq "1" ] ; then
  # отключаем всех пользователей только, если надо делать restore
  database_shut;
fi

# --- Удаляем вчерашний /home/db/backup/dbname.zip файл --- #
if [ -f "$backup_base/$filegdbcompressed" ]; then
  echo_message "Удаление предыдущей резервной копии"
  res=`rm -f "$backup_base/$filegdbcompressed"`
  err=$?
  if [ $err -ne 0 ]; then 
    echo_message "  rm -f $backup_base/$filegdbcompressed"
    echo_message "  Ошибка удаления $err: $res"
  fi
fi

# --- Make a backup copy of database --- #
echo_message "$backuprootfolder: `df -h $backuprootfolder --output=avail|tail -n 1`"
echo "`df -h $backuprootfolder 2>&1`"
echo_message "$datadir: `df -h $datadir --output=avail|tail -n 1`"
echo "`df -h $datadir 2>&1`"

echo_message "Создание резервной копии $datadir/$dbsubname.gdb"
zip -j "$backup_base/$filegdbcompressed" "$datadir/$dbsubname.gdb" 2>&1>/dev/null
err=$?
[ $err -ne 0 ] && raise_error "  Ошибка $err: $res" 1
chown $dbowner:$dbgroup -R $backup_base
chown $dbowner:$dbgroup $backup_base/$filegdbcompressed

# --- check free space for backup ---
# Если для gbak не хватает места на диске он переходит в интерактивный режим
# с запросом пути к next volume и скрипт зависнет.

# Проверяем доступное дисковое пространство
freespace=`df $backuprootfolder --output=avail -B1|tail -n 1`
err=$?
# Необходимое для бакапа дисковое пространство равно размеру БД
if [[ $err -eq 0 ]]; then
  needspace=$(stat -c%s $datadir/$dbsubname.gdb)
  err=$?
fi
# Останавливаем скрипт если мало места
if [[ $err -eq 0  && "$freespace" != "" && "$needspace" != "" && ${freespace} -le ${needspace} ]]; then
  raise_error "Недостаточно места на диске для создания резервной копии: $freespace < $needspace"
fi
# ---

# --- Check database --- #
echo_message "Подготовка к backup"
res=`$IBPATH/bin/gfix $datadir/$dbsubname.gdb -mend -full -user $IBUSER -pass $IBPASS 2>&1`
err=$?
if [[ $err -ne 0 ]]; then
  echo_message "  $IBPATH/bin/gfix $datadir/$dbsubname.gdb -mend -full -user $IBUSER -pass *** 2>&1"
  echo_message "  Ошибка gfix $err: $res"
fi

# --- Start Backup ---

echo_message "Выполняется backup"
res=`$IBPATH/bin/gbak -B -T -G $datadir/$dbsubname.gdb $local_backup_today_folder/$dbname.gbk -USER $IBUSER -PAS $IBPASS 2>&1`
err=$?
if [ $err -ne 0 ]; then 
  echo_message "  $IBPATH/bin/gbak -B -T -G $datadir/$dbsubname.gdb $local_backup_today_folder/$dbname.gbk -USER $IBUSER -PAS *** 2>&1"
  raise_error "  Ошибка $err: $res" 1
fi

echo_message "Создание архива $local_backup_today_folder/$file_gbk_compressed"
compress_files=""
# Добавляем в архив dblist
compress_file=$dbscriptsfolder/dblist
[ -f $compress_file ] && compress_files="$compress_files $compress_file"

# Добавляем в архив dbparams
compress_file=$dbscriptsfolder/dbparams
[ -f $compress_file ] && compress_files="$compress_files $compress_file"

# Добавляем в архив расписание crontab
$sudo_dbadmin crontab -l > /tmp/crontab
compress_file=/tmp/crontab
[ -f $compress_file ] && compress_files="$compress_files $compress_file"

# Добавляем в архив gbk Файл
compress_file=$local_backup_today_folder/$dbname.gbk
[ -f $compress_file ] && compress_files="$compress_files $compress_file"

# Архивируем без вложенности
$sudo_dbadmin zip -j $local_backup_today_folder/$file_gbk_compressed $compress_files 2>&1>/dev/null
err=$?
if [ $err -ne 0 ]; then
  raise_error "  Ошибка при создании архива $local_backup_today_folder/$file_gbk_compressed; Код ошибки: $err"
fi
[ -f /tmp/crontab ] && rm -f /tmp/crontab

# Добавляем в архив databases.ps из arcafe,consulting,shop если они есть
if [ -d /home/arstudio/ar25/current/app ]; then
  cd /home/arstudio/ar25/current/app
  dist=arcafe      && [ -f "$dist/databases.ps" ] && $sudo_dbadmin zip -r $local_backup_today_folder/$file_gbk_compressed $dist/databases.ps 2>&1>/dev/null
  dist=consulting  && [ -f "$dist/databases.ps" ] && $sudo_dbadmin zip -r $local_backup_today_folder/$file_gbk_compressed $dist/databases.ps 2>&1>/dev/null
  dist=arcafe-shop && [ -f "$dist/databases.ps" ] && $sudo_dbadmin zip -r $local_backup_today_folder/$file_gbk_compressed $dist/databases.ps 2>&1>/dev/null
fi

chown $dbowner:$dbgroup $local_backup_today_folder/$file_gbk_compressed
if [ $? -ne 0 ]; then
  echo "chown $dbowner:$dbgroup $local_backup_today_folder/$file_gbk_compressed"
  raise_error "  Ошибка : $local_backup_today_folder/$dbname.gbk: $res"
fi

#----------Restore----------#
if [ $restore_param -ne 1 ]; then
  rm $local_backup_today_folder/$dbname.gbk -f
else
  # еще раз отключаем всех пользователей
  database_shut;
  # --- Rename database --- #
  echo_message "Переименование базы $datadir/$dbsubname.gdb в $datadir/$dbsubname.old.gdb"
  res=`mv -f $datadir/$dbsubname.gdb $datadir/$dbsubname.old.gdb 2>&1`
  if [ $? -ne 0 ]; then 
    echo_message "  mv -f $datadir/$dbsubname.gdb $datadir/$dbsubname.old.gdb 2>&1"
    raise_error "  Не удалось переименовать старую базу данных $dbsubname.gdb: $res" 1
  fi

# --- Start Restore --- #
  echo_message "Выполняется restore"
  res=`$IBPATH/bin/gbak -R -C -P $pagesize -REP $local_backup_today_folder/$dbname.gbk $datadir/$dbsubname.new.gdb -USER $IBUSER -PAS $IBPASS 2>&1`
  if [ $? -ne 0 ]; then
    echo_message "  $IBPATH/bin/gbak -R -C -P $pagesize -REP $local_backup_today_folder/$dbname.gbk $datadir/$dbsubname.new.gdb -USER $IBUSER -PAS *** 2>&1"
    echo_message "  Ошибка restore: $res"
    echo_message "Откат до последней версии БД"
    res=`mv -f $datadir/$dbsubname.old.gdb $datadir/$dbsubname.gdb 2>&1`
    if [ $? -ne 0 ]; then 
      echo_message "mv -f $datadir/$dbsubname.old.gdb $datadir/$dbsubname.gdb 2>&1"
      echo_message "  Ошибка переименования: $res"
    fi
    #chown $dbowner.$dbgroup $datadir/$dbsubname.gdb 2>&1
    #chmod g+w $datadir/$dbsubname.gdb 2>&1

    if [ -f $datadir/$dbsubname.gdb ]; then
      res=`rm -f $datadir/$dbsubname.new.gdb 2>&1`
      if [ $? -ne 0 ]; then 
        echo_message "rm -f $datadir/$dbsubname.new.gdb 2>&1"
        echo_message "Ошибка удаления $datadir/$dbsubname.new.gdb: $res"
      fi
    fi
    database_shut;
    raise_error "" 1
    exit 1;
  fi
  rm $local_backup_today_folder/$dbname.gbk -f

  # --- Some file operations --- #
  echo_message "Переименование базы $dbsubname.new.gdb в $datadir/$dbsubname.gdb"
  res=`mv -f $datadir/$dbsubname.new.gdb $datadir/$dbsubname.gdb 2>&1`
  if [ $? -ne 0 ]; then
    echo_message "mv -f $datadir/$dbsubname.new.gdb $datadir/$dbsubname.gdb 2>&1"
    raise_error "  Ошибка переименования новой базы данных $dbsubname.new.gdb: $res" 1
  fi

  res=`rm -f $datadir/$dbsubname.old.gdb 2>&1`
  if [ $? -ne 0 ]; then 
    echo_message "rm -f $datadir/$dbsubname.old.gdb 2>&1"
    echo_message "  Ошибка удаления старой БД $datadir/$dbsubname.old.gdb: $res"
  fi
  chown $dbowner.$dbgroup $datadir/$dbsubname.gdb 2>&1
  chmod g+w $datadir/$dbsubname.gdb 2>&1
fi

if [ $restore_param -eq 1 ] ; then
  if [ ! -f $xinetd_lock ] ; then
    echo_message "Запуск firebird ..."
    #echo_message "  $dbscriptsfolder/fb_start"
    sudo $dbscriptsfolder/fb_start
  fi
fi


#----------End Restore----------#

echo_message "Распределение архива по месячным и недельным папкам"
res=`$dbscriptsfolder/arrange $backup_base 2>&1`
if [ $? -ne 0 ] ; then
  echo_message "$dbscriptsfolder/arrange $backup_base 2>&1"
  echo_message " $res"
fi

END_TIME=$(date +%s)
seconds=$(( $END_TIME - $START_TIME ))
echo_message "Backup успешно завершен за $seconds сек"

# --- Copy backup to remote server --- #
sudo_dbadmin=""
if [ "$copy_param" == "1" ]; then
  START_SEND_TIME=$(date +%s)
  current_user=`whoami`
  [[ "$current_user" != "dbadmin" ]] && sudo_dbadmin="sudo -u dbadmin"
  # --- Создаем папку ~/db/backups/dd-mm-yy/ на удаленном сервере
  res=`$sudo_dbadmin $ssh_cmd $backup_user@$backup_host mkdir -p $remote_backup_today_folder 2>&1`
  err=$?
  if [ $err -ne 0 ]; then
    echo_message "$sudo_dbadmin $ssh_cmd $backup_user@$backup_host mkdir -p $remote_backup_today_folder"
    raise_error "  Не удалось создать папку $remote_backup_today_folder на сервере $backup_host $res" 1
  fi

  echo_message "Копирование архива базы данных $local_backup_today_folder/$file_gbk_compressed на сервер $remote_backup_today_folder"
  res=`$sudo_dbadmin $scp_cmd -q $local_backup_today_folder/$file_gbk_compressed $backup_user@$backup_host:$remote_backup_today_folder/$file_gbk_compressed 2>&1`
  err=$?
  if [ $err -ne 0 ]; then 
    echo_message "$sudo_dbadmin $scp_cmd -q $local_backup_today_folder/$file_gbk_compressed $backup_user@$backup_host:$remote_backup_today_folder/$file_gbk_compressed 2>&1"
    raise_error "  Не удалось скопировать архив на сервер $backup_host $res" 1
  fi

  # Создаем еще одну копию file_gbk_compressed в папке ~/db/backups
  echo_message "Создание на сервере $backup_user@$backup_host копии базы данных"
  res=`$sudo_dbadmin $ssh_cmd $backup_user@$backup_host cp $remote_backup_today_folder/$file_gbk_compressed $remote_backup_base/$file_gbk_compressed 2>&1`
  err=$?
  if [ $err -ne 0 ]; then 
    echo_message "$sudo_dbadmin $ssh_cmd $backup_user@$backup_host cp $remote_backup_today_folder/$file_gbk_compressed $remote_backup_base/$file_gbk_compressed 2>&1"
    raise_error "  Не удалось создать копию базы данных $remote_backup_base/$file_gbk_compressed $res" 1
  fi

  echo_message "Копируем arrange на сервер:"
  res=`$sudo_dbadmin $scp_cmd -q $dbscriptsfolder/arrange $backup_user@$backup_host:$remote_backup_base/arrange 2>&1`
  err=$?
  if [ $err -ne 0 ]; then 
    echo_message "$sudo_dbadmin $scp_cmd -q $dbscriptsfolder/arrange $backup_user@$backup_host:$remote_backup_base/arrange 2>&1"
    echo_message "  Не удалось скопировать скрипт arrange на сервер: $res"
  fi

  echo_message "Запуск arrange на $backup_host"
  res=`$sudo_dbadmin $ssh_cmd $backup_user@$backup_host $remote_backup_base/arrange $remote_backup_base $days 2>&1`
  err=$?
  if [ $err -ne 0 ]; then 
    echo_message "$sudo_dbadmin $ssh_cmd $backup_user@$backup_host $remote_backup_base/arrange $remote_backup_base $days 2>&1"
    echo_message "  Не удалось запустить arrange на сервере: $res"
  fi

  res=`$sudo_dbadmin $scp_cmd -q $LOG $backup_user@$backup_host:$remote_backup_today_folder/backup_$dbname.log 2>&1`
  err=$?
  if [ $err -ne 0 ]; then
    echo_message "$sudo_dbadmin $scp_cmd -q $LOG $backup_user@$backup_host:~/$remote_backup_today_folder/backup_$dbname.log 2>&1"
    echo_message "  $res"
  fi

  END_TIME=$(date +%s)  
  sendtime=$(( $END_TIME - $START_SEND_TIME ))
  totaltime=$(( $END_TIME - $START_TIME ))
  echo_message "Отправка файлов завершена за $sendtime сек. Общее время выполнения: $totaltime сек. "

  # Повторно отправляем log с указанным временем выполнения
  res=`$sudo_dbadmin $scp_cmd -q $LOG $backup_user@$backup_host:$remote_backup_today_folder/backup_$dbname.log 2>&1`
  if [ $? -ne 0 ]; then
    echo_message "$sudo_dbadmin $scp_cmd -q $LOG $backup_user@$backup_host:~/$remote_backup_today_folder/backup_$dbname.log 2>&1"
    echo_message "  $res"
  fi
fi
# --- End of copy backup to remote server --- #

exec 1>&6 6>&- # Restore stdout and close file descriptor #6.
