Sauvegarde régulière de la config docker et de mes scripts

Author: Unknown

Date: 19/07/2025

J'ai commencé à faire pas mal de choses, et je me voudrais de tout perdre si le serveur a un problème. Dans un premier temps, je vais déjà faire une sauvegarde de ce que je fais sur le disque dur externe USB branché sur mon serveur, avec des sauvegardes incrémentales permettant de remonter à un an avec une précision de un mois et sur une semaine avec une précision de un jour.

Élaboration du script

Histoire d'alléger l'écriture du script, et d'éviter de passer à côté d'une erreur crachée sur la sortie standard par oubli d'un rajout de 2>&1, je rajoute une redirection globale au début de mon script:

    mkdir -p "$LOGDIR"
    exec >> "$LOGFILE" 2>&1

Et comme c'est plus pratique de tout avoir en sortie à l'écran pendant qu'on élabore le script, je rajoute un mode debug:

# Mode debug: 1 pour activer, 0 pour désactiver
DEBUG_MODE=0

if [ "$DEBUG_MODE" -eq 0 ]
then
    # En mode normal, tout dans le fichier uniquement
    mkdir -p "$LOGDIR"
    exec >> "$LOGFILE" 2>&1
fi

Je crée une fonction de log pour bien logguer de manière homogène, avec le nom du script et la date/heure.

# Fonction de log
function log {
    echo "$(date '+%Y%m%d.%H%M%S')|$0|$1"
}

Enfin, je me crée une fonction qui permet de vérifier le code erreur de la dernière commande, et log différemment suivant qu'il s'agit d'un échec ou d'un succès:

# Fonction pour vérifier le statut
function check_status {
    local status=$1
    local msg_success=$2
    local msg_failure=$3

    if [ "$status" -eq 0 ]; then
        log "$msg_success"
        return 0
    else
        log "$msg_failure|error code=$status"
        exit "$status"
    fi
}

Fonction qui s'appelle par exemple ainsi:

rsync avec_plein_de_paramètres
exit_code=$?
check_status $exit_code "Sync for $SUBDIR completed successfully" "Sync for $SUBDIR failed with exit code $exit_code"

La gestion incrémentale est un peu bourrin, mais au moins c'est compréhensible:

   # Décalage des sauvegardes
    if [ "$(date '+%d')" -eq 1 ]
    then
        log "Monthly backup"
        rm -fr $DEST/monthly.11/$SUBDIR
        mv $DEST/monthly.10/$SUBDIR $DEST/monthly.11/$SUBDIR
        mv $DEST/monthly.09/$SUBDIR $DEST/monthly.11/$SUBDIR
        mv $DEST/monthly.08/$SUBDIR $DEST/monthly.09/$SUBDIR
        mv $DEST/monthly.07/$SUBDIR $DEST/monthly.08/$SUBDIR
        mv $DEST/monthly.06/$SUBDIR $DEST/monthly.07/$SUBDIR
        mv $DEST/monthly.05/$SUBDIR $DEST/monthly.06/$SUBDIR
        mv $DEST/monthly.04/$SUBDIR $DEST/monthly.05/$SUBDIR
        mv $DEST/monthly.03/$SUBDIR $DEST/monthly.04/$SUBDIR
        mv $DEST/monthly.02/$SUBDIR $DEST/monthly.03/$SUBDIR
        mv $DEST/monthly.01/$SUBDIR $DEST/monthly.02/$SUBDIR
        mv $DEST/monthly.00/$SUBDIR $DEST/monthly.01/$SUBDIR
        mv $DEST/daily.7/$SUBDIR $DEST/monthly.00/$SUBDIR
    else
        rm -fr $DEST/daily.7/$SUBDIR
    fi
    mv $DEST/daily.6/$SUBDIR $DEST/daily.7/$SUBDIR
    mv $DEST/daily.5/$SUBDIR $DEST/daily.6/$SUBDIR
    mv $DEST/daily.4/$SUBDIR $DEST/daily.5/$SUBDIR
    mv $DEST/daily.3/$SUBDIR $DEST/daily.4/$SUBDIR
    mv $DEST/daily.2/$SUBDIR $DEST/daily.3/$SUBDIR
    mv $DEST/daily.1/$SUBDIR $DEST/daily.2/$SUBDIR
    mv $DEST/daily.0/$SUBDIR $DEST/daily.1/$SUBDIR

Évidemment, il faut qu'aucune erreur n'arrive, sinon le script s'arrête. Donc, impossible d'avoir des "erreurs normales". Dans mon cas, le rsync -a --delete --link-dest=$DEST/daily.1/$SUBDIR /home/user/$SUBDIR $DEST/daily.0/$SUBDIR n'arrivait pas à copier les fichiers de clés pour des problèmes de droit. Ces fichiers de clé étant générés par letsencrypt, j'en aurais besoin pour un backup de type PRA, mais là, il s'agit juste d'éviter de repartir à zéro, donc je peux m'en passer. L'ajout de --exclude='\*.pem' permet de résoudre le problème. L'argument --link-dest demande de faire des hardlink quand le fichier n'a pas changé. Cela permet d'éviter de faire de perdre de l'espace disque inutilement.

Le script final:

#!/bin/bash

# Définir directement le nom du fichier de log avec l'année et le mois
LOGDIR="/var/log/backup"
LOGFILE="$LOGDIR/backup_$(date '+%Y-%m').log"

# Mode debug: 1 pour activer, 0 pour désactiver
DEBUG_MODE=0

# Fonction de log
function log {
    echo "$(date '+%Y%m%d.%H%M%S')|$0|$1"
}

# Fonction pour vérifier le statut
function check_status {
    local status=$1
    local msg_success=$2
    local msg_failure=$3

    if [ "$status" -eq 0 ]; then
        log "$msg_success"
        return 0
    else
        log "$msg_failure|error code=$status"
        exit "$status"
    fi
}

if [ "$DEBUG_MODE" -eq 0 ]
then
    # En mode normal, tout dans le fichier uniquement
    mkdir -p "$LOGDIR"
    exec >> "$LOGFILE" 2>&1
fi

# Début du script
log ""
log "##################################################################"
log "Script: $0"
log "Arguments: $@"
log "Started"
global_exit_code=0

# Exécuter rsync et journaliser les sorties et les erreurs                                                                                                                                                       DEST=/mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user
mkdir -p $DEST/daily.0 $DEST/daily.1 $DEST/daily.2 $DEST/daily.3 $DEST/daily.4 $DEST/daily.5 $DEST/daily.6 $DEST/daily.7
mkdir -p $DEST/monthly.00 $DEST/monthly.01 $DEST/monthly.02 $DEST/monthly.03 $DEST/monthly.04 $DEST/monthly.05 $DEST/monthly.06 $DEST/monthly.07 $DEST/monthly.08 $DEST/monthly.09 $DEST/monthly.10 $DEST/monthly.11
for SUBDIR in docker noteblogger scripts
do
    # Décalage des sauvegardes
    if [ "$(date '+%d')" -eq 1 ]
    then
        log "Monthly backup"
        rm -fr $DEST/monthly.11/$SUBDIR
        mv $DEST/monthly.10/$SUBDIR $DEST/monthly.11/$SUBDIR
        mv $DEST/monthly.09/$SUBDIR $DEST/monthly.11/$SUBDIR
        mv $DEST/monthly.08/$SUBDIR $DEST/monthly.09/$SUBDIR
        mv $DEST/monthly.07/$SUBDIR $DEST/monthly.08/$SUBDIR
        mv $DEST/monthly.06/$SUBDIR $DEST/monthly.07/$SUBDIR
        mv $DEST/monthly.05/$SUBDIR $DEST/monthly.06/$SUBDIR
        mv $DEST/monthly.04/$SUBDIR $DEST/monthly.05/$SUBDIR
        mv $DEST/monthly.03/$SUBDIR $DEST/monthly.04/$SUBDIR
        mv $DEST/monthly.02/$SUBDIR $DEST/monthly.03/$SUBDIR
        mv $DEST/monthly.01/$SUBDIR $DEST/monthly.02/$SUBDIR
        mv $DEST/monthly.00/$SUBDIR $DEST/monthly.01/$SUBDIR
        mv $DEST/daily.7/$SUBDIR $DEST/monthly.00/$SUBDIR
    else
        rm -fr $DEST/daily.7/$SUBDIR
    fi
    mv $DEST/daily.6/$SUBDIR $DEST/daily.7/$SUBDIR
    mv $DEST/daily.5/$SUBDIR $DEST/daily.6/$SUBDIR
    mv $DEST/daily.4/$SUBDIR $DEST/daily.5/$SUBDIR
    mv $DEST/daily.3/$SUBDIR $DEST/daily.4/$SUBDIR
    mv $DEST/daily.2/$SUBDIR $DEST/daily.3/$SUBDIR
    mv $DEST/daily.1/$SUBDIR $DEST/daily.2/$SUBDIR
    mv $DEST/daily.0/$SUBDIR $DEST/daily.1/$SUBDIR
    log "rsync -a --delete --exclude='*.pem' --link-dest=$DEST/daily.1/$SUBDIR /home/user/$SUBDIR $DEST/daily.0/$SUBDIR"
    rsync -a --delete --exclude='*.pem' --link-dest=$DEST/daily.1/$SUBDIR /home/user/$SUBDIR $DEST/daily.0/$SUBDIR
    exit_code=$?
    # Vérifier le code de sortie de rsync pour déterminer le succès ou l'échec
    check_status $exit_code "Sync for $SUBDIR completed successfully" "Sync for $SUBDIR failed with exit code $exit_code"
done

log "Sync completed successfully"
log "End"
log "##################################################################"
log ""

Tout le reste à faire autour du script

Script de synchro

  • Tout d'abord:
cd
mkdir -p scripts
cd scripts
vi backup.bash

Synchro en crontab

  • crontab -e
  • 56 20 * * * /home/user/scripts/backup.bash

Vérification

Quelques jours plus tard, je vérifie et le job n'a pas l'air d'avoir tourné. Le log du script /var/log/backup n'a pas bougé d'un poil. Pourtant, le fichier de log a les mêmes droits que celui dans /var/log/sync_nas, où tout se passe bien. Au passage, je découvreque le montage de /mnt/nas_pctv_ro n'est plus là. Encore un truc à vérifier régulièrement via un traitement dédié. Un coup d'oeil dans /var/log/syslog montre que la crontab a bien lancé le script:

user@lnxsrv:/var/log$ grep backup.bash syslog
2025-07-18T20:56:01.973478+00:00 lnxsrv CRON[132139]: (user) CMD (/home/user/scripts/backup.bash)

Je change la périodicité de la crontab pour essayer de mieux comprendre: 0,5,10,15,20,25,30,35,40,45,50,55 * * * * /home/user/scripts/backup.bash J'essaye de comparer le script qui fonctionne et celui qui ne fonctionne pas:

user@lnxsrv:/var/log$ ls -ald sync_nas backup
drwxrwxrwx 2 root root 4096 juil. 17 22:02 backup
drwxrwxrwx 2 root root 4096 juil. 12 03:33 sync_nas
user@lnxsrv:/var/log$ ls -al sync_nas backup
backup:
total 12
drwxrwxrwx  2 root root   4096 juil. 17 22:02 .
drwxrwxr-x 12 root syslog 4096 juil. 17 21:05 ..
-rw-rw-r--  1 user user   3409 juil. 17 22:03 backup_2025-07.log

sync_nas:
total 152
drwxrwxrwx  2 root root     4096 juil. 12 03:33 .
drwxrwxr-x 12 root syslog   4096 juil. 17 21:05 ..
-rw-rw-r--  1 user user   110293 juin  30 03:38 sync_nas_2025-06.log
-rw-rw-r--  1 user user    25390 juil. 19 03:33 sync_nas_2025-07.log

Côté droits, cela parait identique. Je vois quand même que le fichier de log date du 17 juillet à 22;03 alors que syslog indique que /home/user/scripts/backup.bash a été lancé le 18 juillet à 20h56. je vérifie s'il n'y a pas un problème de droits au niveau des scripts:

user@lnxsrv:/var/log$ ls -l /home/user/scripts/synchro_pctv_personnel_to_local.bash /home/user/scripts/backup.bash
ls: cannot access '/home/user/scripts/backup.bash': No such file or directory
-rwxrwxr-x 1 user user 1936 juin  20 16:25 /home/user/scripts/synchro_pctv_personnel_to_local.bash

OK, PEBCAK, je me suis planté dans le nom du script. Je change le suffixe de mon script:

user@lnxsrv:/var/log$ mv /home/user/scripts/backup.sh /home/user/scripts/backup.bash

Cette fois-ci, cela fonctionne correctement:

