J’ai enfin automatisé mes sauvegardes Yunohost ! Et c’est cool :)

J’en avais marre d’oublier mes sauvegardes YunoHost… alors j’ai tout automatisé ! Un script maison, un NAS, un peu de logique et beaucoup de tranquillité d’esprit. Voici comment j’ai dit adieu aux backups à la main (et aux sueurs froides).

Vous savez quoi ? Jusqu’à récemment, je faisais toutes mes sauvegardes de serveur YunoHost à la main. Oui, oui, « à la main », comme à l’âge de pierre. Je me connectais tous les quelques jours à l’interface admin, lançais mes backups, et voilà. Pas compliqué, mais ça devenait franchement barbant. Et bien sûr, il m’arrivait d’oublier. Souvent même. Mouais… pas très sérieux tout ça.

En plus, ces sauvegardes restaient tranquillement sur le disque dur de mon serveur, bien au chaud, mais aussi bien encombrantes. Et s’il arrivait malheur à ce fameux serveur (genre crash apocalyptique ou grève surprise des disques durs) ? Eh bien, tchô bonne (comme on dit par chez moi) mes sauvegardes et précieuses données. Pas génial comme plan de bataille, non ?

Borg : pas le feeling

Pour régler ça, j’avais déjà tenté une solution que vous connaissez peut-être : Borg Backup. Et là, bizarrement, j’ai jamais vraiment réussi à m’y faire. Je sais, plein de gens aiment Borg, mais je ne sais pas… il y avait un truc qui clochait. Jamais vraiment confiance. Vous voyez le genre de feeling un peu irrationnel du « Ça sent le piège ce truc » ? Bah c’était exactement ça. Je le sentais pas. Sérieux, je n’ai même jamais testé de restauration complète avec Borg. Je ne suis pas super fier de ça…

Première tentative : les liens symboliques (et autres fausses bonnes idées)

J’ai d’abord tenté de la jouer fino avec un symlink, soit un lien symbolique. Tu relies deux répertoires avec ce lien, par exemple celui par défaut des sauvegardes Yunohost et le dossier partagé du NAS (avec un partage Samba). Et quand Yunohost tente d’écrire dans son dossier par défaut, ça redirige vers le NAS. Du moins, en théorie. Car en pratique, purée les problèmes que ça a provoqué ! J’ai bien regretté ma petite expérience 😅 En gros, c’est pas possible à cause de problèmes de droits. In fine, j’ai dû supprimer le dossier de backup et le refaire.

En cherchant comment changer l’emplacement par défaut des sauvegardes sur Yunohost, j’ai appris qu’on pouvait utiliser --output-directory en ligne de commande pour rediriger le backup vers un autre emplacement.

La commande magique :

sudo yunohost backup create --output-directory /mon/dossier/partage/sur/le/nas/

Sauf qu’en vrai, écrire directement sur le NAS posait encore problème à cause des droits (toujours eux). Solution : créer l’archive en local, puis la déplacer vers le NAS. Et là, je me suis dit : bon, ok, il faut un script mais la solution est là. Il faut que je me lance.

De « simple script » à « usine à gaz » (mais utile !)

Bon, c’est pas mon domaine et j’avais besoin d’aide. J’ai donc demandé à une IA de m’assister dans cette tâche (et je l’ai pas regretté).

Au départ, l’idée était simple : automatiser une sauvegarde régulière sur mon NAS, qui a largement assez d’espace et reste accessible même si le serveur principal tombe. « Facile, tranquille, affaire réglée en deux minutes », pensais-je naïvement.

Haaaa, quelle innocence. 🙂

Premier essai : le script fonctionnait… presque. Quelques erreurs de chemin, d’autorisations, de logique. Bon, c’est normal. Après quelques ajustements, ça tournait enfin correctement. Yeah !

Mais pourquoi s’arrêter là ? Quitte à faire les choses bien, autant ajouter :

  • une rotation automatique des sauvegardes (par type)
  • une distinction entre backups « importants » (gardés plus longtemps) et backups classiques
  • la conservation du premier backup de chaque mois (marqué automatiquement comme « important »)

Et au lieu de faire des backups complets à chaque fois (bien lourds à restaurer), j’ai opté pour des backups séparés par application. Beaucoup plus souple, plus rapide à déplacer, et bien plus logique pour restaurer une app spécifique sans toucher au reste.

