2 fév
Shell : Backup multiples et séparés de comptes FTP
Après le script shell de sauvegardes des bases de données mysql je m’attaque à la sauvegardes des comptes FTP liés aux sites internet sur l’un de mes serveurs dédiés (ndlr – dibribubtion : Gentoo Release 2 de ovh). De la même façon que la dernière fois, je suis parti du principe que je devais « faire avec ce que j’avais » et ne rien installer sur le serveur pour m’occuper de mes backups et, cette fois encore, j’ai trouvé des scripts très intéressant sur le sujet mais aucun ne faisaient exactement ce que je voulais… je me suis donc de nouveau retroussé les manches et j’ai ressorti la planche à coder. ![]()
L’idée du script
Je suis parti du même principe que la dernière fois, je voulais pouvoir séparer les sauvegardes. Faire un backup de tout le /home c’est pratique c’est vrai et au moins on est sur de ne rien oublier. Le hic, c’est que si l’on a 20 sites d’installés sur le serveur et que l’on souhaite récupérer quelques fichiers perdus sur un seul de ces sites et que nos sauvegardes ne sont stockés que sur un serveur ftp alloué par ovh on peut facilement perdre un temps considérable à télécharger une archive très lourde alors qu’au final on n’a besoin que de quelques mo… surtout sur les ftp de backup qu’ovh met à disposition gratuitement avec tout serveur dédié. Ce n’est pas une critique négative, leur serveurs ftp sont très bien (surtout pour du gratuit) mais on est beaucoup dessus et les vitesses de transfert varient fréquemment, surtout si les archives font plusieurs gigas. Ca arrive plus de souvent de perdre des fichiers sur un site que de crasher tout un serveur en perdant tout son contenu.
J’aime bien compartimenter les choses, et là l’idée était de créer une archive par site (ou plutôt par compte ftp) et stocker les sauvegardes dans des répertoires différents. En cas d’urgence, si un site est en panne à cause d’un manque de fichiers, il suffit de télécharger la dernière archive du site en question, et pas forcément de l’ensemble du /home du serveur.
J’avais donc ces quelques exigences pour le script :
- Pouvoir automatiquement lister les sites et créer leurs répertoires automatiquement
- En cas de suppression d’un site, que ses archives soient aussi supprimées du ftp
- Pouvoir dupliquer cette arborescence sur le serveur ftp stockant les sauvegardes
- Génération de log temporaire, post de son contenu par mail puis intégration de son texte dans un log général
- Purger proprement les archives en local et en ftp
Le script
Le script est une fois de plus, beaucoup commenté. C’est du au fait que j’ai pas mal galéré par moments et d’autres, c’est tout simplement pour que je puisse m’en rappeler.
Il est en deux parties, le premier fichier, est un simple fichier texte contenant la liste des répertoire a exclure de la sauvegarde. Ca permet entre autre d’éviter de sauvegarder le répertoire contenant les sauvegardes.
fichier : backup_exclude.txt
/home/backup /home/vpopmail
Il suffit de mettre une url absolue par répertoire
Le script, fichier : backup_ftp.sh
#!/bin/bash
DIRTOBACKUP="/home/" # Rep contenant les FTP a sauvegarder
EXCLUDEFILE="/home/backup/ftp/backup_exclude.txt" # Fichier listant les repertoires a ne pas sauvegarder
EMAIL="adresse@mail.com" # Email
MAILSEND=1 # Post d'un mail? (1=Oui, 0=Non)
SUJETMAIL="Backup comptes FTP quotidien" # Sujet du mail
BACKUPDIR="/home/backup/ftp/" # Rep de backup (temporaire si KEEPLOCAL = 0). Ne pas oublier le "/" à la fin (important !!)
OLD_DAYS=5; # Nombre de jours où les backup sont conservés
K_DAYS=`date --date $OLD_DAYS' days ago' "+%Y-%m-%d"`; # On construit la recherche des anciens fichiers
LOG="/home/log/backup_ftp.log"; # Log des backup
DATE="$(date +"%Y-%m-%d")"; # Date du jour
DATE_NOW=`date +"%d/%m/%Y - %H:%M:%S"`; # Date et heure en temps réel (pour le log)
ARCHIVE="backup_"$DATE".tar.gz"; # Noms des archives
OLD_ARCHIVE="backup_"$K_DAYS".tar.gz"; # Les anciennes archives
KEEPLOCAL=1 # Garder une copie locale? (1=Oui, 0=Non)
FTP_BACKUP=1 # Copie sur FTP? (1=Oui, 0=Non)
FTP_SERVER="ftp.ftp_backup.com" # Serveur FTP
FTP_USER="login_ftp" # Login utilisateur FTP
FTP_PASS="pass_ftp" # Pass utilisateur FTP
FTP_DIR="/rep_distant_ftp/" # Répertoire sur le FTP (par exemple : /backups/serveur1/bdd)
NCFTPPUT="/usr/bin/ncftpput" # Emplacement de ncftpput
NCFTPLS="/usr/bin/ncftpls" # Emplacement de ncftpls
NCFTP="/usr/bin/ncftp" # Emplacement de ncftp
# --------------------------------------------------------------------- #
# FIN DES PARAMETRES, ne rien changer ensuite #
# --------------------------------------------------------------------- #
# creation du log temporaire
TMP_LOG=$BACKUPDIR"tmp_backup.log";
TMP_LIST=$BACKUPDIR"tmp_liste_ftp.txt";
>$TMP_LOG;
echo "-- Rapport généré automatiquement par $(basename $0)" >> $TMP_LOG;
echo "" >> $TMP_LOG;
>$TMP_LIST;
cd $DIRTOBACKUP
# on liste tous les sous repertoires
for DIR in `find -maxdepth 1 -mindepth 1 -type d`
do
# on verifie qu'ils ne sont pas présents dans le fichier d'exclusion
grep -q "$DIR" $EXCLUDEFILE
# pas dans le fichier d'exclusion? OK roule ...
if [ ! $? -eq 0 ]; then
# on cree le repertoire qui accueillera les archives du ftp si il n'existe pas
if [ ! -d "$BACKUPDIR$(basename $DIR)" ]; then
echo "["$DATE_NOW"] - Création du répertoire contenant les archives pour : "$DIR >> $TMP_LOG;
mkdir -v $BACKUPDIR$(basename $DIR) >> $TMP_LOG ;
fi
# on place le nom du repertoire dans un fichier listing (ajout d'un / pour eviter de trouver "test" dans "testdenis")
echo $(basename $DIR)"/" >> $TMP_LIST;
# on genere l'archive du ftp et on la place dans son rep
tar -czf $BACKUPDIR$(basename $DIR)"/"$ARCHIVE $DIR;
if [ ! $? -eq 0 ]; then
echo "["$DATE_NOW"] - Une erreur est survenue pendant la creation de l'archive : '"$ARCHIVE"'" >> $TMP_LOG;
cat $TMP_LOG >> $LOG;
rm $TMP_LOG;
exit;
fi
# taille de l'archive + log de l'action
FILESIZE=`ls -lh $BACKUPDIR$(basename $DIR)"/"$ARCHIVE | awk '{print $5}'`
echo "["$DATE_NOW"] - Génération de l'archive pour : "$(basename $DIR)" : "$BACKUPDIR$(basename $DIR)"/"$ARCHIVE" ("$FILESIZE")" >> $TMP_LOG;
fi
done
# On supprime les archives trop anciennes et on renseigne le log
# Note : on recherche par date de derniere modification (mtime), si un répertoire a été exclu des sauvegardes alors qu'il
# avait déjà des sauvegardes, ses sauvegardes seront supprimées au bout de $OLD_DAYS jours ainsi que le répertoire les contenant
echo "["$DATE_NOW"] - Recherche et supprime les archives ayant plus de "$OLD_DAYS" jours" >> $TMP_LOG;
find $BACKUPDIR \( ! -name '$(basename $EXCLUDEFILE)' ! -name '$(basename $TMP_LOG)' \) -mtime +$OLD_DAYS -exec rm -drv {} \; >> $TMP_LOG ;
# on boucle sur le répertoire FTP et on compare avec le répertoire local si il n'y en a pas en trop, si oui -> on ajoute l'action de suppression
echo "["$DATE_NOW"] - Listing des commandes de suppression de repertoire (si non présent en local)" >> $TMP_LOG;
i=0
for DIR in `$NCFTPLS -u $FTP_USER -p $FTP_PASS -x "-1" "ftp://"$FTP_SERVER$FTP_DIR`
do
# -a = && en php -o = || en php
if [ "$DIR" != "." -a "$DIR" != ".." ]; then
# si le rep n'est pas présent dans la liste, on le supprime
grep -q "$DIR/" $TMP_LIST
if [ ! $? -eq 0 ]; then
echo "["$DATE_NOW"] - Le répertoire '"$DIR"' n'est pas présent en local -> suppression" >> $TMP_LOG;
CMDRMDIR[$i]="rm $FTP_DIR$DIR/*; rmdir $FTP_DIR$DIR;";
i=$i+1
fi
fi
done
# maintenant on boucle sur le repertoire local contenant les archives et on liste les actions que l'on va faire effectuer a ncftp
echo "["$DATE_NOW"] - Listing des commandes de création de repertoire (si un nouveau est détecté en local) et de supression des vieilles archives" >> $TMP_LOG;
CREAREP=""
DELOLD=""
PUTCMD=""
i=0
for DIR in `find -maxdepth 1 -mindepth 1 -type d`
do
grep -q "$DIR" $EXCLUDEFILE
# pas dans le fichier d'exclusion? OK roule ...
if [ ! $? -eq 0 ]; then
$NCFTPLS -u $FTP_USER -p $FTP_PASS -x "-1" "ftp://"$FTP_SERVER$FTP_DIR$(basename $DIR)"/"
# si le repertoire n'existe pas sur le ftp, on le crée et on ne cherche pas a supprimer les anciennes archives présente dedans
if [ ! $? -eq 0 ]; then
echo "["$DATE_NOW"] - Création sur le ftp du répertoire : "$(basename $DIR) >> $TMP_LOG;
CREAREP=$CREAREP"mkdir $(basename $DIR); ";
DELOLD="";
else
echo "["$DATE_NOW"] - Suppression de l'archive '"$OLD_ARCHIVE"' dans :"$(basename $DIR) >> $TMP_LOG;
CREAREP="";
DELOLD=$DELOLD"rm $DIR/$OLD_ARCHIVE; ";
fi
echo "["$DATE_NOW"] - Upload de l'archive '"$BACKUPDIR$(basename $DIR)"/"$ARCHIVE"' dans :"$(basename $DIR) >> $TMP_LOG;
#PUTCMD[$i]="cd $FTP_DIR$(basename $DIR); put -f $BACKUPDIR$(basename $DIR)/$ARCHIVE;";
PUTCMD[$i]="$NCFTPPUT -m -v -u $FTP_USER -p $FTP_PASS $FTP_SERVER $FTP_DIR$(basename $DIR) $BACKUPDIR$(basename $DIR)/$ARCHIVE";
let i++
# ((i++)) fonctionne aussi
fi
done
echo "["$DATE_NOW"] - Execution des commandes de création / suppressions de répertoires et d'anciennes archives" >> $TMP_LOG;
# ATTENTION ! Pour que les commandes de ncftp fonctionnent il ne faut pas les identer !
$NCFTP -u $FTP_USER -p $FTP_PASS $FTP_SERVER << EOF
${CMDRMDIR[*]}
cd $FTP_DIR
$CREAREP
$DELOLD
quit
EOF
# fonction retournant le texte correspondant au code de retour de ncftp
function status {
OSTAT="$1"
case $OSTAT in
0) MESS="Succès";;
1) MESS="Erreur : ne peut se connecter à l'hôte $FTPH";;
2) MESS="Erreur : ne peut se connecter à l'hote $FTPH - temps dépassé";;
3) MESS="Transfert échoué";;
4) MESS="Transfert échoué - temps dépassé";;
5) MESS="Changement de répertoire échoué";;
6) MESS="Changement de répertoire échoué - temps dépassé";;
7) MESS="URL mal formé";;
8 ) MESS="Erreur d'utilisation. Peut être que votre version de ncftpput ($NCFTPPUT) est trop ancienne";;
9) MESS="Erreur dans le fichier de configuration de login";;
10)MESS="Initialisation de la librairie échouée";;
11) MESS="Initialisation de session échouée";;
*) MESS="Erreur inconnue";;
esac
echo "Status : "$MESS;
}
echo "["$DATE_NOW"] - DEBUT des upload sur le FTP" >> $TMP_LOG;
echo "Nombre d'upload a effecuter : "${#PUTCMD[*]} >> $TMP_LOG;
i=0
#for cmd in ${PUTCMD[*]}; do
while [ $i -lt ${#PUTCMD[*]} ]; do
(echo -n "["$DATE_NOW"] - "; ${PUTCMD[$i]}; ) >> $TMP_LOG;
echo " -> "; status "$?" >> $TMP_LOG;
let i++
done
# Suppression de la sauvegarde locale si demande
if [ "$KEEPLOCAL" = 0 ]; then
echo "["$DATE_NOW"] - Suppression des archives locale" >> $TMP_LOG;
for DIR in `find -maxdepth 1 -mindepth 1 -type d`
do
grep -q "$DIR" $EXCLUDEFILE
# pas dans le fichier d'exclusion? OK roule ...
if [ ! $? -eq 0 ]; then
echo "["$DATE_NOW"] - Suppression de : "$BACKUPDIR$(basename $DIR)"/"$ARCHIVE >> $TMP_LOG;
rm $BACKUPDIR$(basename $DIR)"/"$ARCHIVE;
fi
done
fi
#Envoi du log temporaire par mail
if [ "$MAILSEND" = 1 ]; then
mail -s "$SUJETMAIL ("$DATE")" $EMAIL < $TMP_LOG;
echo "["$DATE_NOW"] - Email envoyé" >> $TMP_LOG;
fi
# On finalise le log temporaire, place son contenu dans le log permanent et supprimer le log temporaire
echo "["$DATE_NOW"] - FIN du backup" >> $TMP_LOG;
echo "" >> $TMP_LOG;
cat $TMP_LOG >> $LOG;
rm $TMP_LOG;
rm $TMP_LIST;
exit
Notes à propos du script
Il y a quelques points qui m’ont bloqués quelques temps. Par moments j’ai du trouver une autre méthode quand je voyais qu’il n’y avais pas d’issue possible, dans d’autres j’ai fini par trouver une astuce. Ci-dessous, quelques notes en vrac :
les résultats d’une commande shell dans une boucle :
for DIR in `find -maxdepth 1 -mindepth 1 -type d` do #actions ... done
Vérifier qu’un mot est présent dans un fichier :
grep -q "mon_mot" mon_fichier.txt # pas dans le fichier? if [ ! $? -eq 0 ]; then #actions fi
Retourner juste le nom d’un fichier ou répertoire depuis une url :
DIR="./mon_rep/ echo $(basename $DIR) #retourne : mon_rep
Lister un répertoire ftp dans une boucle for avec ncftpls :
for DIR in `ncftpls -u mon_login -p mon_pass -x "-1" ftp://ftp.mon_ftp.com/mon_rep_distant/` do #actions ... done
Le compteur $i++ tant utilisé, version shell :
i=$i+1 # en ajoutant tout simplement plus 1 ((i++)) # avec des parenthèses let i++ # autre méthode que j'ai choisi pour ce script
Ne pas identer les commandes de ncftp :
Celui là, je suis resté bête en le trouvant. En y repensant, c’est logique puisqu’on on envoie des commandes ncftp et pas des commandes « normales » (même si souvent identiques). Donc, si on veux faire fonctionner une connexion / commande / fin de connexion de ncftp dans un script il ne faut rien identer. On ne peut pas utiliser de boucles à l’intérieur des commandes ncftp entre la connexion et la fermeture (si oui, vous génez pas pour me dire comment, j’ai tout essayé ^^). Pour le faire fonctionner dans une boucle, faire comme cela :
i=0 while [ $i -lt 5 ]; do ncftp -u mon_login -p mon_pass ftp.mon_ftp.com << EOF cd mon_rep #etc. quit EOF let i++ done
Ne pas stocker trop de commandes ncftp dans une variable si on veux qu’elles soient toutes executées :
Celle là, je n’ai pas compris, mais elle m’a pas mal gênée. Au départ, dans ce script, je listais toutes les actions de cd /mon rep; put mon_fichier; dans une variable et je les executaient toutes d’un coup en les appellant après ma connexion ncftp. Avec un ou deux comptes ftp ça allait … mais avec plusieurs je me suis aperçu qu’il y avait une taille limite de caractères et que ma chaine était coupée, donc pas executée en entier (et la dernière commande executée était en erreur car incomplète). Je me suis donc résigné à poster toutes mes commandes dans un array et de les executer en faisant défiler l’array. Vu que c’était des commandes uniques, j’ai opté pour ncftpput qui est plus rapide pour des uploads simples.
Grouper les retours d’une commande pour les combiner une seule fois dans un fichier :
Par exemple, pour ce code :
(echo -n "["$DATE_NOW"] - "; $COMMANDE_NCFTPPUT; ) >> fichier_log.log;
Une seule ligne est enregistrée dans le log :
2009-02-01 19:55 – /home/backup/ftp/mon_archive.tar.gz 4mo 1.8mo/sec
Impossible de faire une recherche dans un répertoire avec ncftp :
Ce n’est pas une affirmation mais c’est ce que j’ai constaté. Apparement, il est impossible de lancer un « find » si on veux par exemple récupérer la liste des fichiers ayant plus de x jours. Je peux me tromper hein ^^ donc si quelqu’un a une solution qu’il ne se gène pas. Faudra que je reteste avec un ls mais je n’y crois pas beaucoup.
Pour les prochaines mises à jour
- Rendre activable le bacup ftp ou non (là j’avais la flemme pour être clair, maintenant que je sais qu’on ne peut pas identer les commandes ncftp ça devrait aller)
- Trouver une meilleur solution pour purger les fichiers trop anciens sur le ftp
Utilisation du script
Pour utiliser ce script de backup c’est très simple, il vous suffit de faire seulement ces 3 choses :
1) Régler vos paramètres dans le script shell et dans le fichier d’exclusion
2) Un chmod +x sur le fichier .sh pour le rendre executable (a personnaliser au nom du fichier)
chmod +x /root/mes_scripts/mon_fichier.sh
3) Ajout d’une tache cron (a personnaliser bien entendu)
crontab -e 00 05 * * * /root/mes_scripts/mon_fichier.sh
Si vous avez des idées pour améliorer ce script, n’hésitez pas à poster des commentaires


Posté par Denis Dee Jay le 2 février 2010 à 2 h 14 min
Pareil que pour l’autre script de backup :
» dans le code sinon
——————————————————
Faire attention, à la ligne contenant :
8 ) MESS= »Erreur d’utilisation.
j’ai mis un espace entre 8 et ) qu’il faudra retirer.
ca me mettait le smiley »