Digital Dynamics
L'informatique innovante et abordable

Dernière mise à jour : lun. 02 nov. 2015, 10:41

Sauvegarder mon site internet : comment faire ? (1/3)

DigiBackup: Websites backup and restore solutionDès la création d'un site internet, il faut penser à une solution de sauvegarde et de restauration rapide des données, généralement contenus dans la base de données, mais aussi des fichiers HTML, PHP, .htaccess et autres qui constituent la colonne vertébrale de votre site. En effet, la totalité de votre travail pourrait être anéantie en l'espace de quelques secondes si une panne de disque dur intervenait sur le serveur, si vous faisiez une erreur de manipulation ou encore si un pirate informatique arrivait à prendre le contrôle de votre serveur et détruisait votre site internet.

Que faut-il sauvegarder et comment procéder ?

Essentiellement, deux catégories d'informations sont à sauvegarder: d'une part la base de données et d'autre part les fichiers de structure (HTML, PHP, css, JavaScript, images...) ainsi que les fichiers des utilisateurs (essentiellement des photos).

Nous allons donc élaborer une méthode globale permettant d'aborder les différents aspects de la sauvegarde en minimisant les inconvénients et en optimisant les phases de sauvegarde et de restauration des données.

La problématique de la sauvegarde des fichiers

Pour sauvegarder les fichiers, la solution la plus simple serait de créer une archive de type ZIP ou TAR.GZ, contenant le dossier racine du site ainsi que ses fichiers et sous-répertoires. Cette solution aurait l'avantage d'être simple et de ne rien oublier.

Le problème principal interviendrait en cas de restauration: il faudrait uploader cette archive et la dézipper, puis attribuer les droits corrects de chaque fichier et chaque répertoire. En effet, l'attribution d'un fichier ou répertoire à un mauvais utilisateur pourrait permettre une éventuelle attaque de votre site, de même que des droits trop restrictifs pourraient perturber le fonctionnement du site et que des droits trop laxistes pourraient également permettre un hack de votre site.

Cette solution est donc faisable mais extrêmement fastidieuse si vous disposez d'un site relativement complexe.

La solution idéale devrait donc préserver les droits d'exécution et d'utilisateur de chaque fichier afin de rendre la restauration très sûre et très rapide.

Sauvegarder les fichiers dans une base de données et enregistrer les droits

Pour parer à ce problème de droits, il suffit de convertir chaque fichier sous sa forme binaire, de récupérer ses droits et son emplacement, puis d'enregistrer le tout dans une base de données.

De cette façon, il suffira de faire un dump de la base pour avoir une image complète et fidèle de votre site.

Connexion à la base de données et création d'une table spécifique qui contiendra les fichiers et répertoires du site.

<?php
/*******************************************************************
Classe permettant la connexion propre à MySQLi
********************************************************************/
class connexion extends mysqli {
    public function __construct($host, $user, $pass, $db) {
        parent::__construct($host, $user, $pass, $db);

        if (mysqli_connect_error()) {
            die('Erreur de connexion (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
        }
    }
}

/******************************************************************
Supprime la table de l'ancienne sauvegarde et la re-crée (une table par site)
*******************************************************************/
function createTable($site,$connectVars){ 
	$db = new connexion($connectVars['host'], $connectVars['user'], $connectVars['password'], $connectVars['db']);
	$db->set_charset("utf8");
	$txt1 = "DROP TABLE IF EXISTS `backup_".$site."`;";
	$txt2 = "CREATE TABLE IF NOT EXISTS `backup_".$site."` (
	  `id` int(11) NOT NULL AUTO_INCREMENT,
	  `type` varchar(10) COLLATE utf8_unicode_ci NOT NULL,
	  `path` varchar(256) COLLATE utf8_unicode_ci NOT NULL,
	  `content` longblob NOT NULL,
	  `permissions` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
	  `owner_id` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
	  `owner_name` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
	  `group_id` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
	  `group_name` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
	  `name` varchar(256) COLLATE utf8_unicode_ci NOT NULL,
	  PRIMARY KEY (`id`)
	) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;";
	$db->query($txt1);
	$query = $db->query($txt2);
	$db->close();
	if($query) return(true); else return(false);
	}
?>

Fonction qui explore les répertoires et enregistre les fichiers dans la base de données

