Et voila un petit script python pour faire de la sauvegarde incrémentale avec rsync. Il est pas fini pour le moment, mais comme je suis un peu débordé je le mets en attendant de le finir. Il n'est pas compliqué, donc si vous avez des remarques sur le script ou des idées.
Le but de ce script est de permettre l'automatisation d'un backup incrémental configurable s'adressant à différents serveurs par ssh, NFS ou directement sur le port d'écoute du démon rsync.
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# =============================================================================
#
# File .....: bckmg.py - Gestionnaires de backup
# Date .....: 22/09/2007
# Author....: Franck Joncourt <franck
# !rtsp Dot Joncourt
# ..*q.. AT Wanadoo Dot Fr>
#
# Le script est largement inspiré de celui de Leland Elie (roller.py) et du travail fait
# sur http://www.mikerubel.org/computers/rsync_snapshots/
#
# -------------------------------
#
# 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 2 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, write to :
#
# the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor,
# Boston, MA 02110-1301 USA
#
# -------------------------------
#
# =============================================================================
# Importation des librairies
import os
import time
import sys
# Déclaration des constantes
NAME = "bckmg"
DESC = "Mise à jour de la sauvegarde des modules via rsync."
VERSION = "0.1"
PIDFILE = "/var/run/" + NAME + ".pid"
# Déclaration des variables globales
Servers = []
OptionsList = ["name", "comment", "enable", "path",
"address", "module", "days", "hours"]
# ----------------------------------------------------------------------------
# Parser le fichier de configuration
#
# Cette fonction permet de lire le fichier de configuration afin
# d'obtenir les informations utile au backup
def ParseConfigFile():
print "Chargement de la configuration à partir du fichier ./config\n"
CurrentServer = {}
# Ouvrir le fichier
try:
fh = open("./config", 'r')
data = fh.readlines()
fh.close();
except :
print ("ERROR: Unable to access config file")
sys.exit()
# Parcourir le fichier de configuration
for line in data:
line = line.strip()
# Obtenir le nom du serveur et sauvegarder la configuration précédente
if line.find('[') == 0:
# Sauvegarder la configuration précédente si elle existe
if len(CurrentServer) > 0:
Servers.append(CurrentServer)
CurrentServer = {}
# Obtenir le nom du server pour la nouvelle configuration
if len(line) > 2:
CurrentServer['name'] = line.replace('[', '').replace(']', '')
# Obtenir les informations du serveur, pas les commentaires
elif line.find('#') != 0:
if len(line) > 2:
key = line.split('=')
if len(key) == 2:
CurrentServer[key[0].strip().lower()] = key[1].strip().lower()
# Sauvegarder la dernière configuration obtenue
if len(CurrentServer) != 0:
Servers.append(CurrentServer)
# ----------------------------------------------------------------------------
# Vérifier la configuration obtenue
#
# Cette fonction permet de vérifier la configuration obtenue en parsant le
# fichier de configuration de l'application
#
# \return 0 si la configuration serveur est valide
# -1 si une option est manquante
# -2 si la configuration n'est pas autorisée
def CheckConfig(server):
# Vérifier que toutes les options sont présentes
for option in OptionsList:
if server.has_key(option) == 0:
return -1
# Vérifier que le backup est autorisé
if server['enable'].find('yes') == 0:
print "* Configuration pour %s validée" % server['name']
return 0
else:
return -2
# ----------------------------------------------------------------------------
# Effectuer le traitement des anciens backups
#
# Cette fonction se déplace dans le répertoire de backup et le crée si
# nécessaire. Esnuite, il vérifie que le backup peut-être effectué en fonction
# du délais autorisé entre les backups. De la même manière, le plus ancien
# sera supprimé si nécessaire. Pour finir, une copie du plus récent backup
# sera faîte afin de recevoir la nouvelle sauvegarde incrémentale.
#
# - server.: nom du serveur
# - path ..: le chemin de sauvegarde pour le serveur
# - module : nom du module à sauvegarder
# - days...: nombre de jours maximum de sauvegarde
# - hours..: nombre d'heures minimum entre deux backups
#
# \return 0 si l'opération a été effectuée avec succès
# -1 si une erreur s'est produite
def ProcessLastBackups(server, path, module, days, hours):
# Se placer dans le répertoire racine associé au serveur
try:
os.chdir(path)
except OSError:
print " [ERROR] Impossible d'accéder au répertoire racine %s " % path,
print "associé au server %s." % server
return -1
# Se placer dans le répertoire racine du module
try:
os.chdir(module)
except OSError:
try:
os.mkdir(module)
except OSError:
print " [ERROR] Impossible de créer le répertoire racine",
print "%s/%s associé au module %s" % (path, module, module),
return -1
try:
os.chdir(module)
except OSError:
print " [ERROR] Impossible d'accéder au répertoire racine",
print "%s/%s associé au module %s" % (path, module, module),
return -1
# Obtenir la liste des précédentes sauvegardes
backupList = os.listdir('./')
for directory in backupList:
if os.path.isdir(directory) == False:
backupList.remove(directory)
backupList.sort(reverse=True)
# En déduire la plus ancienne et la plus récente sauvegarde
# et effectuer le traitement nécessaire
if len(backupList) > 0:
lastBackup = int(time.mktime(time.strptime("%s" % backupList[0],
"%Y%m%d%H")))
thisBackup = int(time.time())
# Vérifier que le backup respecte le délais minimum
calcul = (thisBackup - lastBackup) / 3600.0
if calcul <= 0:
print " [ERROR] Sauvegarde dans le futur détectée"
return -1
elif int(calcul) < int(hours):
print " [ERROR] Dernière sauvegarde trop récente"
return -1
# Supprimer les plus anciennes archives
for directory in backupList:
lastBackup = int(time.mktime(time.strptime("%s" % directory,
"%Y%m%d%H")))
calcul = (thisBackup - lastBackup) / (3600.0 * 24.0)
if calcul >= int(days):
print " Suppression de l'archive %s" % directory
pipe = os.popen("rm -Rf %s" % directory)
pipe.close()
sys.stdout.flush()
# Faire une copie du plus récent backup pour recevoir la nouvelle
# sauvegarde incrémentale
directory = time.strftime('%Y%m%d%H')
cde = "cp -al %s %s" % (backupList[0], directory)
pipe = os.popen(cde)
pipe.close()
sys.stdout.flush()
return 1
return 0
# ----------------------------------------------------------------------------
# Effectuer le backup d'un module
#
# Cette fonction permet de faire le backup d'un module pour un serveur donné
# via rsync. Pour ce faire il est nécessaire de fournir les différents
# arguments :
#
# - serverName : nom ou adresse du serveur auquel se connecter
# - module ....: nom du module à sauvegarder
# - path ......: chemin où sauvegarder le module sur le serveur local
#
# \return 0 si le backup c'est bien déroulé
# <0 si une erreur est survenue
def DoBackup(serverName, module, path, days, hours):
print "|-> " + time.strftime("%a, %d %b %Y %I:%M:%S %p"),
print " : Sauvegarde du module %s ..." % module
# Gérer les anciens backups
directory = time.strftime('%Y%m%d%H')
retval = ProcessLastBackups(serverName, path, module, days, hours)
if retval < 0:
return retval
elif retval == 0:
os.mkdir(directory)
# Faire la sauvegarde incrémentale
print " Création de l'archive %s" % directory
cde = "rsync -vrlptgoW --numeric-ids --delete "
cde = cde + "%s::%s " % (serverName, module)
cde = cde + "%s/%s/%s" % (path, module, directory)
sys.stdout.flush()
pipe = os.popen(cde)
data = pipe.readlines()
pipe.close()
sys.stdout.flush()
print " Done"
# ----------------------------------------------------------------------------
# Gérer le verrou de l'application
#
# Cette fonction permet de positionner/enlever le verrou de l'application afin
# d'éviter que plusieurs instances s'éxécute en même temps.
#
# - mode ..: 1 pour créer le verrou
# 0 pour l'enlever
# - verrou : nom du verrou
#
# \return 0 si l'opération a été effectuée avec succès
# -1 si une erreur s'est produite
def SetVerrou(mode, verrou):
# Créer le verrou
if mode == 1:
# Afficher une erreur si un verrou existe
try:
fh = open(verrou, 'r')
fh.close()
print "[ERROR] Le verrou %s a été trouvé signifiant qu'une autre" % verrou,
print "instance est en cours ou que la précédente éxécution s'est mal",
print "terminée."
return -1
# Autrement créer le verrou avec la date courante
except IOError:
fh = open(verrou, 'w')
fh.write(time.strftime("%a, %d %b %Y %I:%M:%S %p\n"))
fh.close()
return 0
# Supprimer le verrou
else:
# Supprimer le verrou si il existe
try:
fh = open(verrou, 'r')
fh.close()
pipe = os.popen("rm -f %s" % verrou)
pipe.close()
return 0
# Afficher une erreur si le verrou n'existe pas
except IOError:
print "[ERROR] Impossible de supprimer le verrou %s !"
return -1
# ----------------------------------------------------------------------------
# Quitter l'application
#
# Cette fonction permet de quitter l'application
#
# - errorCode : valeur du code d'erreur à retourner
def Quit(errorCode):
date = "le " + time.strftime("%a, %d %b %Y %I:%M:%S %p") + " <--"
if errorCode < 0:
print "--> Exécution annulée %s" % date
else:
print "--> Fin d'exécution %s" % date
sys.exit(0)
# ----------------------------------------------------------------------------
# Afficher la bannière
#
# Cette fonction a pour seule utilité d'afficher la bannière de l'application
def ShowBanner():
print "%s release %s" % (NAME, VERSION)
print "%s\n" % DESC
# ----------------------------------------------------------------------------
# Boucle principale du programme
#
# Afficher la bannière
ShowBanner()
# Parser le fichier de configuration
ParseConfigFile()
# Mettre en place le verrou
retval = SetVerrou(1, PIDFILE)
if retval < 0:
Quit(retval)
# Parcourir toutes les configurations serveur
for server in Servers:
# Vérifier la configuration pour la backup
retval = CheckConfig(server)
if retval < 0:
continue
# Effectuer le backup pour chaque module du serveur
module = server['module'].split(' ')
for mod in module:
DoBackup(server['name'], mod, server['path'], server['days'],
server['hours'])
print ""
# Supprimer le verrou
retval = SetVerrou(0, PIDFILE)
if retval < 0:
Quit(retval)
# Terminer l'application
Quit(0)
[diamond.stones.lan] comment = Sauvegarde de Produit enable = yes path = /mnt/backup address = diamond.stones.lan module = produit days = 30 hours = 24 [emerald.stones.lan] comment = Sauvegarde des base de données enable = yes; path = /mnt/backup address = emerald.stones.lan module = mysql days = 5 hours = 6