PCsoleil Informatique

Entreprise Individuelle
Services informatiques Brignoles et Centre-VAR | Professionnels et particuliers

Je suis dans les locaux d’une petite société, 4 ordinateurs sous Windows 10 ou 11 accèdent à un partage réseau placé sur un Serveur Windows 2012 dans son dossier Documents. Ce serveur ne joue que le rôle de partage fichier (oui c’est un peut sous-dimensionné comme utilisation). Un backup quotidien automatique est exécuté le soir via le logiciel Cobian backup sur le serveur, ce dernier plaçant les précieuses données dans un NAS Buffalo dont l’accès est viabilisé par l’activation de Samba (partage fichier réseau Windows).

J’ai pas mal hésité sur le logiciel de sauvegarde à utiliser: Macrium Reflect server avait ma priorité mais son tarif est rédhibitoire sur un serveur Windows 2012, Duplicacy m’a attiré mais son mode Beta ne me rassure guère, Kopia est excellent mais ne dispose pas nativement de la copie d’éléments en cours d’utilisation (Volum Shadow copy).

Borg.. ne supporte pas en natif les services Cloud de sauvegarde comme Backblaze, Wasabi et autres services spécialisées dans le Object storage. D’autres logiciels ou scripts furent éliminés de mon attention de fait; à cause de l’utilisation d’une base de donnée: Ce n’est pas une tare en soit mais pour la robustesse du backup je voulais un process direct; La BDD apparaît alors à mes yeux comme une surcouche sujette aux corruptions.

Mon choix définitif se porte alors sur Restic; Cet outil en ligne de commande mature (sortie en 2015 mais constamment amélioré) coche toutes les cases de mes besoins:

  • Backup incrémental bien sûr
  • Déduplication: Si les dossiers sauvegardés contiennent plusieurs copies d’un fichier, même si ces copies ont un nom différent, un seul fichier sera sauvegardé avec ses métadonnées
  • La copie de fichiers utilisés est parfaitement intégrée, Restic créé un cliché instantané si l’option idoine est renseignée
  • L’interfaçage avec les services cloud backup est très bonne -et si cela ne suffit pas- Restic est programmé pour ainsi dire constituer un organisme symbiotique avec Rclone en quelques lignes de commande simples (Rclone qui bat tous les records en matière de supports de stockage)
  • Et en vrac: Chiffrage des backups, politique de nettoyage des anciennes sauvegardes simple, travail rapide malgré le chiffrage, la compression et le décorticage des fichiers en données blob
 

Malheureusement le montage des backups dans un lecteur virtuel n’est pas possible sous Windows (à moins d’installer WLS for Windows de Microsoft). PS: Restic est tellement apprécié qu’un développeur a conçu un logiciel cross-platform donc Windows pour explorer le contenu des bakups; Restic Browser.

Mon système de sauvegarde ne sera pas trivial: Je vais intégrer une alerte par Email en cas d’erreur de sauvegarde, un heartbeat avec Healthchecks.io, un double backup: Un sur le second disque interne du serveur, l’autre vers le service en ligne Wasabi, un nettoyage des anciens backup… Donc un script sera nécessaire, sous Python car la qualité de ces modules m’autorise à envoyer des email, des pings etc.. beaucoup plus facilement et débarrassé des restrictions de sécurité draconnienne de PowerShell.

Installer Restic

Installer est un grand mot car Restic se présente comme un outil en ligne de commande, je place l’exécutable dans C:\Restic.

Par commodité je fais en sorte de pouvoir appeler Restic depuis une ligne de commande lancée à partir de n’importe quel dossier: Sous Windows server 2012 > Paramètres > Panneau de configuration > Système et sécurité > Paramètres système avancé > Bouton Variables d’environnement. Dans la fenêtre Variables système, clic sur la variable Path > Modifier et je rajoute à la suite des valeurs déjà présente  » ;C:\Restic  » sans les guillemets.

Initier le repository Restic et effectuer la 1ere sauvegarde

Le dossier à sauvegarder est sur C:\users\SERVER\Documents, et le repo restic local sur D:\Backuprestic donc je lance en ligne de commande:

				
					restic -r D:\Backuprestic init
				
			

