#!/bin/bash
#

# Copyright (C) 2017  Tetras Libre <admin@tetras-libre.fr>
# Author: Beniamine, David <David.Beniamine@tetras-libre.fr>
#
# 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 3 of the License, 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, see <http://www.gnu.org/licenses/>.

do_clean(){
    do_log "Demontage du disque veuillez patienter avant de le retirer"
    if $encfs
    then
        fusermount -zu $encfsmount
    fi
    if $luks
    then
        cryptsetup luksClose $luksname
    fi
    umount -f $dev
}

# If $1 is not 0, exit with message $2
test_and_fail(){
    if [ $1 -ne 0 ]
    then
        echo "Echec de la sauvegarde : '$2'"
        do_clean
        exit $1
    fi
}

# Retourne le premier element
premier(){
    echo $1
}

# Supprime la sauvegarde la plus ancienne
supprimer_anciennes_sauvegardes(){
    dir=`premier \`/bin/ls $parent_dir | grep $postfix \``
    [ -z "$dir" ] && return 1
    do_log "Plus de place suppression de la sauvegarde la plus ancienne: $dir"
    rm -rf $parent_dir/$dir
    return $?
}

do_log(){
    echo "Sauvegarde NoCloud - $@"
}

dump_databases(){
    if $mysql
    then
        do_log "Sauvegarde mysql"
        mysqldump --events --single-transaction --flush-logs --all-databases \
            | gzip > /root/db.sql.gz
        test_and_fail $? "Impossible de sauvegarde la base de donnée mysql"
    fi
    if $postgres
    then
        do_log "Sauvegarde postgresql"
         sudo -u postgres pg_dumpall > /root/pg_dump.sql
        test_and_fail $? "Impossible de sauvegarde la base de donnée postgresql"
         gzip /root/pg_dump.sql
     fi
    if $gitlab
    then
        # the backup should be in /var/opt/gitlab/backups thus in srv_directories
        do_log "Creation de la sauvegarde gitlab"
        /usr/bin/gitlab-rake $voptminus gitlab:backup:create
        backup_path=`grep "'backup_path'" /etc/gitlab/gitlab.rb  | sed 's/^.*= "\(.*\)"$/\1/'`
        if [ -d "$backup_path" ]
        then
            /bin/ls -dt $backup_path/* | tail -n +11 | xargs rm -rf
        else
            do_log "Dossier de sauvegarde gitlab non trouvé, pensez à supprimer manuellement les anciennes sauvegardes"
        fi
    fi

}

sauvegarde_serveur(){
    dump_databases
    do_log "Creation de l'archive configuration serveur"
    [ ! -z "$SERVICES" ] && systemctl stop $SERVICES
    tar czf$vopt $dest/serveur.tgz $srv_directories
    ret=$?
    [ ! -z "$SERVICES" ] && systemctl start $SERVICES
    if [ $ret -eq 1 ]
    then
        # only warn on tar return code 1
        do_log "Attention: des fichiers on été modifié durant la sauvegarde de l'archive serveur"
        return 0
    else
        return $ret
    fi
}

sauvegarde_donnees(){
    if $archive
    then
        tar cvzf $dest/Donnees.tar.gz /home
    else
        cp $voptminus -a /home $dest/Donnees
    fi
}

sauvegarde_clone(){
    dump_databases
    cmd="rsync -aAX /"
    EXCLUDE='/dev,/proc,/var/run,/var/cache,/var/backups,/var/lock,/sys,/tmp,/run,/mnt,/media,/lost+found"'
    for filtre in $(echo "$EXCLUDE,$EXCLUDEFROMCLONE" | sed 's/,/ /g')
    do
        cmd="$cmd --exclude=$filtre"
    done
    cmd="$cmd $dest"
    do_log "Running : '$cmd'"
    $cmd
}

sauvegarde_seafile(){
    # On sauvegarde le contenu des bibliothèques
    echo "Sauvegarde - Contenu Seafile"
    if [ ! -d /mnt/seafile-fuse ] ; then mkdir /mnt/seafile-fuse ; fi
    # Demonte le dossier si file au cas ou le dernier backup l'ait laisse dans un drole d'etat
    fusermount -zu /mnt/seafile-fuse
    /srv/$seafile/seafile-server-latest/seaf-fuse.sh start /mnt/seafile-fuse
    if $archive
    then
        tar cvzf $dest/contenus_seafile.tar.gz /mnt/seafile-fuse
    else
        /usr/bin/rsync -rtv --exclude 'seafile-data/storage' --modify-window=2 /mnt/seafile-fuse/ /$dest/contenus_seafile/
    fi
    /bin/sync
    /srv/$seafile/seafile-server-latest/seaf-fuse.sh stop
}

# tente de liberer de l'espace si c'est pertinent puis rejoue les commandes
# passées en arguments
liberer_espace(){
    if [ "`df --output=avail $parent_dir  | sed 1d`" -lt $MIN_SIZE ]
    then
        supprimer_anciennes_sauvegardes
        test_and_fail $? "Plus d'espace sur le disque et pas d'ancienne sauvegarde a supprimer"
        $@
        ret=$?
    else
        if $warn_only
        then
            echo "l'action $@ a indiqué des erreurs, mais la sauvegarde continue"
        else
            test_and_fail 1 "l'action '$@' a planté innopinement"
        fi
    fi
}

usage(){
    echo "Utilisation $0 [options] device"
    echo "Device doit etre un disque non monté"
    echo "Options"
    echo "  -h | --help             Affiche cette aide et quitte"
    echo "  -v | --verbose          Active le mode verbeux"
    echo "  -a | --archive          Archive les donnes au lieu de les copier"
    echo "  -d | --data             Sauvegarde les donnees (/home)"
    echo "  -p | --postgresql       Sauvegarde postgresql (implique --config)"
    echo "  -m | --mysql            Sauvegarde mysql (implique --config)"
    echo "  -c | --config           Sauvegarde le serveur ($srv_directories)"
    echo "  -C | --Clone    exclude Effectue un clone rsync, exclude est une liste
                                    de dossier a exclure separe par des ','"
    echo "  -g | --gitlab           Sauvegarde gitlab (implique --config)"
    echo "  -u | --unifi            Sauvegarde unifi (/var/lib/unifi, implique
                                    --config)"
    echo "  -s | --seafile  host    Sauvegarde seafile host  (seafile fuse)"
    echo "  -e | --encfs    pass    Utilise des dossiers chiffrés encfs,
                                    protégés par le mot de passe donné"
    echo "  -i | --interrupt list   Interrompt les services donnés (liste
                                    séparé par des virgules) durant la création
                                    de l'archive tar."
    echo "  -l | --luks    pass     Utilise des disques chiffres luks,
                                    protégés par le mot de passe donné"
    echo "  -w | --warnonly         En cas d'erreur d'une action de sauvegarde,
                                    le programme affiche un message mais ne
                                    s'nterrompt pas"
    echo "  -P | --Postcmd          Execute la commande donnee apres la
                                    sauvegarde"
}

dest=/mnt/backup
date=`date +%Y-%m-%d_%H-%M-%S`
postfix=_sauvegarde_`hostname`
srv_directories="/root /etc /srv /var/www /usr /lib /opt /var/opt"
data_directories="/home"
ACTIONS=""
gitlab=false
mysql=false
postgres=false
encfs=false
luks=false
luksname=crypt_backup
archive=false
clone=false
MIN_SIZE=$((1024*1024))
warn_only=false
post_cmd=""

# Transform long options to short ones
for arg in "$@"; do
  shift
  set -- "$@" `echo $arg | sed 's/^-\(-.\).*$/\1/'`
done
optspec=":hvC:dcguws:mpe:i:l:aP:"
while getopts "$optspec" optchar; do
    case "${optchar}" in
        h)
            usage
            exit 0
            ;;
        v)
            vopt="v"
            voptminus="-v"
            ;;
        a)
            archive=true
            ;;
        d)
            ACTIONS+="\nsauvegarde_donnees"
            ;;
        c)
            ACTIONS+="\nsauvegarde_serveur"
            ;;
        g)
            ACTIONS+="\nsauvegarde_serveur"
            gitlab=true
            ;;
        u)
            ACTIONS+="\nsauvegarde_serveur"
            srv_directories+=" /var/lib/unifi"
            ;;
        s)
            seafile="$OPTARG"
            ACTIONS+="\nsauvegarde_seafile"
            ;;
        m)
            mysql=true
            ACTIONS+="\nsauvegarde_serveur"
            ;;
        p)
            postgres=true
            ACTIONS+="\nsauvegarde_serveur"
            ;;
        e)
            encfs=true
            ENCPASS="$OPTARG"
            ;;
        i)
            SERVICES="${OPTARG//,/ }"
            ;;
        l)
            luks=true
            ENCPASS="$OPTARG"
            ;;
        w)
            warn_only=true;
            ;;
        P)
            post_cmd="$OPTARG"
            ;;
        C)
            clone=true
            EXCLUDEFROMCLONE="$OPTARG"
            ACTIONS="\nsauvegarde_clone"
            ;;
        *)
            echo "Option inconnue -$optchar"
            usage
            exit 1
            ;;
    esac
done
shift $(($OPTIND - 1))
ACTIONS=`echo -e $ACTIONS | sort -u | sed /^$/d`

if [ -z "$1" ]
then
    usage
    test_and_fail 1 "Pas de disque donnée, abandon"
fi
# $1 should be an unmounted device like /dev/sdb1
dev=$1

do_log "démarrage le `date`"

[ ! -d "$dest" ] && mkdir "$dest"

if $luks
then
    echo $ENCPASS | cryptsetup luksOpen $dev $luksname
    test_and_fail $? "Impossible de déchiffrer le disque, abandon"
    /bin/mount -t auto /dev/mapper/$luksname $dest
    echo "OK" > $dest/test
else
    /bin/mount -t auto $dev $dest
fi
test_and_fail $? "Impossible de monter le disque destination, abandon"

if $encfs
then
    crypted="$dest/crypted"
    encfsmount="$dest/backups"
    encfscmd="/usr/bin/encfs --stdinpass $crypted $encfsmount"
    if [ ! -d $crypted ]
    then
        # First call with --encfs
        rm -rf $encfsmount
        echo -e "y\ny\np\n$ENCPASS\n" | $encfscmd
    else
        echo -e "$ENCPASS\n" | $encfscmd
    fi
    test_and_fail $? "Impossible de monter le coffre chiffré"
    unset ENCPASS
    dest=$encfsmount
fi

parent_dir=$dest
if ! $clone
then
    dest="$parent_dir/$date$postfix"
fi
mkdir "$dest"
[ ! -d "$dest" ] && liberer_espace mkdir "$dest"

for action in $ACTIONS
do
    $action
    ret=$?
    while [ $ret -ne 0 ]
    do
        liberer_espace $action
        ret=$?
    done
    nom=${action/_/ }
    do_log "$nom reussie"
done

# force sync of files to disk before unmounting
/bin/sync
do_log "Resultats de la sauvegarde:"
du -h -d 1 $dest/
df -h $dev
do_clean
if [ ! -z "$post_cmd" ]
then
    do_log "Execution de la commande post sauvegarde"
    $post_cmd
fi
do_log "Sauvegarde terminee le `date`"