Configuration Automatisée de Sauvegarde et Maintenance Plakar
Architecture de Sauvegarde Plakar
Cette documentation décrit une solution complète et sécurisée pour effectuer des sauvegardes centralisées vers un serveur distant avec Plakar, le tout au travers d'un montage SSHFS. Tout le processus de déploiement, de sauvegarde et de rotation (pruning / nettoyage) est entièrement automatisé à l'aide d'Ansible et de Systemd.
Points clés de l'architecture :
- Sauvegardes autonomes (Pull / Agentless) : Chaque serveur s'occupe de sauvegarder ses propres données au travers d'un timer Systemd, vers un dépôt Plakar distant monté dynamiquement.
- Réseau Sécurisé : Les montages s'effectuent via un hôte de rebond (Bastion SSH) pour protéger le serveur des sauvegardes limitant son exposition réseau.
- Registre des hôtes : Chaque script de sauvegarde inscrit le nom d'hôte sur un fichier trace (backup-hosts.txt).
- Maintenance Centralisée : Un seul serveur maître est en charge de lire le registre des hôtes et d'exécuter la rotation des sauvegardes afin de libérer de l'espace sur l'hôte distant.
- Politique de Rétention Stricte : Application d'une double condition, garantissant de conserver au moins 3 sauvegardes utiles pour chaque hôte, tout en effaçant les sauvegardes de plus de 120 jours pour ceux en ayant davantage.
1. Prérequis et Sécurité
Avant d'exécuter les scripts, vos variables sensibles et configurations doivent être proprement préparées (ex: au sein de vos fichiers variable-ansible_playbook.yaml et variable-ansible_secret.yml).
Dans cette documentation (publique), les données d'identification sont identifiées par des valeurs fictives. Lors de votre usage, veillez à remplacer :
- YOUR_SECRET_PASSPHRASE_HERE : La phrase secrète de chiffrement du dépôt Plakar.
- backup_user : L'utilisateur de la machine ciblant le stockage distant.
- backup.example.com : Le domaine / l'adresse IP du serveur de stockage Plakar.
- bastion.example.com : Le domaine / l'adresse IP de votre Bastion SSH.
2. Déploiement du Service de Sauvegarde
Ce premier playbook Ansible se déroule sur l'ensemble de vos serveurs (cibles) qui doivent être sauvegardés. Il installe les dépendances nécessaires, récupère le binaire Plakar via ses dépôts officiels, crée un script de sauvegarde bash et le planifie dans Systemd.
Playbook Ansible
---
- name: Installer et configurer Plakar pour les sauvegardes
hosts: servers_to_backup
gather_facts: false
become: true
become_method: sudo
vars_files:
- variable-ansible_playbook.yaml
- variable-ansible_secret.yml
tasks:
- name: Bloc d'installation et configuration
block:
- name: Installer les dépendances pour Plakar
ansible.builtin.package:
name:
- curl
- gnupg2
- sshfs
state: present
- name: Télécharger et ajouter la clé GPG de Plakar
ansible.builtin.shell: |
set -o pipefail
curl -fsSL https://packages.plakar.io/keys/plakar.gpg | gpg --dearmor -o /usr/share/keyrings/plakar.gpg
args:
creates: /usr/share/keyrings/plakar.gpg
executable: /bin/bash
- name: Ajouter le dépôt Plakar
ansible.builtin.apt_repository:
repo: "deb [signed-by=/usr/share/keyrings/plakar.gpg] https://packages.plakar.io/deb stable main"
filename: plakar
state: present
update_cache: yes
- name: Installer Plakar
ansible.builtin.package:
name: plakar
state: present
- name: Vérifier la version de Plakar
ansible.builtin.command: plakar version
register: plakar_version
changed_when: false
- name: Afficher la version de Plakar
ansible.builtin.debug:
msg: "Plakar version: {{ plakar_version.stdout }}"
- name: S'assurer que le répertoire /opt/ansible existe
ansible.builtin.file:
path: /opt/ansible
state: directory
mode: '0755'
owner: root
group: root
- name: Supprimer l'ancien script de backup Plakar
ansible.builtin.file:
path: /opt/ansible/plakar-backup.sh
state: absent
- name: Copier le script de backup Plakar
ansible.builtin.copy:
src: ansible-script-backup.sh
dest: /opt/ansible/ansible-script-backup.sh
mode: '0700'
owner: root
group: root
- name: Créer le service Systemd pour le backup
ansible.builtin.copy:
dest: /etc/systemd/system/plakar-backup.service
mode: '0644'
content: |
[Unit]
Description=Plakar Backup Service
After=network.target
[Service]
Type=oneshot
ExecStart=/opt/ansible/ansible-script-backup.sh
User=root
- name: Créer le timer Systemd pour le backup (Mercredi 03:00)
ansible.builtin.copy:
dest: /etc/systemd/system/plakar-backup.timer
mode: '0644'
content: |
[Unit]
Description=Run Plakar Backup every Wednesday at 3AM
[Timer]
OnCalendar=Wed *-*-* 03:00:00
Persistent=true
[Install]
WantedBy=timers.target
- name: Activer et démarrer le timer Systemd
ansible.builtin.systemd:
name: plakar-backup.timer
state: started
enabled: yes
daemon_reload: yes
rescue:
- name: Envoyer une alerte par mail en cas d'erreur
local_action:
module: mail
host: "{{ smtp_host }}"
port: "{{ smtp_port }}"
to: "{{ email_recipient }}"
subject: "Échec du playbook Plakar sur {{ inventory_hostname }}"
body: |
Le job d'installation/configuration de Plakar sur {{ inventory_hostname }} a rencontré une erreur.
Tâche en échec : {{ ansible_failed_task.name }}
Détails de l'erreur :
{{ ansible_failed_result.msg | default(ansible_failed_result) }}
delegate_to: localhost
3. Le Script de Sauvegarde Automatique
Ce script (ansible-script-backup.sh) déploie plusieurs comportements cruciaux :
1. Traversée via Bastion SSH : Il s'authentifie au serveur distant au travers d'un mandataire.
2. Initialisation automatique : Si le dépôt Plakar distant n'a jamais été initialisé (ex: nouvelle configuration vierge), il exécute un flush de sécurité et lance son setup.
3. Fréquence respectueuse : Grâce à check_backup_needed, la tâche évalue si une précédente sauvegarde a été réalisée dans les 7 derniers jours sur sa racine. Elle passe cette sauvegarde le cas échéant (économie réseau et CPU).
4. Inventaire / Liste des Hôtes : Ajoute son Hostname ($HOSTNAME_TAG) à un fichier backup-hosts.txt dans le dépôt. Ceci devient alors une source de vérité (utilisée par la suite par la maintenance) montrant tous les serveurs qui ont une sauvegarde active.
5. Aide à la Restauration : Sauvegarde des listes des paquets logiciels APT et Flatpak, en plus des URLs d'ajout de vos repos apt.
#!/bin/bash
# --- SÉCURITÉ ---
if [ "$(id -u)" -ne 0 ]; then
echo "Ce script est conçu pour être lancé avec sudo." >&2
exit 1
fi
plakar -disable-security-check >/dev/null 2>&1
# --- VARIABLES DE CONFIGURATION ---
REMOTE_USER="backup_user"
REMOTE_HOST="backup.example.com"
BASTION_HOST="bastion.example.com"
REMOTE_PATH="/mnt/backup/plakar"
SSH_KEY_PATH="/home/your_user/.ssh/id_rsa"
LOCAL_MOUNT_POINT="/mnt/remote-backup"
export PLAKAR_PASSPHRASE="YOUR_SECRET_PASSPHRASE_HERE"
SSHFS_URL="${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}"
PLAKAR_REPOSITORY="$LOCAL_MOUNT_POINT"
# --- FONCTION DE NETTOYAGE ---
cleanup() {
echo "--- Nettoyage ---"
echo "Tentative de démontage de $LOCAL_MOUNT_POINT..."
fusermount -uz "$LOCAL_MOUNT_POINT"
}
trap cleanup EXIT
# --- AJOUT DE LA CLÉ DE L'HÔTE ---
mkdir -p "$HOME/.ssh"
ssh-keyscan -H "$REMOTE_HOST" >> "$HOME/.ssh/known_hosts" 2>/dev/null
ssh-keyscan -H "$BASTION_HOST" >> "$HOME/.ssh/known_hosts" 2>/dev/null
# --- MONTAGE ET PRÉPARATION ---
echo "--- Préparation ---"
mkdir -p "$LOCAL_MOUNT_POINT"
# --- MONTAGE SSHFS ---
echo "--- Montage SSHFS ---"
if ! mountpoint -q "$LOCAL_MOUNT_POINT"; then
sshfs "$SSHFS_URL" "$LOCAL_MOUNT_POINT" -o "IdentityFile=${SSH_KEY_PATH}",reconnect,ServerAliveInterval=15,ServerAliveCountMax=3,allow_other,StrictHostKeyChecking=no,ProxyCommand="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ${SSH_KEY_PATH} -W %h:%p ${REMOTE_USER}@${BASTION_HOST}"
if [ $? -ne 0 ]; then
echo "Erreur critique lors du montage SSHFS. Arrêt." >&2
exit 1
fi
else
echo "Le point de montage est déjà actif."
fi
# --- CRÉATION DU DÉPÔT PLAKAR (PREMIER LANCEMENT) ---
if [ ! -f "$PLAKAR_REPOSITORY/CONFIG" ]; then
echo "Le dépôt Plakar n'existe pas ou est corrompu. Création..."
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -J "${REMOTE_USER}@${BASTION_HOST}" -i "$SSH_KEY_PATH" "${REMOTE_USER}@${REMOTE_HOST}" "rm -rf ${REMOTE_PATH}/*"
plakar at "$PLAKAR_REPOSITORY" create
if [ $? -ne 0 ]; then
echo "Erreur lors de la création du dépôt plakar. Arrêt." >&2
exit 1
fi
fi
# --- CONFIGURATION EXCLUSIONS ---
EXCLUSIONS=(
"*.mp4"
"*.mkv"
"*.avi"
"*.mov"
"*.wmv"
"/mnt/data/downloads"
"/mnt/remote-backup"
"/mnt/media"
"/bin"
"/boot"
"/dev"
"/etc"
"/initrd.img"
"/initrd.img.old"
"/lib"
"/lib64"
"/media"
"/proc"
"/root"
"/run"
"/sbin"
"/srv"
"/sys"
"/tmp"
"/usr"
"/var"
"/vmlinuz"
"/vmlinuz.old"
"/snap"
"/mnt/backup"
)
IGNORE_FLAGS=""
for excl in "${EXCLUSIONS[@]}"; do
IGNORE_FLAGS="$IGNORE_FLAGS -ignore \"$excl\""
done
register_hostname_tag() {
local hostname_tag="$1"
local tag_file="$LOCAL_MOUNT_POINT/backup-hosts.txt"
echo "Enregistrement du hostname-tag"
if [ ! -f "$tag_file" ]; then
touch "$tag_file"
fi
if grep -qFx "$hostname_tag" "$tag_file" 2>/dev/null; then
echo "Le hostname-tag '$hostname_tag' existe déjà."
else
echo "Ajout en de '$hostname_tag'."
echo "$hostname_tag" >> "$tag_file"
fi
}
check_backup_needed() {
local target="$1"
local last_date_str
local hostname_tag
hostname_tag=$(hostname)
# Récupère la dernière date du snapshot pour ce sous-serveur
last_date_str=$(plakar at "$PLAKAR_REPOSITORY" ls -tag "$hostname_tag" | awk -v tgt="$target" '$NF == tgt {print $1; exit}')
if [ -n "$last_date_str" ]; then
local last_ts=$(date -d "$last_date_str" +%s)
local current_ts=$(date +%s)
local elapsed_seconds=$((current_ts - last_ts))
# 604800 secondes = 1 semaine (7 jours)
if [ $elapsed_seconds -lt 604800 ]; then
return 1 # Récent, pas besoin de backup
fi
fi
return 0 # Pas de backup ou trop vieux
}
HOSTNAME_TAG=$(hostname)
register_hostname_tag "$HOSTNAME_TAG"
DO_BACKUP=false
if check_backup_needed "/"; then
echo "Sauvegarde de / nécessaire."
DO_BACKUP=true
else
echo "Sauvegarde de / récente (< 7 jours). Ignorée."
fi
if [ "$DO_BACKUP" = false ]; then
echo "Toutes les sauvegardes sont à jour."
exit 0
fi
# --- SAUVEGARDE DES PAQUETS MÉTA ---
echo "Sauvegarde des listes de paquets et d'applications..."
if command -v apt-mark &> /dev/null; then
apt-mark showmanual > "/opt/apt-list.txt"
fi
if command -v flatpak &> /dev/null; then
flatpak list --columns=application --app > "/opt/flatpak-list.txt"
fi
if [ -d "/etc/apt/sources.list.d" ]; then
cat /etc/apt/sources.list.d/* > "/opt/repo-fallback.txt"
fi
# --- SAUVEGARDE PLAKAR ---
if [ "$DO_BACKUP" = true ]; then
CMD="plakar at \"$PLAKAR_REPOSITORY\" backup -tag \"$HOSTNAME_TAG\" -concurrency 1 $IGNORE_FLAGS \"/\""
echo "Commande: $CMD"
eval "$CMD"
if [ $? -ne 0 ]; then
echo "Erreur critique de la sauvegarde plakar." >&2
exit 1
fi
fi
exit 0
4. Maintenance Centralisée et Pruning (Nettoyage des archives)
Afin d'éviter de remplir l'espace de stockage distant à terme, un autre serveur d'architecture (ou votre serveur d'infrastructure principal) va exécuter une routine Ansible ponctuelle pour nettoyer l'évolution des sauvegardes. L'opération est hautement sécurisée : elle utilise un script sur-mesure Python (intégré et exécutable par Ansible via stdin) pour évaluer quels snapshots doivent être prunes ou conservés.
- Mécanisme Anti-Conflit : Ce script identifie les éventuels processus Docker de l'UI Web Plakar qui tourneraient et pourraient verrouiller le dossier, afin de libérer les
stale locksavant l'exécution. - Paramètres : Tous les snapshots vieux de plus de 120 jours sont supprimés.
- Fail-safe Intelligent : Si un serveur n'a pas pu se sauvegarder récemment après une panne, et que toutes ces sauvegardes sont proches du stade obsolète (> 120 jours), la tâche préserve au minium ses 3 dernières sauvegardes afin de ne pas perdre de données complètes.
Scénario de Maintenance (Ansible)
---
- name: Maintenance et nettoyage Plakar
hosts: central_maintenance_server
gather_facts: false
become: true
become_method: sudo
vars_files:
- variable-ansible_playbook.yaml
- variable-ansible_secret.yml
vars:
min_snapshots_per_host: 3
prune_age_days: 120
plakar_passphrase: "YOUR_SECRET_PASSPHRASE_HERE"
remote_user: "backup_user"
remote_host: "backup.example.com"
bastion_host: "bastion.example.com"
remote_path: "/mnt/backup/plakar"
ssh_key_path: "/home/your_user/.ssh/id_rsa"
local_mount_point: "/mnt/remote-backup"
plakar_service_container: "plakar-web"
tasks:
- name: Gestion du cycle de maintenance
block:
- name: Désactiver la politique de redémarrage automatique
ansible.builtin.command: docker update --restart=no {{ plakar_service_container }}
changed_when: true
- name: Arrêter le service Plakar Web (pour libérer le verrou)
community.docker.docker_container:
name: "{{ plakar_service_container }}"
state: stopped
- name: Attendre la libération complète du verrou (5s)
ansible.builtin.pause:
seconds: 5
- name: Créer le point de montage
ansible.builtin.file:
path: "{{ local_mount_point }}"
state: directory
mode: '0755'
- name: Ajouter la clé de l'hôte distant (known_hosts)
ansible.builtin.shell: |
mkdir -p /root/.ssh
ssh-keyscan -H {{ remote_host }} >> /root/.ssh/known_hosts 2>/dev/null || true
ssh-keyscan -H {{ bastion_host }} >> /root/.ssh/known_hosts 2>/dev/null || true
changed_when: false
- name: Vérifier si déjà monté
ansible.builtin.shell: grep -qs "{{ local_mount_point }}" /proc/mounts
register: mount_check
ignore_errors: true
changed_when: false
- name: Monter SSHFS si nécessaire
ansible.builtin.command: >
sshfs "{{ sshfs_url }}" "{{ local_mount_point }}"
-o "IdentityFile={{ ssh_key_path }}",reconnect,ServerAliveInterval=15,ServerAliveCountMax=3,allow_other,StrictHostKeyChecking=no,ProxyCommand="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i {{ ssh_key_path }} -W %h:%p {{ remote_user }}@{{ bastion_host }}"
when: mount_check.rc != 0
register: sshfs_mount
- name: Nettoyer les fichiers de verrouillage (stale locks)
ansible.builtin.shell: rm -f "{{ local_mount_point }}/locks/"*
changed_when: true
ignore_errors: true
- name: Lire la liste des hosts depuis backup-hosts.txt
ansible.builtin.command: cat "{{ local_mount_point }}/backup-hosts.txt"
register: backup_hosts_file
changed_when: false
failed_when: false
- name: Parser la liste des hosts
ansible.builtin.set_fact:
all_tags_list: "{{ backup_hosts_file.stdout_lines | select('match', '^[a-zA-Z0-9._-]+$') | list }}"
- name: Analyser les snapshots pour chaque tag (Prune Evaluation)
ansible.builtin.shell: |
plakar at "{{ local_mount_point }}" ls -tag "{{ item }}" | python3 -c '
import sys, json, datetime
snapshots = []
current_time = datetime.datetime.now(datetime.timezone.utc)
prune_threshold = 120 # jours
for line in sys.stdin:
parts = line.strip().split()
if len(parts) < 6: continue
date_str = parts[0]
snapshot_id = parts[1]
path = parts[-1]
try:
if date_str.endswith("Z"): date_str = date_str[:-1] + "+00:00"
snap_date = datetime.datetime.fromisoformat(date_str)
age = (current_time - snap_date).days
except:
continue
snapshots.append({
"id": snapshot_id,
"date": snap_date.isoformat(),
"age_days": age,
"path": path
})
oldest_age = max([s["age_days"] for s in snapshots]) if snapshots else 0
old_count = len([s for s in snapshots if s["age_days"] > prune_threshold])
remaining_after_prune = len(snapshots) - old_count
print(json.dumps({
"tag": "{{ item }}",
"total_snapshots": len(snapshots),
"oldest_age_days": oldest_age,
"snapshots_older_than_120d": old_count,
"remaining_after_prune": remaining_after_prune
}))
'
environment:
PLAKAR_PASSPHRASE: "{{ plakar_passphrase }}"
loop: "{{ all_tags_list }}"
register: all_tags_analysis
changed_when: false
- name: Exécuter le prune PAR TAG (protège les hosts inactifs)
ansible.builtin.command: plakar at "{{ local_mount_point }}" prune -days {{ prune_age_days }} -tag "{{ item.item }}" -apply
environment:
PLAKAR_PASSPHRASE: "{{ plakar_passphrase }}"
when: >-
(item.stdout | from_json).snapshots_older_than_120d > 0 and
(item.stdout | from_json).remaining_after_prune >= min_snapshots_per_host
loop: "{{ all_tags_analysis.results }}"
loop_control:
label: "Pruning {{ item.item }}"
register: prune_results
- name: Attendre la fin des prunes (10s)
ansible.builtin.pause:
seconds: 10
when: prune_results.changed
- name: Exécuter la maintenance globale Plakar
ansible.builtin.command: plakar at "{{ local_mount_point }}" maintenance
environment:
PLAKAR_PASSPHRASE: "{{ plakar_passphrase }}"
register: maintenance_out
when: prune_results.changed
changed_when: maintenance_out.rc == 0
- name: Démonter le volume SSHFS
ansible.builtin.command: fusermount -uz "{{ local_mount_point }}"
when: mount_check.rc != 0 or sshfs_mount.changed
ignore_errors: true
- name: Réactiver et Redémarrer le service Plakar Web
ansible.builtin.command: docker update --restart=always {{ plakar_service_container }}
changed_when: true
- community.docker.docker_container:
name: "{{ plakar_service_container }}"
state: started
rescue:
- name: Rollback / Rescue en cas d'erreur de maintenance
ansible.builtin.command: fusermount -uz "{{ local_mount_point }}"
ignore_errors: true
- name: Envoyer une alerte par mail
local_action:
module: mail
host: "{{ smtp_host }}"
port: "{{ smtp_port }}"
to: "{{ email_recipient }}"
subject: "Échec critique / Maintenance Plakar"
body: |
Tâche en échec : {{ ansible_failed_task.name }}
Erreur : {{ ansible_failed_result.msg | default(ansible_failed_result) }}
delegate_to: localhost
F.A.Q
Avec l’outil de backup plakar je peux utiliser SFTP quelle serais la commande ?
Oui ! Plakar dispose d’un connecteur SFTP natif.
Il suffit de pointer le “kloset” vers l’URL SFTP puis d’utiliser les actions habituelles (create, backup, restore, …).
Exemple complet
# 1️⃣ Créer un dépôt (kloset) sur le serveur SFTP
plakar at sftp://sftpuser@my‑server.example.com/backups create
# 2️⃣ Sauvegarder un répertoire local dans ce dépôt
plakar at sftp://sftpuser@my‑server.example.com/backups backup /home/monuser/Documents
Avec un remote nommé (option pratique)
# Définir le remote « mysftp » une fois pour toutes
plakar config remote set mysftp location sftp://sftpuser@my‑server.example.com/backups
# Sauvegarder en utilisant le nom du remote
plakar at mysftp backup /home/monuser/Documents
Ces commandes créent le dépôt sur le serveur SFTP (si besoin) et y envoient le snapshot du répertoire indiqué, le tout via une connexion SSH/SFTP sécurisée et sans interaction manuelle (clé SSH configurée au préalable).
Il y’a des commande en cli via plakar pour voir depuis quand certain “repo” (source de sauvegarde) on reçu une sauvegarde ?
Oui, Plakar dispose de plusieurs commandes qui permettent d’afficher les dates (et donc l’historique) des sauvegardes réalisées pour un dépôt / « repo ».
Voici les deux moyens les plus courants :
1. Lister toutes les snapshots d’un dépôt (ls)
Cette commande renvoie la liste de toutes les snapshots présentes dans le kloset :ID de la snapshot – date/heure du snapshot – tag éventuel.
Le format de sortie par défaut montre déjà la date (ISO‑8601) à laquelle chaque snapshot a été créée, ce qui vous indique « depuis quand ce repo a reçu une sauvegarde ».
2. Obtenir le détail d’une snapshot précise (info)
# 1️⃣ Lister pour récupérer l’ID de la snapshot qui vous intéresse
plakar at /var/backups ls
# 2️⃣ Afficher les métadonnées de cette snapshot
plakar info <snapshot-id>
plakar info affiche :
- timestamp : date et heure de création du snapshot
- source : le repo (source) qui a été sauvegardé
- tags, size, nombre de fichiers, etc.
Ainsi vous pouvez vérifier exactement à quel moment chaque source a reçu sa première (ou sa dernière) sauvegarde.
Récapitulatif pratique
| Action | Commande | Résultat affiché |
|---|---|---|
| Lister toutes les sauvegardes | plakar at /chemin/du/kloset ls |
ID + date + tags |
| Détails d’une sauvegarde | plakar info <snapshot‑id> |
Timestamp, source, etc. |
| Filtrer par source (optionnel) | plakar query --source @nom_source --last 1 |
Dernière snapshot de la source |
Astuce : si vous avez plusieurs dépôts, vous pouvez créer un remote nommé (ex.
mysftp) avecplakar config remote set mysftp location sftp://user@host/backupspuis utiliserplakar at mysftp lspour la même chose sans retaper le chemin complet.
Ces deux commandes couvrent normalement le besoin de savoir « depuis quand un repo a reçu une sauvegarde ». Si vous avez besoin de filtres plus avancés (par période, par tag, …), la sous‑commande query permet de combiner des critères supplémentaires.