Apres renseignement du mot de passe de crytage, je lance pour de bon la sauvegarde originelle. Si le dossier à sauvegarder comportait des fichiers en cours d’utilisation j’aurais complété le script avec –use-fs-snapshot en dernières lettres.

				
					restic -r D:\Backuprestic backup C:\Users\SERVER\Documents
				
			

Voilà pour le duplicat en local. Je passe au backup Wasabi: Initiation et upload

				
					set AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXXXXXXXXX
set AWS_SECRET_ACCESS_KEY=BlablaBlaBLAblA
restic -r s3:https://s3.eu-west-2.wasabisys.com/NOM_BUCKET init
# Choix du password du repository Restic, par commodité
# Je renseigne le même que celui en local
restic -r s3:https://s3.eu-west-2.wasabisys.com/NOM_BUCKET backup C:\Users\SERVER\Documents

				
			

HealthChecks.io

Je créé un « Check » sur le service en ligne healthchecks.io et prend note de l’URL à pinger de forme https://hc-ping.com/xxx-xxx-xxx-xx. Je règle le cron sur un jour que ce soit en rubrique « Period » et en section « Grace Time »; Le serveur Windows n’est jamais arrêté même le Week end. En menu Integrations je renseigne mon adresse mail qui servira au service pour m’alerter en cas de période de grace échue. Comment fonctionne HealthCheck.

Wasabi

Là aussi une configuration très simple: Création d’un Bucket en prenant garde de ne pas activer le versionning, et c’est tout.

Script Python

Un script assez simple mais composé de quelques subtilités pour lui conférer une certaines robustesse et compacité; Son but est d’envoyer les sauvegardes sur le second disque dur interne et dans un bucket Wasabi, supprimer les anciennes version, envoyer un email en cas de problème avec en corps de message le problème rencontré (EX: disque externe corrompu, bucket Wasabi codes changés etc..), et stimuler régulièrement HealthCheck d’un ping, pour être certain que le script de sauvegarde se lance bien (principe du Death’s man switch). Palcé dans C:\Restic\ au doux nom de resticserver.py

J’appel dans le script un fichier common.py contenant les nombreuses variables utiles au processus: adresse du serveur mail, port, clef wasabi et sésame du repository Restic entre autres.

La fonction ping_with_retry prend garde à ce que le ping d’échec ou de succès de backup vers Healthchecks soit bien envoyé quitte à réessayer en cas de problème réseau ou de micro-coupures.

