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 :