<?php
/******************************************************************
Explore et enregistre chaque répertoire et sous-répertoire
Convertit chaque fichier en binaire et l'enregistre dans la BDD
*******************************************************************/
function recur_dir($dir,$exclude=array(),$site,$baseDir,$db) { 
	$dirlist = opendir($dir);
	while ($file = readdir ($dirlist)){ // explore le répertoire
		if (!in_array($file,array('.','..','.DS_Store'))){ // Vérifie que le fichier doit bien être sauvegardé
			$newpath = $dir.'/'.$file;
			$permissions = substr(decoct(fileperms($newpath)),-4);
			$ownerIud = fileowner($newpath);
			$ownerName = posix_getpwuid($ownerIud);
			$groupIud = filegroup ($newpath);
			$groupInfo = posix_getgrgid($groupIud);
			$groupName = $groupInfo['name'];
			if (is_dir($newpath)){ // Si le fichier est un répertoire, enregistre dans la BDD et explore les sous-répertoires
				$txt = "INSERT INTO `".$site."` (id,type,path,permissions,owner_id,owner_name,group_id,group_name,name,content) VALUES (0,'DIR','".str_replace($baseDir.'/','',$newpath)."','".$permissions."','".$ownerIud."','".$ownerName['name']."','".$groupIud."','".$groupName."','".$file."','')";
				$db->query($txt);
				if(!in_array(str_replace($baseDir.'/','',$dir.'/'.$file),$exclude)) recur_dir($newpath,$exclude,$site,$baseDir,$db);
				}
			else { // Convertit les fichiers en binaire et les enregistre dans la base de données
				$FileContent = $db->real_escape_string(bin2hex(file_get_contents($newpath)));
				$txt = "INSERT INTO `".$site."` (id,type,path,permissions,owner_id,owner_name,group_id,group_name,name,content) VALUES (0,'FILE','".str_replace($baseDir.'/','',$newpath)."','".$permissions."','".$ownerIud."','".$ownerName['name']."','".$groupIud."','".$groupName."','".$file."',x'".$FileContent."')";
				$db->query($txt);
				}
			}
		}
	closedir($dirlist); 
	}
?>

Fonctions qui vous serviront au reporting

<?php
/******************************************************************
Envoie un mail de reporting au format UTF-8
*******************************************************************/
function envoyer($objet,$content){
	global $mailVars;
	$to = $mailVars['to'];			
	$headers = 'From: '.$mailVars['fromName'].' <'.$mailVars['fromAddr'].'>'."\n";
	$headers .= 'Reply-to: '.$to."\n";
	$headers .= 'Return-Path: '.$to."\n";
	$headers .= 'Message-ID: <'.time().'.'.$to.'>'."\n";
	$headers .= 'X-Mailer: PHP v'.phpversion()."\n";
	$headers .= 'X-Sender: '.$to."\n";
	$headers .= 'X-auth-smtp-user: '.$to."\n";
	$headers .= 'X-abuse-contact: '.$mailVars['AbuseAddr']."\n";
	$headers .= 'MIME-version: 1.0'."\n";
	$headers .= 'Content-Type: text/plain; charset=UTF-8'."\n";
	$headers .= 'Content-Transfer-Encoding: 8bit'."\n"."\n";
	$subject = '=?UTF-8?q?'.str_replace('%', '=', rawurlencode($objet)).'?=';
	$body = $content."\n";
	mail($to, $subject, $body, $headers);
	}

/******************************************************************
Retourne la durée en H:m:s entre 2 microtime
*******************************************************************/
function duree($debut,$fin){
	$difference = round($fin - $debut,2);
	$heures = floor($difference/3600);
	$minutes = floor(($difference - ($heures * 3600)) /60);
	$secondes = $difference -(($minutes*60) + ($heures * 3600));
	$duree = ($heures>0?$heures.' heure'.($heures>1?'s ':' '):' ').($minutes>0?$minutes.' min. ':'').$secondes.' secondes';
	return $duree;
	}
?>

Mise en oeuvre du script

<?php
include( __DIR__ .'/functions.php');
include( __DIR__ .'/config.php'); // Fichier à renseigner avec vos paramètres
/**************************************************************************** 
Copie des fichiers dans la base
*****************************************************************************/
$content = 'Début du backup'."\n\n";
foreach($configuration as $site => $v)	
	{
	$debInter = microtime(true);
	if(false !== createTable($site,$v['dbConnect'])) 
		{
		$db = new connexion($v['dbConnect']['host'], $v['dbConnect']['user'], $v['dbConnect']['password'], $v['dbConnect']['db']);
		$db->set_charset("utf8");
		$content .= 'Table backup_'.$site.' créée. Le backup de ce site peut commencer.'."\n";
		recur_dir($v['siteRoot'],$v['ignore'],'backup_'.$site,$v['siteRoot'],$db);
		$finInter = microtime(true);
		$content .= 'Mise à jour de la table backup_'.$site.' terminée en '.duree($debInter,$finInter)."\n\n";
		$db->close();
		}
	else $content .= 'Impossible de créer la table backup_'.$site.'. Le backup de ce site est abandonné.'."\n\n";
	}
/****************************************************************************
Fin du script - génération et envoi du rapport
*****************************************************************************/
$fin = microtime(true);
$content .= "Script terminé - Temps d'exécution : ".duree($debut,$fin)."\n";
$subject = 'Backup de mes sites';
envoyer($subject,$content);
?>

Sauvegarde de la base de données

Nous allons maintenant aborder la partie concernant la sauvegarde de la base de données (qui comprend désormais également une table contenant les fichiers de votre site sauvegardés). Cette étape étant relativement longue à détailler, je vous donne rendez-vous dans la seconde partie.

Page 1 2 3

L'informatique facile et expliquée