#!/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