#!/bin/bash
     
    #VARIABLES ET CONFIGURATION
    #**********************************************************************************
     
    #Configuration :
    LIMITATION_JOUR=1											#Mettre à 1 pour activer une limitation de la bande passante différente en journée, 0 = pas de différence
    	DEBUT_JOUR="08:00"										#Heure de début de limitation pour le jour, format hh:mm impératif
    	BP_LIMITE_JOUR=60										#Bande passante maximale en ko/s.   0 = pas de limite
     
    FIN_SAUVEGARDE="06:50"										#Heure de fin de la sauvegarde, format hh:mm (avant l'heure de début de la suivante)
    BP_LIMITE=0												#Bande passante maximale en ko/s.   0 = pas de limite
     
    MAIL_SUCCES=1											#Mettre à 0 pour ne pas recevoir le rapport de sauvegarde par mail en cas de succès. Aucune influence sur les rapports d'erreur
     
    DOSS_SCRIPT="/usr/scripts/svg"					#Dossier abritant ce script
    NOM_FLAG="flag"												#Nom du fichier "flag" (sans extension)
    EXTENSION=".log"											#Extension à ajouter aux fichiers logs générés
     
    DOSSIER_SOURCE="/media/datas"									#Dossier source à sauvegarder
    DOSSIER_WWW="/var/www/.backup"
    NAS_DISTANT="hectorpablo.fr"									#Adresse du serveur source
    PORT=22														#Port du service ssh serveur source
    USER="admlan"													#Nom d'utilisateur source
    USER_MAIL="marco.polo@live.fr"								#E-mail pour la reception des rapports
     
    CIBLE="/media/backup/sauvegardes"					#Dossier de destination des sauvegardes (un dossier par date est créé ensuite à l'intérieur)
    USER_CIBLE="root"									#Nom d'utilisateur serveur destination
     
    EXCLUS="/usr/scripts/svg/liste.txt"				#Liste des fichiers et dossiers à exclure de la sauvegarde
     
    #FIN DES VARIABLES ET DE LA CONFIGURATION, NE PAS MODIFIER PLUS BAS
    #*******************************************************************************************************************************************
     
    # La fonction envoi_mail gère l'ajout des textes explicatifs et l'envoi des mails aux utilisateurs
    #
    #Le mail envoyé dépend de l'argument passé à cette fonction :
    #	1 = Synchro précédente non terminée
    #	2 = Fin anormale de la synchro précédente (plantage script ou panne électrique)
    #	3 = Erreur lors du premier rsync d'analyse
    #	4 = Erreur lors du rsync de sauvegarde
    #	5 = Erreur lors du rsync de mise à jour arborescence
    #	6 = Le script a ete stoppé par une nouvelle occurence du script => mauvaise programmation des heures
    #	
    #	9 = Sauvegarde terminée avec succès
    #--------------------------------------------------------------------------------------------------------------------
    function envoi_mail()
    {
    	if [ $1 -eq 1 ]; then
    			echo -e '\n*** ATTENTION ! ***' >> $flag
    			echo -e 'cette sauvegarde distante n a pas pu être complétée dans les dernières 24h\n' >> $flag
    			echo -e 'aide :' >> $flag
    			echo -e 'number of files = nombre total de fichiers dans la source' >> $flag
    			echo -e 'number of files transferred = nombre de fichiers qu il y avait à envoyer : absent -ou modifiés- de la destination' >> $flag
    			echo -e 'total transferred size = Volume des données qu il y avait à envoyer\n' >> $flag
    			mail -s '=?utf-8?Q?NAS Caution: Sauvegarde distante non terminée?=' --append="From: BackupRPI <webadmin@hectorpablo.fr>" $USER_MAIL < $flag
    		elif [ $1 -eq 2 ]; then
    			echo -e '\n*** ATTENTION ! ***' >> $flag
    			echo -e 'cette sauvegarde distante n a pas été terminée correctement' >> $flag
    			echo -e 'Cela peut être du à un arrêt du NAS cible (hebergeant le script et les sauvegardes), une panne électrique ou un plantage du script\n' >> $flag
    			echo -e 'aide :' >> $flag
    			echo -e 'number of files = nombre total de fichiers dans la source' >> $flag
    			echo -e 'number of files transferred = nombre de fichiers qu il y avait à envoyer : absent -ou modifiés- de la destination' >> $flag
    			echo -e 'total transferred size = Volume des données qu il y avait à envoyer\n' >> $flag
    			mail -s '=?utf-8?Q?NAS Caution: Sauvegarde distante interrompue?=' --append="From: BackupRPI <webadmin@hectorpablo.fr>" $USER_MAIL < $flag
    		elif [ $1 -eq 3 ]; then
    			echo -e '\n ==> ERREUR RSYNC LORS DU DRY-RUN, ARRET DU SCRIPT, erreur Rsync n°'$retval >>$flag
    			mail -s '=?utf-8?Q?NAS Warning : ECHEC DE SAUVEGARDE DISTANTE LORS DE L ANALYSE?=' --append="From: BackupRPI <webadmin@hectorpablo.fr>" $USER_MAIL <$flag
    		elif [ $1 -eq 4 ]; then
    			echo -e '\n ==> ERREUR RSYNC LORS DE LA SYNCHRO, ARRET DU SCRIPT. Erreur n°'$retval >> $flag
    			mail -s '=?utf-8?Q?NAS Warning : ECHEC DE SAUVEGARDE DISTANTE?=' --append="From: BackupRPI <webadmin@hectorpablo.fr>" $USER_MAIL <$flag
    		elif [ $1 -eq 5 ]; then
    			echo -e '\n ==> ERREUR RSYNC LORS DE LA MISE A JOUR DE L ARBORESCENCE, ARRET DU SCRIPT. Erreur n°'$retval >> $flag
    			mail -s '=?utf-8?Q?NAS Warning : ECHEC DE SAUVEGARDE DISTANTE?=' --append="From: BackupRPI <webadmin@hectorpablo.fr>" $USER_MAIL <$flag
    		elif [ $1 -eq 6 ]; then
    			echo -e '\n*** ATTENTION ! ***' >> $flag
    			echo -e 'Cette sauvegarde a été interrompue suite au demarrage d une autre sauvegarde' >> $flag
    			echo -e 'VERIFIEZ LA CONFIGURATION DE L HEURE DE FIN QUI DOIT ETRE PROGRAMMEE AVANT LE DEBUT DE LA SUIVANTE' >> $flag
    			mail -s '=?utf-8?Q?NAS Warning : ECHEC SAUVEGARDE, VERIFIEZ LA PROGRAMMATION?=' --append="From: BackupRPI <webadmin@hectorpablo.fr>" $USER_MAIL <$flag
    		elif [ $1 -eq 9 ]; then
    			if [ $MAIL_SUCCES -ne 0 ]; then															#Envoie le mail si MAIL_SUCCES n'est pas 0 dans la conf
    				echo 'Sauvegarde terminée avec succès'  >> $flag
    				mail -s '=?utf-8?Q?NAS : Sauvegarde distante réussie?=' --append="From: BackupRPI <webadmin@hectorpablo.fr>" $USER_MAIL <$flag
    			fi
    	fi
    }
     
    # La fonction copie_liens permet de créer au début de la sauvegarde l'image précedente en faisant une copie de la
    #sauvegarde N-1 par des liens en durs, cette copie sera ensuite exploitée par rsync pour la mettre à jour.
    # L'utilisation de ce système à la place de l'option --link-dest de rsync permet d'utiliser l'option --detect-renamed de
    #rsync
    #----------------------------------------------------------------------------------------------------------------------
    function copie_liens()
    {
    cp -alL $CIBLE/courante $CIBLE/000_$date_jour 2>> $flag			#copie (liens durs) de la sauvegarde courante. Sert de base et est mise à jour ensuite
    echo -e '\nCopie image courante terminée...' >> $flag
    }
     
    # La fonction lien_courante permet de mettre à jour le lien symbolique "courante" sur la sauvegarde distante
    # qui pointe vers la sauvegarde actuelle (c'est l'image courante)
    # La fonction reçoit un argument qui est le dossier à lier au dossier "courante"
    #----------------------------------------------------------------------------------------------------------------------
    function lien_courante()
    {
    rm -rf $CIBLE/courante 2>> $flag							#Suppression du dossier de la sauvegarde courante...
    ln -s $CIBLE/$1 $CIBLE/courante 2>> $flag					#... et recréation (en fait un simple lien vers la sauvegarde du jour)
    }
     
    # La fonction rotation_sauvegardes permet d'incrémenter le numéro des dossiers contenant les sauvegardes en conservant la date, par
    # exemple : 005_2012-12-12 est renommé en 006_2012-12-12. La sauvegarde numero 999 est supprimée.
    # Cette fonction supprime également les sauvegardes incompletes (nom de dossier se terminant par _incomplete) à partir de la 25eme sauvegarde
    #----------------------------------------------------------------------------------------------------------------------
    function rotation_sauvegarde()
    {
    if [ -d $CIBLE/999_* ];then  						#suppression de la sauvegarde numéro 999 si elle existe
            echo "suppression de la sauvegarde 999" >> $flag
            rm -rf $CIBLE/999_* 2>> $flag
    fi
     
    ls $CIBLE/[0-9]* -dr|while read -r; do					#Listing répertoire avec filtre sur les sauvegardes
            let i=10#$(printf ${REPLY#${CIBLE}/} | cut -d "_" -f1)		#Récuperation numéro sauvegarde
            if [ $i -gt 25 ] && [[ $REPLY = *_incomplete ]]; then		##à partir de la 25eme sauvegarde (et après) on teste si c'est une incomplète
                    echo "suppression de la sauvegarde incomplète $i" >> $flag	
                    rm -rf $REPLY 2>> $flag  #si c'est une incomplète, on la supprime
            else
            	let j=$i+1						#génére le numéro n+1 sur 3 chiffres
            	mv $REPLY $CIBLE/$(printf "%.3d" $j)_$(printf ${REPLY#${CIBLE}/} | cut -d "_" -f2-)	#renomme la sauvegarde n en n+1
            fi
    done
    }
     
    # La fonction suppression sauvegarde permet de supprimer les anciennes sauvegardes en en gardant 1 seule par interval défini. Elle reçoit
    # 3 paramètres : une butée haute, une butée basse et un interval (dans cet ordre). La série entre les deux butées est divisées en intervals
    # de la longueur définie par le paramètre interval puis une seule sauvegarde est conservée dans celui ci.
    #Note : si la longueur de la série entre les butées n'est pas divisible par l'interval, le reste ne correspondant pas à un interval complet est
    #ignoré (ex : de 100 à 75 par pas de 10 : on garde une sauvegarde entre 100 et 91, une entre 90 et 81 mais rien n'est supprimé entre 80 et 75
    #-----------------------------------------------------------------------------------------------------------------------------------------------
    function suppression_sauvegardes()
    {
    for ((i=$2;i<=$1;i=i+$3))
    do
    let j=$i+$3-1
     
    cpt=$(ls $CIBLE | cut -d "_" -f1 | awk -v i=$i -v j=$j '$1>=i && $1<=j' | wc -l)
    if (($cpt > 1));then
            for f in $(ls $CIBLE | cut -d "_" -f1 | awk -v i=$i -v j=$j '$1>=i && $1<=j' | head -n -1)
            do
                    echo "suppression de la sauvegarde $f" >> $flag
                    rm -rf $CIBLE"/"$f* 2>> $flag
            done
    fi
    done
    }
     
    #La fonction finalisation permet d'effectuer les dernières opérations communes à la fin de la sauvegarde normale :
    #Ces opérations sont la rotation des sauvegardes puis la mise à jour du lien vers la sauvegarde courante et enfin la 
    #gestion de la suppression des plus anciennes sauvegardes selon une répartition spécifique et variable dans le temps
    #-----------------------------------------------------------------------------------------------------------------------------------------------
    function finalisation()
    {
    echo -e '\nSauvegarde terminée à '$(date +%H:%M:%S)'\nRotation des sauvegardes...'  >> $flag
    rotation_sauvegarde																			#Effectue le renommage de toutes les sauvegardes n en n+1 (rotation)
    echo 'Rotation terminée, effacement et recréation du dossier de sauvegarde courante...'$(date +%H:%M:%S)  >> $flag
    lien_courante 001_$date_jour																#Suppression du dossier de la sauvegarde courante et recréation (en fait un simple lien vers la sauvegarde du jour)
    suppression_sauvegardes 1000 401 200														#Suppression de quelques anciennes sauvegardes avec une répartition variable selon l'ancienneté
    suppression_sauvegardes 400 131 30
    suppression_sauvegardes 130 41 10
    envoi_mail 9
    echo 'SVG terminée'$(date +%H:%M:%S) >> $flag											#...déplace et renomme le fichier flag dans les logs
    mv $flag $DOSS_SCRIPT/log/$date_jour_detailled$EXTENSION	
    }
     
    #La fonction finalisation_incomplete permet d'effectuer les dernières opérations communes à la fin d'une sauvegarde incomplete :
    #renommage du dossier de sauvegarde avec ajout du suffixe "incomplete", rotation des sauvegardes puis la mise à jour du lien vers la sauvegarde courante
    #-----------------------------------------------------------------------------------------------------------------------------------------------
    function finalisation_incomplete()
    {
    echo -e '\nSauvegarde incomplete terminée à '$(date +%H:%M:%S)'\nRotation des sauvegardes...'  >> $flag
    mv $CIBLE/000_$date_jour $CIBLE/000_${date_jour}_incomplete
    rotation_sauvegarde
    echo 'Rotation terminée, effacement et recréation du dossier de sauvegarde courante...'  >> $flag
    lien_courante 001_${date_jour}_incomplete								#Mise à jour de la sauvegarde courante
    envoi_mail 1
    mv $flag $DOSS_SCRIPT/log/$date_jour_detailled-incomplete$EXTENSION				#déplacement fichier flag en log avec spécification incomplete
    }
     
    #La fonction finalisation_erreur permet d'effectuer les dernières opérations communes à la fin d'une sauvegarde qui se termine en erreur :
    #renommage du dossier de sauvegarde avec ajout du suffixe "incomplete", rotation des sauvegardes puis la mise à jour du lien vers la sauvegarde courante
    #-----------------------------------------------------------------------------------------------------------------------------------------------
    function finalisation_erreur()
    {
    echo -e '\nErreur d execution à '$(date +%H:%M:%S)'\nSuppression du dossier 000_'$date_jour >> $flag
    rm -r $CIBLE/000_$date_jour >> $flag 2>&1								#supprime le dossier 000 dans lequel on faisait la sauvegarde
    if [ $1 -eq 4 ] && [ -e $DOSS_SCRIPT/tempRsync ]; then					#si erreur lors de la synchro (1er argument = 4) on supprime aussi le dossier
    	echo -e '\nSuppression du dossier tempRsync' >> $flag				#des incomplets rsync
    	rm -r $DOSS_SCRIPT/tempRsync
    fi
    envoi_mail $1															#envoi du mail selon le numéro d'erreur reçu en argument
    mv $flag $DOSS_SCRIPT/log/$date_jour_detailled-erreur$EXTENSION					#déplace le fichier de flag dans le dossier des logs et le renomme
    }
     
    #PROGRAMME PRINCIPAL, contrôle l'état actuel, prépare le dossier de sauvegarde, lance une analyse (dry-run rsync), 
    #puis copie l'arborescence des dossiers (sans supprimer les anciens) afin de permettre le bon fonctionnement de --detect-renamed
    #en cas de fichiers déplacés ou dossiers renommés et enfin lance la sauvegarde effective des fichiers.
    #Inclus le contrôle des codes retour des fonctions principales pour s'assurer de la bonne exécution.
    #----------------------------------------------------------------------------------------------------------------------
    flag=$DOSS_SCRIPT/$NOM_FLAG$EXTENSION									#définition du fichier de flag
    SOURCE=$USER@$NAS_DISTANT:$DOSSIER_SOURCE								#et de la source
    #SOURCE=$DOSSIER_SOURCE								#et de la source
    #Si il y a déjà un fichier flag (anormal)=> la synchro de la veille n'est soit pas terminée (mauvaise programmation heure de fin) ou stoppée due à plantage ou arrêt système)
    if [ -e $flag ]; then
    	pkill -SIGUSR1 -f -u $USER_CIBLE rsync								#tente de fermer Rsync avec le signal SIGUSR1 (le signal est testé par le script de la veille et lui signal de se fermer également)
    	sleep 120															#Tempo 120 secondes
     
    	if [ -e $flag ]; then												#Re-teste présence fichier flag. Si oui c'est que le script précédent n etait plus lance => il y a eu plantage ou arret de la machine l hébergeant
    		date_veille=$(head -n 1 $flag | cut -c15-)						#récupère la date sauvegarde interrompue (dans 1ere ligne du fichier flag)
    		echo -e '\n!!!!!!!!!!\nScript du' $(date +%Y-%m-%d) >> $flag
    		echo -e 'Suppression du dossier : 000_'$date_veille >> $flag
    		rm -r $CIBLE/000_$date_veille >> $flag 2>&1						#supprime le dossier 000_ dans lequel la précédente sauvegarde a echoue
    		envoi_mail 2													#mail sauvegarde non terminée cause arrêt NAS ou plantage script
    		mv $flag $DOSS_SCRIPT/log/$date_veille-erreur$EXTENSION			#déplace le fichier de flag dans le dossier des logs et le renomme
    	fi
    fi
     
    date_jour=$(date +%Y-%m-%d)												#Enregistrement date du jour
    date_jour_detailled=$(date +%Y-%m-%d-%H-%M-%S)												#Enregistrement date du jour
    #Effacement et reCréation du fichier de flag :
    echo 'SAUVEGARDE DU '$date_jour > $flag									#NE PAS MODIFIER CETTE LIGNE
     
    last_write=$((($(date +%s)-$(stat -c %Y /media/backup/sauvegardes/courante))/60))
     
     
    #test rep datas vide
    if [ $(ssh admlan@hectorpablo.fr 'ls /media/datas/ | wc -l') -eq 0 ]; then
    	echo "Dossier datas vide" >> $flag
    	echo "Annulation de la sauvegarde" >> $flag	
    	echo 'SVG terminée'$(date +%H:%M:%S) >> $flag											#...déplace et renomme le fichier flag dans les logs
    	mv $flag $DOSS_SCRIPT/log/$date_jour_detailled-nosvg$EXTENSION	
    	exit
    #test modif depuis derniere
    elif [ $($DOSS_SCRIPT/rsync -ahzn -e "ssh -p $PORT" --stats --delete --delete-excluded --detect-renamed --timeout=60 --exclude-from=$EXCLUS $SOURCE $CIBLE/001_* | egrep -w 'Number of deleted files: 0|Number of created files: 0|Number of regular files transferred: 0' | wc -l) -eq 3 ]; then
            echo "Pas de modifications" >> $flag
    	echo "Annulation de la sauvegarde" >> $flag
            echo 'SVG terminée'$(date +%H:%M:%S) >> $flag                                                                                   #...déplace et renomme le fichier flag dans les logs
            mv $flag $DOSS_SCRIPT/log/$date_jour_detailled-nosvg$EXTENSION
            exit
     
    fi
     
    #Création du dossier de sauvegarde du jour : on recopie en liens l'image courante :
    copie_liens
     
    #Copie de l'arborescence des dossiers uniquement (ordre des option --includes/--exclude important, pas de --delete)
    echo -e '\nDébut mise à jour arborescence à '$(date +%H:%M:%S) >> $flag
    $DOSS_SCRIPT/rsync -ahz -e "ssh -p $PORT" --timeout=60 --exclude-from=$EXCLUS --include='*/' --exclude='*' $SOURCE $CIBLE/000_$date_jour >> $flag 2>&1
    #$DOSS_SCRIPT/rsync -ahzv --timeout=60 --exclude-from=$EXCLUS --include='*/' --exclude='*' $SOURCE $CIBLE/000_$date_jour >> $flag 2>&1
    retval=$?																						#enregistre le code retour de rsync
    if [ $retval -ne 0 ];	then																	#contrôle du code  retour
    	#si erreur (clés ssh, connexion, serveur distant éteind, indisponible, ...) : on finalise et on quitte
    	finalisation_erreur 5
    	exit
    fi
    echo -e 'Mise à jour arborescence terminée à '$(date +%H:%M:%S) >> $flag
     
    #Lancement de la synchro réelle :
    #if [ $LIMITATION_JOUR -ne 0 ]; then								#Si débit different pour le jour on règle l'heure de fin en conséquence
    #	heure_fin=$DEBUT_JOUR
    #	else
    #		heure_fin=$FIN_SAUVEGARDE
    #fi
     
    echo -e '\n\nDébut de sauvegarde à '$(date +%H:%M:%S) >> $flag
    echo -e 'Rapport de sauvegarde :\n----------------------------------------------' >> $flag
    $DOSS_SCRIPT/rsync -ahzv -e "ssh -p $PORT" --stats --delete --delete-excluded --detect-renamed --timeout=60 --partial --partial-dir="tempRsync" --exclude-from=$EXCLUS $SOURCE $CIBLE/000_$date_jour >> $flag 2>&1
    #$DOSS_SCRIPT/rsync -ahzv --stats --delete --delete-excluded --detect-renamed --timeout=60 --bwlimit=$BP_LIMITE --stop-at=$heure_fin --partial --partial-dir="tempRsync" --exclude-from=$EXCLUS $SOURCE $CIBLE/000_$date_jour >> $flag 2>&1
    retval=$?																					#enregistrement et contrôle du code retour
     
    if [ $retval -eq 0 ]; then			#si rsync est quitté normalement, terminé avant l'heure maxi, déroulement normal...
    	finalisation																			#on appelle la fonction de finalisation
    	exit
    elif [ $retval -eq 19 ]; then		#si rsync est quitté avec le code 19 (SIGUSR1) alors finalise et quitte le script car une nouvelle occurence s'est lancée...
    	finalisation_erreur 6
    	exit
    elif [ $retval -ne 0 ]; then		#si on a une erreur autre, on finalise et on quitte
    	finalisation_erreur 4
    	exit
    fi