Aujourd'hui on va parler de sauvegardes. Plus précisément, je vais décrire le plan de sauvegarde qui est actuellement en place sur mon serveur : comment il fonctionne, avec quel outillage, et surtout sur quels critères il a été conçu. Ceci, parce que je pense que c'est plus intéressant qu'un article de type tutoriel avec une série de commandes plus ou moins commentées, qui répondent à un besoin spécifique mais rarement explicité, et qui ne sont donc pas toujours évidentes à adapter pour son besoin propre. Ce n'est pas non plus un traité complet sur les sauvegardes, parce que non seulement je n'ai ni les connaissances ni l'expérience pour écrire ça, mais en plus ce serait beaucoup trop de travail vu l'étendue du sujet.

Mise en place d'un plan de sauvegarde

C'est un lieu commun en informatique qu'il faut sauvegarder ses données, mais une fois ce constat établi il n'est pas forcément évident de déterminer ce qu'on veut sauvegarder, où, quand et comment.

Quoi ?

Commençons par déterminer les données qu'on veut mettre en sûreté. Ici on est dans le contexte d'un serveur, qui fournit donc un ensemble de services. Parmi ceux-ci, certains sont assez anecdotiques (agrégateur RSS, seedbox BitTorrent…) et d'autre potentiellement plus critiques (courrier électronique, stockage de fichiers). Pour l'instant, j'ai fait le choix de ne sauvegarder que les services jugés critiques.

Une fois ceci fait, il ne reste qu'à examiner la configuration des services en question pour déterminer quelles sont les données à sauvegarder. Dans mon cas nous avons donc :

  • pour les e-mails, un Maildir par utilisateur (c'est une configuration où les utilisateurs d'e-mail correspondent aux utilisateurs Unix, aux alias près)
  • pour Nextcloud, le répertoire de données, le répertoire de configuration et la base de données (voir la doc.

Où, quand, comment ?

Là ça devient plus difficile de répondre séparément, puisque ces questions sont assez fortement liées. Mais essayons tout de même.

Idéalement, les sauvegardes seraient faites vers un endroit différent de mon serveur : au moins sur une machine différente, si possible sur un site géographique différent. On laisse la question en suspens pour le moment, et on passe au reste.

Idéalement encore, elles auraient lieu à une fréquence relativement élevée : une fois par semaine ou même une fois par jour par exemple. C'est une estimation à la louche en fonction de l'activité typique des services en question : pour les e-mails en principe il y a déjà de la synchronisation (et donc de la réplication) avec IMAP, et pour Nextcloud on peut faire le pari que si un fichier est critique il restera probablement quelque part sur une machine personnelle pendant quelque temps. Une implication d'avoir des sauvegardes fréquentes, c'est qu'on va vouloir que le processus de sauvegarde reste assez léger, car on ne voudrait pas que le serveur passe sa vie faire des sauvegardes.

Idéalement enfin, la procédure serait entièrement automatique. Je considère que les tâches d'administration ne sont pas forcément les plus passionnantes du monde, et il y en a déjà suffisamment (mises à jour, par exemple) sans avoir en plus les sauvegardes à gérer. C'est d'autant plus vrai qu'on souhaite des sauvegardes fréquentes, bien sûr. Avoir une procédure de sauvegarde qui requiert une intervention manuelle c'est s'assurer qu'elle n'aura pas lieu souvent. En gros, on veut quelque chose de scriptable qu'on mettra dans un cron et qu'on s'empressera d'oublier.

Ceci, en retour, permet de répondre à la question du lieu. Dans la mesure où on veut que tout se fasse automatiquement, et de façon régulière et fréquente, il faut forcément que ça se fasse sur un (autre) serveur : on ne peut pas envisager, par exemple, de faire les sauvegardes sur une machine de bureau, quand elle est allumée et qu'on y pense. Problème : je n'ai qu'un serveur, et je n'ai pas vraiment envie d'en payer un second uniquement pour les sauvegardes. Je ne vais pas entrer dans le détail dans ce billet, mais disons qu'on peut s'attendre à ce que contourner ce problème implique d'aller mettre ses sauvegardes dans un environnement que l'on maîtrise moins, voire en lequel on a peu confiance. On va donc demander à nos sauvegardes, en plus de ce qu'on a déjà énuméré, d'être chiffrées.

Une fois tout ceci posé, il ne reste plus qu'à mettre en œuvre.

Application

L'outil principal de ce plan de sauvegardes est Borg. Ce programme permet de faire des sauvegardes chiffrées, et utilise une technique de déduplication pour réduire la taille des sauvegardes, ce qui lui confère la même propriété sympathique que les sauvegardes incrémentales ou différentielles : si la première sauvegarde peut être volumineuse et prendre du temps, ce sera beaucoup moins le cas pour les suivantes. Les autres arguments en sa faveur sont sa documentation claire et complète, ainsi que l'existence d'une version OpenBSD (l'OS de mon serveur). Je reviendrai rapidement sur le choix de l'outil à la fin du billet.