Ensuite, j’ai rajouté :

  • une vérification d’intégrité de chaque archive
  • des logs horodatés par exécution
  • des notifications par mail (avec plusieurs niveaux de détail)
  • un mode « dry-run » pour tester sans exécuter réellement
  • la rotation indépendante des sauvegardes, via un paramètre --rotation

Et pour finir, intégration à cron à 1h du matin. Le serveur bosse pendant que je dors. Royal.

Le script complet

Disclaimer : Ce script fonctionne chez moi, mais je ne peux rien garantir pour vous. Je décline toute responsabilité. Sauvegardez vos sauvegardes 😄

#!/bin/bash

################################################################
# backup-script.sh                                             #
# - Sauvegarde système + apps YunoHost                         #
# - Option --rotation : exécuter uniquement la rotation        #
# - Option --test     : dry‑run complet (aucune écriture)      #
################################################################

#############################
# ▸ 0. Gestion des arguments
#############################
ROTATE_ONLY=false
TEST_MODE=false
case "$1" in
  --rotation) ROTATE_ONLY=true ;;
  --test)     TEST_MODE=true  ;;
esac

#############################
# ▸ 1. Configuration globale
#############################
BACKUP_DIR="/nas/backups/"                   # Répertoire des archives
BACKUPS_RETENTION_COUNT=2                    # Archives normales à conserver
IMPORTANT_RETENTION_DAYS=365                 # Conservation des *_important (jours)
LOG_RETENTION_DAYS=14                        # Conservation des logs (jours)
DATE=$(date +%Y-%m-%d_%H%M)
MAIL_TO="[email protected]"
MAIL_LEVEL=5                                 # 0 > 5 (voir doc)
LOG_DIR="/var/log/backup-zrx"
LOG_FILE="$LOG_DIR/backup-zrx_$DATE.log"
REQUIRED_SPACE_MB=1024                       # Espace libre minimal (Mo)
TEMP_BACKUP_DIR="/tmp/yunobackup-$DATE"
DELETED_BACKUPS=""

# ── Wrapper dry‑run ───────────────────────────────────────────
run_cmd() {
  if $TEST_MODE; then
    echo "[TEST] $*"
  else
    "$@"
  fi
}

#############################
# ▸ 2. Préparation logs
#############################
mkdir -p "$LOG_DIR"
find "$LOG_DIR" -type f -name 'backup-zrx_*.log' -mtime +$LOG_RETENTION_DAYS -delete
exec >> "$LOG_FILE" 2>&1

handle_error() {
  echo "[ERREUR] Ligne $1"
  if (( MAIL_LEVEL >= 1 )); then
    BODY="Erreur script YunoHost\nDate: $(date)\nLigne: $1"
    (( MAIL_LEVEL >= 2 )) && BODY+="\n\n$(cat "$LOG_FILE")"
    echo -e "$BODY" | mail -s "[YunoHost] Erreur" "$MAIL_TO"
  fi
  exit 1
}
trap 'handle_error $LINENO' ERR
set -e

################################
# ▸ 3. Fonction rotation
################################
rotate_archives() {
  echo "[INFO] Rotation : garder $BACKUPS_RETENTION_COUNT archives par type"
  for prefix in $(find "$BACKUP_DIR" -type f -name '*.tar.gz' | sed -E 's|.*/([^/]+)-[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{4}.*||' | sort -u); do
    mapfile -t files < <(ls -1t "$BACKUP_DIR/$prefix"-*.tar.gz 2>/dev/null | grep -v important)
    keep=( "${files[@]:0:$BACKUPS_RETENTION_COUNT}" )
    for f in "${files[@]}"; do
      skip=0; for k in "${keep[@]}"; do [[ "$f" == "$k" ]] && skip=1 && break; done
      if (( skip==0 )) && [ -f "$f" ]; then
        sz=$(du -sh "$f" | cut -f1)
        DELETED_BACKUPS+="$(basename "$f") ($sz)\n"
        run_cmd rm -f "$f"
        echo "[INFO] Supprimé : $(basename "$f")"
      fi
    done
  done
  echo "[INFO] Nettoyage des *_important de plus de $IMPORTANT_RETENTION_DAYS jours"
  while IFS= read -r imp; do
    sz=$(du -sh "$imp" | cut -f1)
    DELETED_BACKUPS+="$(basename "$imp") ($sz)\n"
    run_cmd rm -f "$imp"
    echo "[INFO] Supprimé (important expiré) : $(basename "$imp")"
  done < <(find "$BACKUP_DIR" -type f -name '*_important.tar.gz' -mtime +$IMPORTANT_RETENTION_DAYS)
}

