Skip to content

Backup

Le rôle Backup installe et configure Restic afin de sauvegarder automatiquement les données persistantes du VPS.

Il est responsable de :

  • l’installation de Restic ;
  • l’initialisation du dépôt de sauvegarde ;
  • l’exécution de commandes préparatoires, comme un dump PostgreSQL ;
  • l’arrêt temporaire des conteneurs qui nécessitent une sauvegarde cohérente ;
  • la création des snapshots et l’application de la politique de rétention ;
  • la réplication optionnelle des snapshots vers une Hetzner Storage Box ;
  • la planification quotidienne avec un timer systemd ;
  • l’installation des commandes resticctl et resticctl-hetzner pour administrer les dépôts.

À chaque exécution, le rôle génère un script qui réalise les opérations suivantes :

  1. exécute les commandes définies dans backup_pre_commands ;
  2. arrête les conteneurs en cours d’exécution listés dans backup_stop_containers ;
  3. sauvegarde les chemins déclarés dans backup_paths avec le tag automated et le nom d’hôte du serveur ;
  4. redémarre les conteneurs précédemment arrêtés ;
  5. si la réplication Hetzner est activée, copie les snapshots absents vers la Storage Box ;
  6. supprime les anciens snapshots dans chaque dépôt selon la politique de rétention, puis libère les données devenues inutiles avec prune.

Un trap garantit que les conteneurs arrêtés par le script sont redémarrés si une erreur survient pendant la sauvegarde.

Le service restic-backup.service est de type oneshot. Il est déclenché par restic-backup.timer, une fois par jour à 03:00 par défaut, avec un délai aléatoire maximal de 15 minutes.

roles/backup/
├── defaults/
│ └── main.yml # Variables par défaut
├── files/
│ ├── resticctl # Administration du dépôt local
│ └── resticctl-hetzner # Administration du dépôt Hetzner
├── handlers/
│ └── main.yml # Rechargement de systemd
├── tasks/
│ ├── install.yml # Installation de Restic
│ ├── hetzner.yml # SSH et dépôt de la Storage Box
│ ├── main.yml # Validation et point d'entrée
│ ├── repository.yml # Configuration du dépôt
│ └── service.yml # Script, service et timer systemd
└── templates/
├── restic-backup.service.j2
├── restic-backup.sh.j2
├── restic-backup.timer.j2
└── restic.env.j2
  • une distribution utilisant APT ;
  • systemd ;
  • Docker, car le service généré déclare Requires=docker.service ;
  • suffisamment d’espace pour le dépôt et les fichiers temporaires ;
  • les répertoires parents nécessaires aux commandes préparatoires.

La configuration par défaut crée un dump Penpot dans /srv/backups/staging/penpot.dump. Le répertoire /srv/backups/staging doit donc exister avant la première sauvegarde.

Les valeurs par défaut sont définies dans roles/backup/defaults/main.yml.