user@lnxsrv:/var/log$ ll backup/backup_2025-07.log
-rw-rw-r-- 1 user user 5310 juil. 19 12:30 backup/backup_2025-07.log
user@lnxsrv:/var/log$ tail backup/backup_2025-07.log
20250719.123001|/home/user/scripts/backup.bash|Sync for docker completed successfully
20250719.123001|/home/user/scripts/backup.bash|rsync -a --delete --exclude='*.pem' --link-dest=/mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user/daily.1/noteblogger /home/user/noteblogger /mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user/daily.0/noteblogger
20250719.123001|/home/user/scripts/backup.bash|Sync for noteblogger completed successfully
20250719.123001|/home/user/scripts/backup.bash|rsync -a --delete --exclude='*.pem' --link-dest=/mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user/daily.1/scripts /home/user/scripts /mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user/daily.0/scripts
20250719.123001|/home/user/scripts/backup.bash|Sync for scripts completed successfully
20250719.123001|/home/user/scripts/backup.bash|Sync completed successfully
20250719.123001|/home/user/scripts/backup.bash|End
20250719.123001|/home/user/scripts/backup.bash|##################################################################
20250719.123001|/home/user/scripts/backup.bash|##################################################################
20250719.123001|/home/user/scripts/backup.bash|

Re re vérification

Au passage suivant de la crontab, après avoir en plus modifié un des éléments sauvegardés, j'ai bien des jolis logs:

user@lnxsrv:/var/log$ tail -15 /var/log/backup/backup_2025-07.log
20250719.124001|/home/user/scripts/backup.bash|
20250719.124001|/home/user/scripts/backup.bash|##################################################################
20250719.124001|/home/user/scripts/backup.bash|Script: /home/user/scripts/backup.bash
20250719.124001|/home/user/scripts/backup.bash|Arguments:
20250719.124001|/home/user/scripts/backup.bash|Started
20250719.124001|/home/user/scripts/backup.bash|rsync -a --delete --exclude='*.pem' --link-dest=/mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user/daily.1/docker /home/user/docker /mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user/daily.0/docker
20250719.124001|/home/user/scripts/backup.bash|Sync for docker completed successfully
20250719.124001|/home/user/scripts/backup.bash|rsync -a --delete --exclude='*.pem' --link-dest=/mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user/daily.1/noteblogger /home/user/noteblogger /mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user/daily.0/noteblogger
20250719.124001|/home/user/scripts/backup.bash|Sync for noteblogger completed successfully
20250719.124001|/home/user/scripts/backup.bash|rsync -a --delete --exclude='*.pem' --link-dest=/mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user/daily.1/scripts /home/user/scripts /mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user/daily.0/scripts
20250719.124001|/home/user/scripts/backup.bash|Sync for scripts completed successfully
20250719.124001|/home/user/scripts/backup.bash|Sync completed successfully
20250719.124001|/home/user/scripts/backup.bash|End
20250719.124001|/home/user/scripts/backup.bash|##################################################################
20250719.124001|/home/user/scripts/backup.bash|

J'aime bien ces lignes de ################################# cela rappelle quand je faisais cela gamin dans les commentaires de mes programmes en GFA Basic sur Atari ;-) Et la sauvegarde incrémentale me permet bien de voir ma modification:

user@lnxsrv:/mnt/linux/PERSONNEL/SERVER/backup/lnxsrv/home_user$ diff -r daily.0/scripts/ daily.1/scripts/
diff -r daily.0/scripts/scripts/backup.bash daily.1/scripts/scripts/backup.bash
39a40
> log "##################################################################"
80d80
<     exit_code=$?
82c82
<     check_status $exit_code "Sync for $SUBDIR completed successfully" "Sync for $SUBDIR failed with exit code $exit_code"
---
>     check_status $? "Sync for $SUBDIR completed successfully" "Sync for $SUBDIR failed with exit code $exit_code"
86a87
> log "##################################################################"

Améliorations possibles

La rotation journalière est en fait une rotation à chaque exécution. Elle n'est journalière que si le script n'est exécutée qu'une fois par jour.

La suite:

J'aimerais également:

  • une génération régulière des mes blogs
  • remonter le disque dur externe si démonté-je-ne-sais-pas-pourquoi
  • remonter le partage du nas si démonté-je-ne-sais-pas-pourquoi
  • un redémarrage automatique des containers en cas de reboot du serveur
  • un suivi / admin web docker (https://github.com/henrygd/beszel?tab=readme-ov-file?)
  • une centralisation des logs apaches docker et les interroger via Matomo
  • installer une gallerie photo++ style photoprism
  • une centralisation des logs docker et les interroger via un ELK
  • une centralisation des logs locaux et les interroger via un ELK
  • vérifier que la sauvegarde mensuelle fonctionne
  • une centralisation de certains logs distants et les interroger via un ELK
  • sortir les éventuels password des fichiers de configuration

Tags: computing ubuntu serveur-bayart back2code informatique article mon-serveur-à-moi-qui-me-va-bien