#!/bin/bash
#==================================================
#        FILE: mybackup.sh
#
#       USAGE: mybackup.sh [args]
#
# DESCRIPTION: erzeugt Backup von Ordnern (jede Datei einzeln komprimiert) auf externem Medium
#      AUTHOR: K.B.
   VERSION="1.1.0"
#     CREATED: 2014-02-05
   REVISION=$(ls -l --time-style=+%F $0 | cut -d\  -f6)
#   $Id: $
# https://osdn.net/users/klaus_bernstein/pf/mybackup
#
# Die endgültige Datei liegt in /usr/local/sbin mit dem Namen mybackup
# Die Konfigurationsdatei mybackup.conf in /etc/mybackup/
# dies kann durch Angabe der Option -c  <file> überschrieben werden
# seit Version 0.7.0 hat diese Konfigurationsdatei die Struktur einer XML-Datei
#
# HISTORY:
# 2018-12-26 (v1.1.1): versuchsweise unter Subversion in OSDN abgelegt
# 2018-12-05 (v1.1.0): prüft bei incr Backup ob Vollbackup vorhanden
# 2018-05-01 (v1.0.2): LABEL aus conf-Datei gelesen
# 2018-02-03 (v1.0.1): max. Anzahl Vollbackup in conf-Datei ausgelagert
# 2018-01-17 (v1.0.0): minimale Logdatei angelegt
# 2016-10-01 (v0.9.3): Test, ob CONFDIR vorhanden
# 2016-09-19 (v0.9.0): aktuelle INCR-Datei vor Neuanlegen gelöscht
#
# TODO:
# 
# 2016-09-06:
# Zeitstempel überdenken
# s. http://wiki.linux-club.de/opensuse/Zeitstempel_von_Dateien
# ev. doch MODULO (o.ä.) benutzen
#
# 2016-06-12:
# jetzt=$(date +%s)
# date --date=@$jetzt
#
# Datum sechs Monate vorher:
#  date --date='-6 month'
# für das automatische Löschen des ältesten Backup
#
# 2016-06-04:
# PRE und POST-Skripte variabel halten?
# die im Konfigurationsfile angegebenen Werte durch Option --pre bzw. --post überschreiben?
# Hintergrund: Manche Aktionen (wie zB. Pakete aufräumen nicht immer ausführen)
# oder solche Sachen als cronjob laufen lassen (sauberer)
#
# Logdatei über den Backupvorgang anlegen?
#
# 2016-04-04:
# mount kontrollieren
# Überprüfen, ob das Backup-Medium (=ZIEL) ev. schon gemountet ist
#
# vorhandene Festplatten kontrollieren
# gibt es /dev/sda, /dev/sdb usw. -> wird nach POST (ev. PRE) ausgelagert
#
# DB-Rettung u. Paket-Listing verändern
# da verschiedene Datenbanken und auch verschiedene Paketmanager je nach System vorhanden sein können,
# den ganzen Mechanismus am besten in die Konfigurationsdatei verlagern (Abteilung POST und/oder PRE)
#==================================================

# Error upon expanding unset variables:
set -u