########################################
# ▸ 4. Mode rotation uniquement
########################################
if $ROTATE_ONLY; then
  START=$(date +%s)
  rotate_archives
  DUR=$(( ( $(date +%s) - START ) / 60 ))
  FREE=$(df --output=avail -m "$BACKUP_DIR" | tail -n1)
  MSG="Rotation seule terminée\nDurée : ${DUR} min\nLibre : ${FREE} Mo\n\nArchives supprimées :\n${DELETED_BACKUPS:-Aucune}"
  (( MAIL_LEVEL >= 3 )) && echo -e "$MSG" | mail -s "[YunoHost] Rotation terminée" "$MAIL_TO"
  echo "[INFO] Rotation seule terminée à $(date)"
  exit 0
fi

########################################
# ▸ 5. Vérifications pré‑sauvegarde
########################################
run_cmd rm -rf /tmp/yunobackup-* || true
AVAIL=$(df --output=avail -m "$BACKUP_DIR" | tail -n1)
if (( AVAIL < REQUIRED_SPACE_MB )); then
  echo "[ERREUR] Espace insuffisant (${AVAIL} Mo)"; exit 1
fi

mkdir -p "$TEMP_BACKUP_DIR"
START=$(date +%s)

# Helper pour tag _important
need_important() {
  prefix="$1"; month_tag=$(date +%Y-%m)
  ls "$BACKUP_DIR/${prefix}-${month_tag}"*_important.tar.gz &>/dev/null || return 0
  return 1
}