Variable Type Valeur par défaut Description
backup_restic_package chaîne restic Nom du paquet Restic installé avec APT.
backup_repository chaîne /srv/backups/restic Dépôt Restic local ou URL d’un backend distant.
backup_password chaîne définie dans les valeurs par défaut Mot de passe utilisé pour chiffrer le dépôt.
vault_backup_password chaîne vault_restic_backup_password Variable intermédiaire utilisée par backup_password pour lire le secret Ansible Vault.
backup_environment_file chemin /etc/restic/restic.env Fichier contenant RESTIC_REPOSITORY et RESTIC_PASSWORD.
backup_script_path chemin /usr/local/sbin/restic-backup Emplacement du script de sauvegarde généré.
backup_hetzner_enabled booléen false Active la copie hors site vers une Hetzner Storage Box.
backup_hetzner_host chaîne vide Domaine de la Storage Box, par exemple u123456.your-storagebox.de.
backup_hetzner_user chaîne vide Utilisateur principal ou sous-compte de la Storage Box.
backup_hetzner_port entier 23 Port SSH Hetzner utilisant les clés au format OpenSSH.
backup_hetzner_repository_path chemin restic Chemin relatif du dépôt dans la Storage Box.
backup_hetzner_password chaîne backup_password Mot de passe de chiffrement du dépôt distant. Peut être différent du dépôt local.
backup_hetzner_ssh_private_key chaîne vide Contenu de la clé privée SSH, fourni par Ansible Vault.
backup_hetzner_ssh_private_key_path chemin /root/.ssh/id_ed25519_restic_hetzner Clé privée dédiée présente sur le VPS.
backup_pre_commands liste de chaînes dump PostgreSQL Penpot Commandes exécutées avant l’arrêt des conteneurs.
backup_paths liste de chemins données Uptime Kuma, Forgejo et Penpot Fichiers et répertoires inclus dans chaque snapshot.
backup_excludes liste de chemins ou motifs [] Éléments exclus de la sauvegarde avec restic backup --exclude.
backup_keep_daily entier 60 Nombre de snapshots quotidiens conservés.
backup_keep_monthly entier 12 Nombre de snapshots mensuels conservés.
backup_timer_on_calendar chaîne *-*-* 03:00:00 Expression systemd définissant la fréquence d’exécution.
backup_timer_randomized_delay durée systemd 15m Délai aléatoire ajouté à l’heure planifiée.
backup_stop_containers liste de chaînes uptime-kuma, forgejo Conteneurs arrêtés uniquement s’ils existent et sont en cours d’exécution.
Application Chemin Contenu
Uptime Kuma /var/lib/docker/volumes/uptime-kuma-data/_data Données du volume Docker.
Forgejo /srv/apps/forgejo/data Données persistantes de Forgejo.
Penpot /srv/backups/staging/penpot.dump Dump PostgreSQL créé avant la sauvegarde.
Penpot /var/lib/docker/volumes/penpot_penpot_assets/_data Ressources stockées dans le volume Docker.
backup_password: "{{ vault_backup_password }}"
backup_repository: /srv/backups/restic
backup_pre_commands:
- |
install -d -m 0700 /srv/backups/staging
POSTGRES_CONTAINER=$(docker compose --project-directory /srv/apps/penpot ps -q penpot-postgres)
docker exec "$POSTGRES_CONTAINER" sh -c \
'pg_dump --username="$POSTGRES_USER" --dbname="$POSTGRES_DB" --format=custom' \
> /srv/backups/staging/penpot.dump
backup_paths:
- /srv/apps/forgejo/data
- /srv/backups/staging/penpot.dump
- /var/lib/docker/volumes/uptime-kuma-data/_data
- /var/lib/docker/volumes/penpot_penpot_assets/_data
backup_excludes:
- "*.tmp"
- "*/cache/*"
backup_keep_daily: 60
backup_keep_monthly: 12
backup_timer_on_calendar: "*-*-* 03:00:00"
backup_timer_randomized_delay: 15m
backup_stop_containers:
- uptime-kuma
- forgejo
backup_hetzner_enabled: true
backup_hetzner_host: u123456.your-storagebox.de
backup_hetzner_user: u123456
backup_hetzner_repository_path: restic/production
# Facultatif : utilise backup_password si cette variable est omise.
backup_hetzner_password: "{{ vault_backup_hetzner_password }}"
backup_hetzner_ssh_private_key: "{{ vault_backup_hetzner_ssh_private_key }}"

Le rôle utilise une clé ED25519 dédiée, sans passphrase, car le timer systemd doit pouvoir se connecter sans interaction. La clé privée est conservée chiffrée dans Ansible Vault, puis déployée automatiquement sur le VPS avec le mode 0600. Seule la clé publique est installée sur la Storage Box.

  1. Sur la machine Ansible, générer la paire de clés :
Terminal window
ssh-keygen -t ed25519 \
-f ~/.ssh/id_ed25519_restic_hetzner \
-N '' \
-C "restic-backup"
  1. Dans Hetzner Console, activer SSH Support pour la Storage Box. Activer aussi External Reachability si le VPS se trouve hors du réseau Hetzner.
  2. Installer la clé publique avec le mot de passe de la Storage Box :
Terminal window
ssh-copy-id -p 23 -s \
-i ~/.ssh/id_ed25519_restic_hetzner.pub \
u123456@u123456.your-storagebox.de
  1. Ouvrir le fichier Vault :
Terminal window
ansible-vault edit inventory/group_vars/vps/vault_backup.yml

Ajouter le contenu complet de la clé privée :

vault_backup_hetzner_ssh_private_key: |
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----

Définir ensuite les variables backup_hetzner_* dans inventory/group_vars/vps/vps.yml, puis exécuter le playbook. Le rôle déploie la clé privée, configure la connexion SSH non interactive, initialise le dépôt distant et active sa réplication à chaque sauvegarde.

Le rôle est appelé depuis playbooks/site.yml.

Le rôle :

  1. installe /usr/local/bin/resticctl ;
  2. vérifie que le dépôt, le mot de passe et la liste des chemins ne sont pas vides ;
  3. installe Restic ;
  4. crée /etc/restic avec le mode 0700 ;
  5. crée le dépôt local si nécessaire ;
  6. déploie /etc/restic/restic.env avec le mode 0600 ;
  7. initialise le dépôt s’il n’est pas encore accessible ;
  8. si Hetzner est activé, configure SSH et initialise le dépôt distant ;
  9. déploie le script, le service et le timer ;
  10. recharge systemd ;
  11. active et démarre restic-backup.timer.

Le rôle est idempotent : un dépôt existant n’est pas réinitialisé et le timer reste simplement actif lors des exécutions suivantes.

La commande resticctl charge automatiquement /etc/restic/restic.env, puis transmet tous ses arguments à Restic. Elle doit être exécutée avec les droits permettant de lire ce fichier.

Pour reproduire exactement le cycle automatisé, utiliser le service systemd :

Terminal window
sudo systemctl start restic-backup.service

Lancer directement resticctl backup ne déclenche ni les commandes préparatoires, ni l’arrêt des conteneurs, ni la politique de rétention.

Terminal window
sudo resticctl snapshots
Terminal window
sudo resticctl ls latest
Terminal window
sudo resticctl check

Pour vérifier aussi les données des packs, opération plus longue et plus coûteuse :

Terminal window
sudo resticctl check --read-data

Lister d’abord les snapshots disponibles :

Terminal window
sudo resticctl snapshots

Restaurer le dernier snapshot sans écraser les données en production :

Terminal window
sudo mkdir -p /srv/restores/latest
sudo resticctl restore latest --target /srv/restores/latest

Restic restaure l’arborescence complète sous le répertoire cible. Vérifier les fichiers, arrêter le service concerné, puis recopier uniquement les données nécessaires vers leur emplacement d’origine.

Après restauration, localiser le dump :

Terminal window
sudo find /srv/restores/latest -name penpot.dump

La réinjection doit être adaptée au nom du conteneur, à la base et à l’utilisateur PostgreSQL de l’environnement. Vérifier ces valeurs avant toute restauration en production.

Terminal window
systemctl list-timers restic-backup.timer
Terminal window
systemctl status restic-backup.timer
systemctl status restic-backup.service

Un service oneshot peut apparaître inactive (dead) après une exécution réussie. Le résultat de la dernière exécution et les journaux permettent de confirmer son état.

Terminal window
journalctl -u restic-backup.service
journalctl -u restic-backup.timer
Terminal window
sudo resticctl snapshots --tag automated

Contrôler également les snapshots répliqués :

Terminal window
sudo resticctl-hetzner snapshots --tag automated

Vérifier que le fichier d’environnement existe, que ses permissions sont correctes et que le dépôt est accessible :

Terminal window
sudo stat /etc/restic/restic.env
sudo resticctl snapshots

Une erreur wrong password or no key found indique généralement que le mot de passe ne correspond pas au dépôt.

Pour le dépôt Hetzner, vérifier aussi la connexion SSH et le dépôt distant :

Terminal window
sudo ssh -T backup-hetzner
sudo resticctl-hetzner snapshots

Afficher les journaux détaillés de la dernière exécution :

Terminal window
sudo journalctl -u restic-backup.service -n 200 --no-pager

Contrôler en priorité :

  • l’existence de chaque chemin de backup_paths ;
  • l’espace disponible avec df -h ;
  • l’état du démon Docker ;
  • l’existence de /srv/backups/staging ;
  • le résultat des commandes de backup_pre_commands.

Le script redémarre automatiquement les conteneurs qu’il a lui-même arrêtés. Si un arrêt brutal empêche le mécanisme de s’exécuter, vérifier leur état puis les redémarrer explicitement :

Terminal window
docker ps -a
sudo docker start uptime-kuma forgejo
Terminal window
systemctl is-enabled restic-backup.timer
systemctl list-timers --all restic-backup.timer
journalctl -u restic-backup.timer

Réactiver le timer si nécessaire :

Terminal window
sudo systemctl enable --now restic-backup.timer
  • conserver le mot de passe dans Ansible Vault et dans un gestionnaire de secrets indépendant ;
  • stocker au moins une copie hors du VPS ;
  • surveiller les erreurs du service systemd ;
  • exécuter régulièrement resticctl check ;
  • réaliser des tests de restauration ;
  • dimensionner l’espace nécessaire au dépôt, aux dumps temporaires et à l’opération prune ;
  • réviser la liste des chemins lors de l’ajout ou de la suppression d’un service.