# Standard variables
__myname=${0##*/};

#########################################################
# SUBROUTINES. BEGIN.
#########################################################

function usage()
{
   cat << EOF
${__myname} [args]

mybackup erzeugt Backup von Ordnern (jede Datei einzeln komprimiert) auf externem Medium

        -h      Show this help screen
        -c      <file>  Konfigurationsfile
        -d      differentielles Backup
        -i      inkrementelles Backup
        -s      tut nichts, simuliert nur das Backup
        -v      Be very verbose (uses set -x)
        -V      Print version information

This is version ${VERSION}, released on ${REVISION}
EOF
   exit 0
}

function error_exit()
{
   ERROR_STATUS=${2:-1}
   echo "ERROR ${ERROR_STATUS}: ${1:-Unbekannter Fehler}" >&2
   exit ${ERROR_STATUS}
}

function log()
{
   $TEST && echo ${1}
}

function rdom() {
# QUELLE: https://schnallich.net/index.php/Bash/Parse_XML
   export TAG; export VAL;
   local IFS=\> ;
   read -d \< TAG VAL ;
}

function lesen() {
   while rdom
   do
      if [[ $TAG = $1 ]]; then
         echo ${VAL};
      fi;
   done < ${CONFFILE}
}

function del_old_backup() {
# sorgt dafür, daß nie mehr als MAX Vollbackups existieren   
   local LD="ls -dtr1"
   # -d  Verzeichnisse an sich auflisten, nicht deren Inhalte
   # -r  Umgekehrte Reihenfolge beim Sortieren
   # -t  Nach Änderungszeit sortieren, neueste zuerst
   # -1  zeigt eine Datei pro Zeile an
   local DIR=$1
   while [[ $($LD $DIR/[01]* | wc -l) -gt $MAX ]]
   do
      oldDir=$($LD $DIR/[01]* | head -n1)
      rm -r $oldDir
   done
}

function del_old_incr() {
# löscht alte incrementelle Backups
# als fn damit es in den Hintergrund geschickt werden kann
   local DIR=$1
   local OLD=$DIR.old
   mv $DIR $OLD
   echo "lösche $DIR"
   rm -r $OLD
}

#########################################################
# SUBROUTINES. END
#########################################################

[[ $EUID -ne 0 ]] && error_exit "This script must be run as root"

TEST=true
INCREMENTELL=false
LABEL="LBACK"                                # Standard-Label der Backup-Partition, wird in der Konfig-Datei gesetzt
CONFDIR="/etc/mybackup"                      # Standard-Directory für Konfiguration
[ -d "$CONFDIR" ] || mkdir "$CONFDIR"        # CONFDIR anlegen, wenn nicht vorhanden
CONFFILE="${CONFDIR}/mybackup.conf"          # Konfigurationsfile, s.a. Option -c
LOGDIR="/var/log/mybackup"                   # Standard-Directory für Logdatei
[ -d "$LOGDIR" ] || mkdir "$LOGDIR"          # LOGDIR anlegen, wenn nicht vorhanden
LOGFILE="${LOGDIR}/mybackup.log"
EX_FILE="${CONFDIR}/exclude"                 # enthält die exclude-Muster, wird temporär erzeugt
MNT="/mnt/backup"                            # Mountpoint für Ziel (kann in der Konfigdatei überschrieben werden)
RSYNC_OPT="-aRCP --exclude-from=${EX_FILE}"
RSYNC_DRY=""                                 # wird auf -n gesetzt, wenn rsync nur simuliert werden soll
RSYNC_INC=""                                 # Bezugsdirectory für incrementelles Backup (Option -i)

while getopts ":hc:disvV" options; do
   case $options in
        h )
            usage
            ;;
        c )
           if [ -f $OPTARG ] ;then
         CONFFILE=${OPTARG}
         log "Konfigurationsfile: ${CONFFILE}"
      else
         error_exit "Konfigurationsfile ${OPTARG} nicht vorhanden" 2
           fi
           ;;
        s )
      echo "ACHTUNG: Backup-Lauf wird nur simuliert!"
      echo "10 Sekunden zum Abbrechen mit Ctrl-C"
      TEST=false
      RSYNC_DRY="-n"
      for i in {1..2}
      do
         echo -n .
         sleep 1s
      done
      echo
      ;;
        v )
            set -v
            ;;
        d|i )
            log "Option: ${OPTIND}"
       INCREMENTELL=true
            ;;
        V )
            echo "${__myname}, Version ${VERSION}, Revision ${REVISION}"
            exit 0
            ;;
        \? )
            error_exit "Unzulässige Option -$OPTARG" 3
            ;;
    esac
done
shift $((OPTIND-1))     # Optionen "rausschieben"
#########################################################
# OPTIONEN. END
#########################################################

log "CONFDIR=$CONFDIR"
log "CONFFILE=$CONFFILE"

[ -f ${CONFFILE} ] || error_exit "${CONFFILE} nicht gefunden" 2
set -f   # Globbing ausschalten
user_mnt=$(lesen 'MOUNT')
MNT=${user_mnt:=${MNT}}
log "MNT=${MNT}"
NAME=$(lesen 'NAME')
log "NAME=${NAME}"
LABEL=$(lesen 'LABEL')
log "LABEL=${LABEL}"
QUELLE=$(lesen 'QUELLE')
log "QUELLE=${QUELLE}"
MAX=$(lesen 'MAX')
log "MAX=${MAX}"
if [[ ${MAX} = '' ]]
then
   error_exit "Kein Wert für MAX gefunden" 4
fi
EXCLUDE=$(lesen 'EXCLUDE')
log "Config for the exclude: ${EXCLUDE}"
echo "# File nicht editieren, wird automatisch erzeugt!" > ${EX_FILE}

for m in ${EXCLUDE}
do
   echo ${m} >> ${EX_FILE}
done
POST=${CONFDIR}/$(lesen 'POST')
log "Postfile: ${POST}"
set +f   # Globbing wieder an
if [ ! -f ${POST} ]
then
   error_exit "Post-File ${POST} existiert nicht" 5
fi
      
if [[ ! -d ${MNT} ]]
then
   mkdir ${MNT} || error_exit "mountpoint ${MNT} lässt sich nicht anlegen" $?
fi
mount -L ${LABEL} ${MNT} || mount -l | egrep $MNT.+$LABEL || error_exit "mount ${LABEL} an ${MNT} fehlgeschlagen" $?

ZIEL=${MNT}/${NAME}/$(date +%m)

if $INCREMENTELL
then
   [ ! -d ${ZIEL} ] && error_exit "Kein Vollbackup für $(date +"%B %Y") vorhanden" 6
   lfd_nr=$[$(date +%-d)/4]   # Minuszeichen hinter % verhindert führende Null
   RSYNC_INC="--link-dest=$ZIEL"
   ZIEL=${MNT}/${NAME}/INCR$lfd_nr
   del_old_incr ${ZIEL} &
else
   # alte Vollbackups löschen
   del_old_backup ${MNT}/${NAME} &
fi

mkdir -p ${ZIEL} || error_exit "Fehler beim anlegen von DIR ${ZIEL}"
date +"%F_%X%t$ZIEL" > ${LOGFILE}
for v in ${QUELLE}; do
   log "kopiere von ${v}-->${ZIEL}"
   rsync ${RSYNC_OPT} ${RSYNC_DRY} ${RSYNC_INC} ${v} ${ZIEL} # || error_exit "rsync" $? K.B. 2011-11-01
done

echo "==== PHASE 2 ==="
. ${POST}

exit
