Post

💾 Sauvegarde/Restauration dossier (rsync snapshots + lien current)

💾 Sauvegarde/Restauration dossier (rsync snapshots + lien current)

Sauvegarde/Restauration dossier

Sauvegarde dossier

  • Il vérifie l’espace libre sur /mnt/FreeUSB2To/sauvegardes.
  • Si le seuil est franchi, il envoie une notification ntfy avec priorité haute et s’arrête.
  • Vous recevez donc l’alerte avant que la sauvegarde ne tente d’écrire sur un disque trop plein.

Seuil actuel

Dans le script, la protection est réglée sur :

  • MIN_FREE_GB=20
  • MIN_FREE_PCT=5

Donc l’alerte part si l’espace libre passe sous 20 Go ou sous 5%.

Notification envoyée

Le message d’alerte utilise ntfy avec :

  • Title
  • Priority: high
  • Tags: warning,disk
  • le jeton dans /etc/backup-sharenfs.env.

Alerte plus tôt

Vous pouvez rendre le seuil plus prudent, par exemple :

  • MIN_FREE_GB=50
  • ou MIN_FREE_PCT=10

C’est souvent préférable sur un disque de sauvegarde, surtout si plusieurs snapshots s’accumulent.

Vérification

Pour tester manuellement la logique d’alerte, lancez :

1
sudo /usr/local/sbin/backup-sharenfs.sh

Si l’espace est trop bas, vous recevrez une notification ntfy immédiatement.

Fichier d’environnement ntfy

/etc/backup-sharenfs.env

1
2
NTFY_URL=https://ntfy.rnmkcy.eu/yan_infos
NTFY_TOKEN=tk_9h2bfxjs0pkbuwsnavc6w0wua5t5x

Protection :

1
2
sudo chown root:root /etc/backup-sharenfs.env
sudo chmod 600 /etc/backup-sharenfs.env

Script complet

  • exécution toutes les 30 minutes,
  • lancement manuel,
  • verrou anti double lancement,
  • vérification de montage,
  • vérification d’espace disque,
  • logs,
  • rotation de 30 jours,
  • préservation des ACL/xattrs/liens,
  • option d’exclusions.
  • VERBOSE=1 activable en manuel,
  • jeton ntfy dans /etc/backup-sharenfs.env,
  • current géré comme lien symbolique,
  • suppression correcte de current,
  • progression rsync visible en manuel,
  • mode silencieux pour le timer.

Cette structure suit le schéma classique “snapshots + lien current” utilisé avec rsync.

/usr/local/sbin/backup-sharenfs.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/bin/bash
set -euo pipefail

SRC="/sharenfs/"
BASE="/mnt/backup/sharenfs"
CUR="$BASE/current"
SNAPDIR="$BASE/snapshots"
LOGDIR="$BASE/logs"
EXCLUDE_FILE="/etc/backup-sharenfs.exclude"
ENV_FILE="/etc/backup-sharenfs.env"
LOCK="$BASE/.lock"
DATE="$(date +%F_%H-%M)"
NEW="$SNAPDIR/$DATE"
LOGFILE="$LOGDIR/backup-$DATE.log"

MIN_FREE_GB=20
MIN_FREE_PCT=5
RETENTION_DAYS=30
VERBOSE="${VERBOSE:-0}"

[ -f "$ENV_FILE" ] && . "$ENV_FILE"

notify() {
  local title="$1"
  local prio="$2"
  local tags="$3"
  local msg="$4"

  [ -n "${NTFY_URL:-}" ] && [ -n "${NTFY_TOKEN:-}" ] || return 0

  curl -fsS \
    -H "Title: $title" \
    -H "Authorization: Bearer $NTFY_TOKEN" \
    -H "Priority: $prio" \
    -H "Tags: $tags" \
    --data-binary "$msg" \
    "$NTFY_URL" >/dev/null
}

if ! mountpoint -q /mnt/FreeUSB2To; then
  echo "Destination non montée: /mnt/FreeUSB2To"
  notify "Backup sharenfs" "high" "warning,usb" "Échec: /mnt/FreeUSB2To n'est pas monté."
  exit 1
fi

mkdir -p "$SNAPDIR" "$LOGDIR"

avail_kb="$(df -Pk "$BASE" | awk 'NR==2 {print $4}')"
size_kb="$(df -Pk "$BASE" | awk 'NR==2 {print $2}')"
free_gb=$((avail_kb / 1024 / 1024))
free_pct=$((avail_kb * 100 / size_kb))

if [ "$free_gb" -lt "$MIN_FREE_GB" ] || [ "$free_pct" -lt "$MIN_FREE_PCT" ]; then
  msg="Échec: espace disque insuffisant sur $BASE (${free_gb}G libres, ${free_pct}%)."
  echo "$msg"
  notify "Backup sharenfs" "high" "warning,disk" "$msg"
  exit 1
fi

exec 9>"$LOCK"
flock -n 9 || exit 0

RSYNC_OPTS=(
  -aHAX
  --numeric-ids
  --delete
  --delete-delay
  --partial
  --partial-dir=.rsync-partial
  --human-readable
  --info=progress2
  --info=stats2
)

if [ -f "$EXCLUDE_FILE" ]; then
  RSYNC_OPTS+=(--exclude-from="$EXCLUDE_FILE")
fi