Plus bas nous verrons que le script Python est lancé en mode invisible avec l’exécutable Windows pythonw au lieu de python. Malgré cela, mes premiers essais provoquaient l’affichage bref de fenêtres de commande; La cause en était que l’addon subprocess de python, exécuté dans la fonction run_restic, initie une commande line windows pour lancer Restic (Restic ne se lance pas nativement sous Python, même s’il semble exister des Wrappers en rapport je préfère ne pas les utiliser n’étant pas assurré de leurs supports à l’avenir. Une astuce dans les paramètres de subprocess.run me soulage de ce léger inconvénient sous la forme du code creationflags=subprocess.CREATE_NO_WINDOW.

Dans mes tests, j’ai constaté des cas de verrouillage du repository Restic, ce qui interdit toute modification de son état et bloque donc les backups. Les sécurités mises en place m’alertent parfaitement sur cet état de fait mais encore faut-il pouvoir rendre pérenne la solution, c’est pourquoi les commandes run_restic aussi bien en ce qui concerne le duplicat wasabi que celui du dique externe commencent par un Unlock des Repo, au cas où ce verrouillage impromptu serait effectif.

Je n’oublie pas la confection automatique d’un log à chaque opération, ce log contient la sortie textuelle des opérations, succès et erreurs de Restic, il est préfixé par un horodatage précis et atteri dans C:\Restic\logs\.

Voici plus bas le contenu du script principal, enregistré dans C:\Restic. Il faudra sans doute lancer quelques installations d’addons Python via la commande pip install -m. Le fichier d’information common.py n’est qu’un dictionnaire au sens de Python sera détaillé plus bas encore.

Dictionnaire Python common.py

				
					import subprocess
import requests
import os
import smtplib
import time
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import datetime, timedelta
from common import configbackup

# le fichier log se place dans un sous dossier
# Il a une convention de nommage par horodatage très précis pour éviter l'écrasement
date_str = datetime.now().strftime("%d-%m-%Y---%H-%M-%S")
log_file = f"C:\\Restic\\logs\\{date_str}-restic-server.log"

# Ici les tentatives de ping vers Healthcheks sont robustes car répétées si erreur réseau,
# à intervalles éloignées de 10 secondes
def ping_with_retries(url, retries=5, timeout=10):
    for _ in range(retries):
        try:
            response = requests.get(url, timeout=timeout)
            if response.status_code == 200:
                return True
        except requests.RequestException:
            pass
        time.sleep(5)
    return False
    
# Si Restic rencontre une erreur un mail d'alerte notifie le responsable
def send_email(subject, body):
    message = MIMEMultipart()
    message["From"] = configbackup["email_from"]
    message["To"] = configbackup["email_to"]
    message["Subject"] = subject
    message.attach(MIMEText(body, "plain"))
    with smtplib.SMTP_SSL(configbackup["smtp_server"], configbackup["smtp_port"]) as server:
        server.login(configbackup["smtp_user"], configbackup["smtp_password"])
        server.sendmail(configbackup["email_from"], configbackup["email_to"], message.as_string())
        
# Fonction un peu délicate, chargée des variables lues
# Depuis les occurences de run_restic plus bas,
# Elle mêmes récupérées pour la plupart dans le fichier common.py
# Encore enrichie des infos de connexion Restic et Wasabi
def run_restic(operation_description, command, success_url, failed_url, repository, log_file):
    env_vars = os.environ.copy()
    env_vars["AWS_ACCESS_KEY_ID"] = configbackup["wasabi_access_key"]
    env_vars["AWS_SECRET_ACCESS_KEY"] = configbackup["wasabi_secret_key"]
    env_vars["RESTIC_REPOSITORY"] = repository
    env_vars["RESTIC_PASSWORD"] = configbackup["restic_password_repository"]    
    
    try:
    # Restic ne s'exécute pas nativement sous Python; Je dois lancer
    # Grace à subprocess un commande Windows pour l'exécuter
        result = subprocess.run(command, capture_output=True, text=True, check=True, env=env_vars, creationflags=subprocess.CREATE_NO_WINDOW)
        with open(log_file, "a") as f:
            f.write(result.stdout)
        ping_with_retries(success_url)  
    # Si l'opération précédente rencontre une erreur,
    # Un fail ping est poussé vers Healthchecks.io
    # En plus d'un mail d'alerte
    except subprocess.CalledProcessError as e:
        error_output = e.stderr if e.stderr else "Erreur inconnue."
        with open(log_file, "a") as f:
            f.write(error_output)
        send_email(f"Erreur de Restic {operation_description} sur dépôt {repository}",
                   f"Une erreur s'est produite lors de l'exécution de Restic ({operation_description}) concernant le dépôt {repository} \n: {error_output}")
        ping_with_retries(failed_url) 
        
# Unlock du dépôts Restic (on ne sait jamais)        
run_restic("Unlock backup local", ["restic", "unlock"], configbackup["hc_ping_url_success"], configbackup["hc_ping_url_failed"], configbackup["restic_repository_local"], log_file)
        
run_restic("backup local", ["restic", "backup", configbackup["restic_source_local"], "--no-scan"], configbackup["hc_ping_url_success"], configbackup["hc_ping_url_failed"], configbackup["restic_repository_local"], log_file)


# Exécuter forget puis prune
run_restic("forget local", ["restic", "forget", "--keep-daily", "90", "--keep-weekly", "25", "--keep-monthly", "24", "--keep-yearly", "2" ], configbackup["hc_ping_url_success"], configbackup["hc_ping_url_failed"], configbackup["restic_repository_local"], log_file)

run_restic("prune local", ["restic", "prune"], configbackup["hc_ping_url_success"], configbackup["hc_ping_url_failed"], configbackup["restic_repository_local"], log_file)



run_restic("Unlock Bucket Wasabi", ["restic", "unlock"], configbackup["hc_ping_url_success"], configbackup["hc_ping_url_failed"], configbackup["restic_repository_wasabi"], log_file)

run_restic("backup Wasabi", ["restic", "backup", configbackup["restic_source_local"], "--no-scan"], configbackup["hc_ping_url_success"], configbackup["hc_ping_url_failed"], configbackup["restic_repository_wasabi"], log_file)

run_restic("forget Wasaby", ["restic", "forget", "--keep-daily", "90", "--keep-weekly", "25", "--keep-monthly", "24", "--keep-yearly", "2" ], configbackup["hc_ping_url_success"], configbackup["hc_ping_url_failed"], configbackup["restic_repository_wasabi"], log_file)

run_restic("prune Wasabi", ["restic", "prune"], configbackup["hc_ping_url_success"], configbackup["hc_ping_url_failed"], configbackup["restic_repository_wasabi"], log_file)
				
			

J’aime bien cette idée de découplage entre le script principal exécutant des commandes et un fichier contenant les informations nécessaires de connexion, stockage, protocoles etc.. Cela rend le script facilement adaptable en cas de nouvel environnement. Voici le contenu anonymisé de common.py situé lui aussi dans C:\Restic.

				
					configbackup = {
    "smtp_server": "smtp.xxx.xx",
    "smtp_port": "normalement 465 à voir selon le fournisseur", 
    "smtp_user": "adresse mail de la boite mail",
    "smtp_password": "mot de passe de la boite mail",
    "email_from": "adresse mail de l'expéditeur",
    "email_to": "adresse mail du récipiendaire",
    "restic_repository_local": "D:\\backuprestic",
    "restic_source_local": "C:\\Users\\SERVER\\Documents",
    "restic_password_repository": "Mot de passe repository Restic",
    "restic_repository_wasabi": "s3:https://s3.eu-west-2.wasabisys.com/NOM_DU_BUCKET",
    "wasabi_access_key": "XXXXXXXXXXXXXXXXbla?",
    "wasabi_secret_key": "blablablablableuh",
    "hc_ping_url_success": "https://hc-ping.com/blablabliblou",
    "hc_ping_url_failed": "https://hc-ping.com/bla-blabla-blabla/fail",
}

				
			

Tâche planifiée

Rien de plus simple sous Windows de constituer une exécution automatique selon des réglages précis, en évitant toutefois le piège de n’autoriser la tâche que si l’utilisateur est connecté dans l’onglet Général (Les sessions utilisateurs sur serveur Windows se doivent d’être fermées chez les gens honnêtes). La tâche est quotidienne, l’onglet Actions respecte les conditions suivantes:

  • Action: Démarrer un programme
  • Programme/Script: C:\Programmes\Python311\pythonw.exe
  • Ajouter des arguments: resticserver.py
  • Démarrer dans C:\Restic
 
Autres conditions au niveau de l’onglet Paramètres:
  • Authoriser l’exécution de cette tâche à la demande (pratique pour tester)
  • Arrêter si la tâche s’exécute plus de 1 jours (Une première sauvegarde manuelle ayant été effectuée préalablement dans les repository sus-citées, si la sauvegarde incrémentale, dédoublonnée, compressée prend plus de 1 jours sur un serveur digne de ce nom.. il y a un problème)

Conseils

Ne suivez pas à la lettre les textes de mon article: Celui-ci est technique et susceptible de contenir quelques coquilles; une virgule dans le script est vite oubliée.

Je n’imagine pas que Restic soit vulnérable aux ransomware: En effet le nombre de versions de backups possible est si important que, même si à l’instant T le repository contient les données crytptées la veille par une de ces bestioles informatiques, le jour T -1 vos données seront à l’état original, mais tant il est vrai que cela ne résoud pas la perte potentielle de travail de l’instant T, opter pour un système de backup moins performant que Restic expose à se retrouver avec une moindre fréquence et historique de révisions.

Si, en plus de changer votre mot de passe de boite mail vous supprimez ou négligez l’@ mail de notification sur laquelle Healthchecks envoi ces alertes.. Ben ça devient compliqué de trouver un process fiable.

Besoin d'un devis informatique ?
Parlons-en !

Formulaire devis 2
Téléphone Demande complexe ou difficile à décrire ? Appelez au 06.28.07.77.83

Diagnostic de 35€ offert si devis accepté

Demande de devis