Borg stocke les sauvegardes dans des « dépôts », qui peuvent être locaux ou distants (via ssh) ; la première étape est donc d'en créer un. Dans ce billet je ne parle que de sauvegarde locale, on va donc le mettre dans un coin de notre système de fichier (dans mon cas ce sera /var/borg_backups).

borg init --encryption=repokey /var/borg_backups

L'option --encryption permet de spécifier le mode de chiffrement et d'authentification à utiliser. Ici on choisit l'option repokey, qui spécifie un HMAC en SHA256 pour l'authentification et un chiffrement en AES (qui est en fait le seul algorithme disponible), et qui intègre la clé de chiffrement des données à l'intérieur du dépôt (protégée par une phrase de passe).

Venons en maintenant au script de sauvegarde. Son fonctionnement est extrêmement simple, puisque ce n'est en fait qu'une séquence de commandes à exécuter linéairement. Voici ce qu'il fait :

  1. mettre en pause la réception des e-mails dans les Maildir, ici effectuée par OpenSMTPD
  2. sauvegarder les Maildir de tous les utilisateurs avec la commande borg create
  3. reprendre la réception des e-mails
  4. mettre Nextcloud en mode maintenance
  5. sauvegarder les fichiers et la configuration de Nextcloud
  6. faire un dump de la base de données Nextcloud
  7. le sauvegarder
  8. le supprimer
  9. désactiver le mode maintenance.

Cela donne le script suivant (je ne détaille pas l'utilisation de la commande borg create, la doc explique ça très bien).

#!/bin/sh

export BORG_PASSPHRASE="blablabla"
REPO="/var/borg_backups"
DATE="{now:%Y-%m-%d-%H:%M}"

BORG=/usr/local/bin/borg
PGDUMP=/usr/local/bin/pg_dump
PHP=/usr/local/bin/php

# emails
################################################################################

EMAIL_USERS="foo bar baz"
MAILDIRS=`for u in $EMAIL_USERS ; do echo /home/$u/Maildir ; done`

# pause local email delivery
echo "Pausing local mail delivery."
smtpctl pause mda

# backup email folders
$BORG create --progress $REPO::mail_$DATE $MAILDIRS

# resume local email delivery
echo "Mail backup complete. Resuming local mail delivery."
smtpctl resume mda

# nextcloud
################################################################################

# this script requires a .pgpass file containing the nextcloud user
# password to exist in /root
# see https://www.postgresql.org/docs/current/libpq-pgpass.html
# here we create it temporarily

NC_PATH="/var/www/nextcloud"
NC_DB_PASS="blablabla"
DUMP_DIR="/root/nc_db_dump"

# turn maintenance mode on
echo "Starting Nextcloud backup.\nTurning maintenance mode on."
doas -u www $PHP $NC_PATH/occ maintenance:mode --on

# backup nextcloud config and data
$BORG create --progress $REPO::nextcloud_folders_$DATE $NC_PATH/{config,data}

# backup database in directory format
echo "Dumping nextcloud database."
mkdir -p $DUMP_DIR
touch /root/.pgpass
chmod 0600 /root/.pgpass
echo "localhost:5432:nextcloud:nextcloud:$NC_DB_PASS" > /root/.pgpass
$PG_DUMP -h localhost -U nextcloud -Fd nextcloud -f $DUMP_DIR
rm /root/.pgpass
echo "Dump complete. Saving."
$BORG create --progress $REPO::nextcloud_db_$DATE $DUMP_DIR
rm -rf $DUMP_DIR

# turn maintenance mode off
echo "Nextcloud backup complete.\nTurning maintenance mode off."
doas -u www $PHP /var/www/nextcloud/occ maintenance:mode --off

Remarque pour les linuxiens : doas est l'équivalent OpenBSD de sudo.

Le script contient la phrase de passe du dépôt, que l'on passe à Borg via la variable d'environnement BORG_PASSPHRASE, ainsi que le mot de passe de la base de données de Nextcloud, que l'on passe à pg_dump (l'utilitaire de dump de PostgreSQL, qui est le SGBD de mon serveur) via un fichier. Étant peu habitué à l'exercice, j'ai eu quelques scrupules à placer des mots de passe « en clair » dans un script, mais cette crainte ne résiste pas à un examen rationnel : ce script est uniquement lisible par root. Par conséquent, si un attaquant accède aux mots de passe écrits dedans, c'est qu'il est déjà root sur la machine donc il n'est plus temps de songer à des mesures de protection. À la limite on peut considérer dommageable que cela donne accès aux sauvegardes, mais c'est une condition nécessaire pour pouvoir automatiser.