########################################
# ▸ 6. Backup système
########################################
SYS_PREFIX="system"; IMP=""; need_important "$SYS_PREFIX" && IMP="_important"
SYS_NAME="${SYS_PREFIX}-$DATE${IMP}"
run_cmd yunohost backup create --system --name "$SYS_NAME" --output-directory "$TEMP_BACKUP_DIR"
run_cmd mkdir -p "$TEMP_BACKUP_DIR/$SYS_NAME/data/mail"
run_cmd cp -a /var/mail/* "$TEMP_BACKUP_DIR/$SYS_NAME/data/mail/" || true
ARCH="$TEMP_BACKUP_DIR/$SYS_NAME.tar.gz"
if [ -f "$ARCH" ]; then
  DEST="$BACKUP_DIR/$SYS_NAME.tar.gz"
  run_cmd cp "$ARCH" "$DEST"
  run_cmd rm -f "$ARCH"
  run_cmd tar -tzf "$DEST" &>/dev/null && echo "[INFO] Archive OK: $DEST" || { echo "[ERREUR] Archive corrompue $DEST"; run_cmd rm -f "$DEST"; }
fi

########################################
# ▸ 7. Backup applications
########################################
for app in $(yunohost app list --output-as json | jq -r '.apps[] | .id'); do
  IMP=""; need_important "$app" && IMP="_important"
  APP_NAME="${app}-$DATE${IMP}"
  echo "[INFO] Backup app $app"
  run_cmd yunohost backup create --apps "$app" --name "$APP_NAME" --output-directory "$TEMP_BACKUP_DIR"
  ARCH="$TEMP_BACKUP_DIR/$APP_NAME.tar.gz"
  if [ -f "$ARCH" ]; then
    DEST="$BACKUP_DIR/$APP_NAME.tar.gz"
    run_cmd cp "$ARCH" "$DEST"
    run_cmd rm -f "$ARCH"
    if run_cmd tar -tzf "$DEST" &>/dev/null; then
      echo "[INFO] Archive OK: $DEST"
    else
      echo "[ERREUR] Archive corrompue $DEST"; DELETED_BACKUPS+="$(basename "$DEST") (corrompue)\n"; run_cmd rm -f "$DEST"
    fi
  fi
done

run_cmd rm -rf "$TEMP_BACKUP_DIR"

########################################
# ▸ 8. Rotation finale + mail
########################################
rotate_archives
DUR=$(( ( $(date +%s) - START ) / 60 ))
FREE=$(df --output=avail -m "$BACKUP_DIR" | tail -n1)
MSG="Sauvegarde terminée\nDurée : ${DUR} min\nLibre : ${FREE} Mo\n\nArchives supprimées :\n${DELETED_BACKUPS:-Aucune}"
if (( MAIL_LEVEL >= 3 )); then
  if (( MAIL_LEVEL == 5 || MAIL_LEVEL == 4 )); then
    mail -s "[YunoHost] Sauvegarde terminée" -a "$LOG_FILE" "$MAIL_TO" <<< "$MSG"
  else
    echo -e "$MSG" | mail -s "[YunoHost] Sauvegarde terminée" "$MAIL_TO"
  fi
fi

if $TEST_MODE; then
  echo "[TEST] Dry‑run : vérifications OK, aucune sauvegarde lancée.";
else
  echo "[INFO] Terminé à $(date)"
fi

exit 0

Mise en place pas-à-pas

1 . Se connecter au serveur en SSH

ssh [email protected]

Ajoutez -p 2222 si votre port SSH est personnalisé.

2 . Installer les outils nécessaires

sudo apt update
sudo apt install -y jq mailutils cifs-utils

3 . Monter le NAS (ex. via Samba)

sudo mkdir -p /nas/backups/
sudo nano /etc/fstab

Ajoutez :

//IP.DE.VOTRE.NAS/Backups /nas/backups/ cifs uid=root,gid=root,credentials=/root/.nas-creds,iocharset=utf8,vers=3.0 0 0

Puis :

echo -e "username=mon_user_nas\npassword=mon_mot_de_passe" | sudo tee /root/.nas-creds
sudo chmod 600 /root/.nas-creds
sudo mount -a

4 . Créer le fichier de script

sudo nano /usr/local/bin/backup-script.sh

Collez le contenu du script. Enregistrez (Ctrl + O), quittez (Ctrl + X), puis :

sudo chmod +x /usr/local/bin/backup-script.sh

5 . Personnaliser les variables de configuration

En haut du script :

BACKUP_DIR="/nas/backups/"
BACKUPS_RETENTION_COUNT=3
IMPORTANT_RETENTION_DAYS=365
LOG_RETENTION_DAYS=14
MAIL_TO="[email protected]"
MAIL_LEVEL=5
REQUIRED_SPACE_MB=1024

Modifiez selon vos besoins.

Pour le niveau de notification par email (MAIL_LEVEL) :

  • 0: aucun email
  • 1: email en cas d’erreur uniquement
  • 2: email en cas d’erreur, avec log
  • 3: email toujours, sans log
  • 4: email toujours, log en cas d’erreur uniquement
  • 5: email toujours, avec log

6 . Tester (dry-run)

sudo /usr/local/bin/backup-script.sh --test

Vérifiez :

tail -f /var/log/backup-zrx/backup-zrx_$(date +%Y-%m-%d)_*.log

7 . Lancer une sauvegarde réelle

sudo /usr/local/bin/backup-script.sh

8 . Automatiser avec cron

sudo crontab -e

Ajoutez :

0 1 * * * /usr/local/bin/backup-script.sh

Conclusion : mission accomplie !

Bon, cette petite aventure était bien sympa et surtout très instructive. Je suis vraiment content du résultat final. Au moins, maintenant, je suis serein : mes sauvegardes sont régulières, bien rangées, facilement récupérables et sécurisées. La rotation m’assure également de ne pas surcharger mon NAS. Je n’ai plus du tout besoin de gérer les backups à la main. Je peux dormir tranquille !

Et vous alors ? Vous faites comment vos sauvegardes actuellement ? Vous avez déjà eu des mésaventures avec des restaurations foireuses ? Que pensez-vous de l’assistance de l’IA pour la création de scripts comme celui-ci ? Personnellement, je trouve que c’est un game changer.

N’hésitez pas à réagir dans les commentaires, je suis curieux de connaître vos expériences. À très vite !


Newsletter

Recevez ma newsletter mensuelle afin de ne rien rater. Inscrivez-vous ici :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *