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
resticctletresticctl-hetznerpour administrer les dépôts.
Fonctionnement
Section titled “Fonctionnement”À chaque exécution, le rôle génère un script qui réalise les opérations suivantes :
- exécute les commandes définies dans
backup_pre_commands; - arrête les conteneurs en cours d’exécution listés dans
backup_stop_containers; - sauvegarde les chemins déclarés dans
backup_pathsavec le tagautomatedet le nom d’hôte du serveur ; - redémarre les conteneurs précédemment arrêtés ;
- si la réplication Hetzner est activée, copie les snapshots absents vers la Storage Box ;
- 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.
Structure du rôle
Section titled “Structure du rôle”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.j2Prérequis
Section titled “Prérequis”- 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.
Variables
Section titled “Variables”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. |
Chemins sauvegardés par défaut
Section titled “Chemins sauvegardés par défaut”| 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. |
Exemple de configuration
Section titled “Exemple de configuration”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: 60backup_keep_monthly: 12
backup_timer_on_calendar: "*-*-* 03:00:00"backup_timer_randomized_delay: 15m
backup_stop_containers: - uptime-kuma - forgejo
backup_hetzner_enabled: truebackup_hetzner_host: u123456.your-storagebox.debackup_hetzner_user: u123456backup_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 }}"Préparer la clé SSH Hetzner
Section titled “Préparer la clé SSH Hetzner”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.
- Sur la machine Ansible, générer la paire de clés :
ssh-keygen -t ed25519 \ -f ~/.ssh/id_ed25519_restic_hetzner \ -N '' \ -C "restic-backup"- Dans Hetzner Console, activer SSH Support pour la Storage Box. Activer aussi External Reachability si le VPS se trouve hors du réseau Hetzner.
- Installer la clé publique avec le mot de passe de la Storage Box :
ssh-copy-id -p 23 -s \ -i ~/.ssh/id_ed25519_restic_hetzner.pub \ u123456@u123456.your-storagebox.de- Ouvrir le fichier Vault :
ansible-vault edit inventory/group_vars/vps/vault_backup.ymlAjouter 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.
Déploiement
Section titled “Déploiement”Le rôle est appelé depuis playbooks/site.yml.
Le rôle :
- installe
/usr/local/bin/resticctl; - vérifie que le dépôt, le mot de passe et la liste des chemins ne sont pas vides ;
- installe Restic ;
- crée
/etc/resticavec le mode0700; - crée le dépôt local si nécessaire ;
- déploie
/etc/restic/restic.envavec le mode0600; - initialise le dépôt s’il n’est pas encore accessible ;
- si Hetzner est activé, configure SSH et initialise le dépôt distant ;
- déploie le script, le service et le timer ;
- recharge systemd ;
- 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.
Utilisation de resticctl
Section titled “Utilisation de resticctl”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.
Lancer une sauvegarde manuellement
Section titled “Lancer une sauvegarde manuellement”Pour reproduire exactement le cycle automatisé, utiliser le service systemd :
sudo systemctl start restic-backup.serviceLancer directement resticctl backup ne déclenche ni les commandes préparatoires, ni l’arrêt des conteneurs, ni la politique de rétention.
Lister les snapshots
Section titled “Lister les snapshots”sudo resticctl snapshotsExaminer le contenu d’un snapshot
Section titled “Examiner le contenu d’un snapshot”sudo resticctl ls latestVérifier l’intégrité du dépôt
Section titled “Vérifier l’intégrité du dépôt”sudo resticctl checkPour vérifier aussi les données des packs, opération plus longue et plus coûteuse :
sudo resticctl check --read-dataRestauration
Section titled “Restauration”Restaurer dans un répertoire temporaire
Section titled “Restaurer dans un répertoire temporaire”Lister d’abord les snapshots disponibles :
sudo resticctl snapshotsRestaurer le dernier snapshot sans écraser les données en production :
sudo mkdir -p /srv/restores/latestsudo resticctl restore latest --target /srv/restores/latestRestic 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.
Restaurer le dump PostgreSQL de Penpot
Section titled “Restaurer le dump PostgreSQL de Penpot”Après restauration, localiser le dump :
sudo find /srv/restores/latest -name penpot.dumpLa 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.
Vérification
Section titled “Vérification”Vérifier le prochain déclenchement
Section titled “Vérifier le prochain déclenchement”systemctl list-timers restic-backup.timerVérifier le timer et le service
Section titled “Vérifier le timer et le service”systemctl status restic-backup.timersystemctl status restic-backup.serviceUn 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.
Consulter les journaux
Section titled “Consulter les journaux”journalctl -u restic-backup.servicejournalctl -u restic-backup.timerContrôler les snapshots automatisés
Section titled “Contrôler les snapshots automatisés”sudo resticctl snapshots --tag automatedContrôler également les snapshots répliqués :
sudo resticctl-hetzner snapshots --tag automatedDépannage
Section titled “Dépannage”Le dépôt ne s’ouvre pas
Section titled “Le dépôt ne s’ouvre pas”Vérifier que le fichier d’environnement existe, que ses permissions sont correctes et que le dépôt est accessible :
sudo stat /etc/restic/restic.envsudo resticctl snapshotsUne 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 :
sudo ssh -T backup-hetznersudo resticctl-hetzner snapshotsLa sauvegarde manuelle échoue
Section titled “La sauvegarde manuelle échoue”Afficher les journaux détaillés de la dernière exécution :
sudo journalctl -u restic-backup.service -n 200 --no-pagerContrô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.
Un conteneur reste arrêté
Section titled “Un conteneur reste arrêté”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 :
docker ps -asudo docker start uptime-kuma forgejoLe timer ne s’exécute pas
Section titled “Le timer ne s’exécute pas”systemctl is-enabled restic-backup.timersystemctl list-timers --all restic-backup.timerjournalctl -u restic-backup.timerRéactiver le timer si nécessaire :
sudo systemctl enable --now restic-backup.timerBonnes pratiques
Section titled “Bonnes pratiques”- 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.