Pour rester dans les considérations de sécurité, on peut noter que le script s'exécute en root, de même que la plupart de ses commandes. Il y a sans doute moyen de gérer les permissions un peu plus finement, afin que les commandes borg (par exemple) s'exécutent avec moins de privilèges.

Par ailleurs, on peut remarquer que la base de données est dumpée intégralement puis supprimée à chaque exécution du script, ce qui n'est pas très subtil. Il s'agit cependant de la solution la plus simple, et comme il s'agit d'une petite base de données (le dump pèse une dizaine de Mo) ça n'a pas grande importance.

La seule chose qui me gêne un peu avec ce script, c'est l'absence de gestion des erreurs. Les commandes sont enchaînées séquentiellement, chacune s'exécutant sans tenir compte d'un éventuel échec de la précédente, ce qui signifie par exemple que si la mise en maintenance de Nextcloud échoue pour une quelconque raison, on va tout de même effectuer une sauvegarde des données pendant qu'elles continuent à bouger, ce qui est potentiellement embêtant. À mon avis c'est le principal point à améliorer.

Nous avons donc un script qu'il suffit maintenant de lancer à intervalle régulier avec cron. J'ai choisi pour ma part de faire des sauvegardes journalières : cela va s'accumuler rapidement, donc je finirai sans doute par mettre en place une rotation (i.e. garder uniquement les N dernières), mais ce sera pour plus tard.

Pistes pour aller plus loin

Il y a plein de choses que je ne couvre pas dans ce billet, et qui peuvent être intéressantes. Par exemple :

  • comment faire une sauvegarde du système entier, pour pouvoir redéployer son serveur à l'identique en cas de crash du disque dur, par exemple
  • en parlant de disque dur, les technologies de stockage redondant (RAID notamment)
  • analyser en détail les aspects sécurité, en particulier dans le cas de sauvegardes distantes
  • vérifier la bonne restauration (car avoir des sauvegardes c'est bien, mais si le moment venu on s'aperçoit que la restauration ne fonctionne pas c'est dommage…)
  • comment se prémunir contre une corruption de données
  • de façon générale, les principes et les doctrines concernant les sauvegardes
  • les supports de sauvegarde exotiques tels que Freenet, comme ici.

Et bien entendu, les sauvegardes distantes. Mais ce sera sans doute le sujet d'un prochain billet.

Quelques liens sur le sujet

  • un fil reddit qui compare plusieurs outils, dont Borg
  • un des nombreux billets de Genma sur les sauvegardes ; dans celui-ci, il décrit la règle dite « 3-2-1 »
  • le tao des backups, un aperçu des enjeux et considérations à prendre en compte, assez sympathiquement rédigé en forme de conte
  • une page de wiki de sebsauvage sur l'utilisation pratique de Borg