run_backup() {
  echo "=== Backup start: $(date -Is) ==="

  if [ -e "$CUR" ]; then
    if [ -L "$CUR" ] || [ -f "$CUR" ]; then
      rm -f "$CUR"
    elif [ -d "$CUR" ]; then
      rm -rf "$CUR"
    fi
  fi

  if [ -d "$CUR" ] && [ "$(find "$CUR" -mindepth 1 -maxdepth 1 | wc -l)" -gt 0 ]; then
    mkdir -p "$NEW"
    rsync "${RSYNC_OPTS[@]}" --link-dest="$CUR" "$SRC" "$NEW/"
  else
    mkdir -p "$NEW"
    rsync "${RSYNC_OPTS[@]}" "$SRC" "$NEW/"
  fi

  rm -f "$CUR"
  ln -s "$NEW" "$CUR"

  find "$SNAPDIR" -mindepth 1 -maxdepth 1 -type d -mtime +"$RETENTION_DAYS" -exec rm -rf {} +

  echo "=== Backup end: $(date -Is) ==="
}

if [ "$VERBOSE" = "1" ]; then
  run_backup 2>&1 | tee "$LOGFILE"
else
  run_backup >>"$LOGFILE" 2>&1
fi

notify "Backup sharenfs" "low" "information_source,backup" "Succès: sauvegarde terminée à $(date -Is). Snapshot: $DATE."

Puis :

1
2
sudo chmod 750 /usr/local/sbin/backup-sharenfs.sh
sudo chown root:root /usr/local/sbin/backup-sharenfs.sh

Service systemd

/etc/systemd/system/backup-sharenfs.service

1
2
3
4
5
6
7
8
[Unit]
Description=Backup /sharenfs to local USB snapshots
RequiresMountsFor=/mnt/backup/sharenfs

[Service]
Type=oneshot
EnvironmentFile=/etc/backup-sharenfs.env
ExecStart=/usr/local/sbin/backup-sharenfs.sh

Timer 30 minutes

/etc/systemd/system/backup-sharenfs.timer

1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=Run /sharenfs backup every 30 minutes

[Timer]
OnBootSec=5min
OnUnitActiveSec=30min
Persistent=true
Unit=backup-sharenfs.service

[Install]
WantedBy=timers.target

Activation :

1
2
sudo systemctl daemon-reload
sudo systemctl enable --now backup-sharenfs.timer

Usage

Manuel, avec affichage de progression :

1
sudo VERBOSE=1 /usr/local/sbin/backup-sharenfs.sh

Manuel, silencieux :

1
sudo /usr/local/sbin/backup-sharenfs.sh

Vérifier le lien current :

1
ls -l /mnt/backup/sharenfs/current

Vous devez voir un lien du type :

1
current -> /mnt/backup/sharenfs/snapshots/2026-06-27_11-29

Test ntfy séparé

1
2
3
4
5
6
7
8
source /etc/backup-sharenfs.env
curl \
  -H "Title: Information!" \
  -H "Authorization: Bearer $NTFY_TOKEN" \
  -H "Priority: low" \
  -H "Tags: information_source" \
  -d "Notification utilisant un jeton d'accès" \
  "$NTFY_URL"

Restauration dossier

  1. Vérifiez le snapshot à restaurer.
  2. Arrêtez tout service qui écrit dans /sharenfs si nécessaire.
  3. Copiez le snapshot vers la destination avec rsync.
  4. Contrôlez les droits, ACL et fichiers cachés.
  5. Relancez les services concernés.

Vérifier le snapshot disponible

1
2
ls -l /mnt/FreeUSB2To/sauvegardes/current
ls /mnt/FreeUSB2To/sauvegardes/snapshots

Arrêter les écritures si nécessaire

Si /sharenfs est utilisé par un service, arrêtez-le avant restauration.

Restaurer le dernier état

1
sudo rsync -aHAX /mnt/FreeUSB2To/sauvegardes/current/ /sharenfs/

Restaurer un snapshot précis

Remplacez la date par le snapshot voulu :

1
sudo rsync -aHAX /mnt/FreeUSB2To/sauvegardes/snapshots/2026-06-27_11-29/ /sharenfs/

Restaurer un sous-dossier

1
sudo rsync -aHAX /mnt/FreeUSB2To/sauvegardes/current/mon_dossier/ /sharenfs/mon_dossier/

Restaurer un seul fichier

1
sudo cp -a /mnt/FreeUSB2To/sauvegardes/current/chemin/du/fichier /sharenfs/chemin/du/fichier

Contrôler le résultat

1
2
find /sharenfs | head
df -h /mnt/FreeUSB2To

Relancer les services

Redémarrez ensuite les services qui utilisent /sharenfs.

Test avant remise en production

Pour éviter toute erreur, restaurez d’abord dans un dossier temporaire :

1
2
3
sudo mkdir -p /tmp/restore-test
sudo rsync -aHAX /mnt/FreeUSB2To/sauvegardes/current/ /tmp/restore-test/
ls -l /tmp/restore-test

Point important

current doit être un lien symbolique vers le dernier snapshot, pas un dossier réel. Si besoin, vérifiez avec :

1
ls -l /mnt/FreeUSB2To/sauvegardes/current

Mémo urgence

Voici le mémo d’urgence en 5 lignes :

1
2
3
4
5
sudo systemctl stop service-utilisant-sharenfs
sudo rsync -aHAX /mnt/FreeUSB2To/sauvegardes/current/ /sharenfs/
ls -l /mnt/FreeUSB2To/sauvegardes/current
find /sharenfs | head
sudo systemctl start service-utilisant-sharenfs

Pour un snapshot précis, remplacez current/ par snapshots/AAAA-MM-JJ_HH-MM/.

Cet article est sous licence CC BY 4.0 par l'auteur.