Ajouter la sauvegarde automatique sur clé USB
Dès qu'on branche la clé déclarée (reconnue par son UUID), udev demande à systemd de lancer une sauvegarde rsync des dossiers choisis. Ce qui a disparu de l'ordinateur est retiré de la clé, mais conservé 30 jours dans une corbeille datée sur la clé. Notifications de bureau, journal systemd, garde-fous contre une destination erronée. Installateur, tests sans sudo et mode d'emploi fournis. Refs #1
This commit is contained in:
parent
9f65bc807b
commit
a04a72054a
8 changed files with 1164 additions and 0 deletions
|
|
@ -10,6 +10,9 @@ Les évolutions du site des présentations, de la plus récente à la plus ancie
|
|||
et conducteur dans `slides/sauvegardes/` (#1).
|
||||
- « Ranger et sauvegarder » : une diapo propose une arborescence type pour le dossier
|
||||
Documents, et une diapo facultative présente la copie automatique sur clé USB.
|
||||
- Nouveau script `slides/sauvegardes/script/` : sauvegarde rsync automatique dès
|
||||
qu'on branche une clé USB déclarée, avec corbeille de 30 jours sur la clé
|
||||
(installateur, tests, mode d'emploi dans `LISEZMOI.md`).
|
||||
- Ajout de cette page des nouveautés.
|
||||
|
||||
## 10 octobre 2026
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
# /etc/udev/rules.d/99-sauvegarde-cle-usb.rules
|
||||
# Écrit par installer.sh (@UUID@ est remplacé par l'UUID de la clé).
|
||||
# Quand la partition portant cet UUID apparaît, udev demande à systemd de
|
||||
# démarrer le service de sauvegarde. Les autres clés ne déclenchent rien.
|
||||
ACTION=="add", SUBSYSTEM=="block", ENV{ID_FS_UUID}=="@UUID@", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sauvegarde-cle-usb.service"
|
||||
228
slides/sauvegardes/script/LISEZMOI.md
Normal file
228
slides/sauvegardes/script/LISEZMOI.md
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
# Sauvegarde automatique sur clé USB
|
||||
|
||||
Petit outil pour **Linux Mint 22** (Cinnamon) : on branche **sa** clé de
|
||||
sauvegarde, la copie de ses documents démarre toute seule, une notification
|
||||
dit quand on peut la retirer.
|
||||
|
||||
Accompagne la séance Alpinux « Ranger et sauvegarder ses documents ».
|
||||
|
||||
## Sommaire
|
||||
|
||||
| Pour commencer | Au quotidien | En cas de besoin |
|
||||
|---|---|---|
|
||||
| [Pour qui ?](#pour-qui) | [Tester](#tester) | [Restaurer un fichier](#restaurer-un-fichier) |
|
||||
| [Ce que ça fait](#ce-que-ça-fait) | [Lire le journal](#lire-le-journal) | [Désinstaller](#désinstaller) |
|
||||
| [Installer (3 commandes)](#installer-en-3-commandes) | [Régler](#régler) | [Limites](#limites-à-connaître) |
|
||||
| [Trouver l'UUID](#trouver-luuid-de-sa-clé) | | [Comment ça marche](#comment-ça-marche) |
|
||||
|
||||
## Pour qui ?
|
||||
|
||||
Pour qui veut une copie de ses documents **sans y penser** : on branche la
|
||||
clé, on attend le message, on la débranche. Il faut savoir ouvrir un
|
||||
terminal et taper une commande avec `sudo` (une fois, pour l'installation).
|
||||
|
||||
## Ce que ça fait
|
||||
|
||||
- Seule **la clé déclarée** déclenche la sauvegarde (reconnue par son
|
||||
identifiant UUID). Une autre clé ne fait rien.
|
||||
- Le dossier personnel (ou une liste de dossiers) est copié dans
|
||||
`Sauvegarde/` sur la clé. Les fois suivantes, seul ce qui a changé est
|
||||
recopié : c'est rapide.
|
||||
- Ce qui a été **supprimé** de l'ordinateur est retiré de la clé, pour que la
|
||||
copie reste fidèle.
|
||||
|
||||
> **Synchroniser n'est pas sauvegarder.** Une copie « miroir » recopie aussi
|
||||
> les erreurs : un fichier effacé par mégarde disparaîtrait de la clé au
|
||||
> branchement suivant. C'est pourquoi, par défaut, **rien n'est effacé d'un
|
||||
> coup** : ce qui est retiré ou remplacé sur la clé est d'abord mis de côté
|
||||
> dans `.corbeille/AAAA-MM-JJ_HHMMSS/`, et gardé **30 jours**.
|
||||
|
||||
- Notifications : « Sauvegarde en cours… », puis « Sauvegarde terminée,
|
||||
vous pouvez retirer la clé », ou un message d'erreur (clé pleine…).
|
||||
- Un fichier `derniere-sauvegarde.txt` à la racine de la clé indique la date
|
||||
et le résultat de la dernière sauvegarde.
|
||||
- Ne sont pas copiés : caches (`.cache`), corbeille de l'ordinateur,
|
||||
miniatures, fichiers temporaires. `Téléchargements` peut être exclu.
|
||||
|
||||
## Installer en 3 commandes
|
||||
|
||||
Branchez d'abord la clé de sauvegarde, puis dans un terminal :
|
||||
|
||||
```bash
|
||||
git clone --depth 1 https://gitea.alpinux.org/alpinux.cedrica5l/alpinux.presentations.git
|
||||
cd alpinux.presentations/slides/sauvegardes/script
|
||||
sudo ./installer.sh
|
||||
```
|
||||
|
||||
(Si `git` manque : `sudo apt install git`.)
|
||||
|
||||
L'installateur affiche les clés USB branchées, demande laquelle utiliser et
|
||||
pour quel utilisateur, puis met tout en place. On peut le relancer sans
|
||||
risque (changer de clé, mettre à jour) : vos réglages sont conservés.
|
||||
|
||||
Ensuite : **débranchez et rebranchez la clé**, la première sauvegarde part.
|
||||
Elle peut être longue ; les suivantes sont rapides.
|
||||
|
||||
Sans questions : `sudo ./installer.sh --uuid E234-70AF --utilisateur marie`
|
||||
|
||||
## Trouver l'UUID de sa clé
|
||||
|
||||
L'installateur le fait pour vous. Pour le voir vous-même :
|
||||
|
||||
```bash
|
||||
lsblk -o NAME,LABEL,UUID,FSTYPE,SIZE,TRAN
|
||||
```
|
||||
|
||||
Repérez votre clé (colonne `TRAN` = `usb`, taille, nom `LABEL`) : l'UUID est
|
||||
sur la ligne de la **partition** (`sdb1`…), par exemple `E234-70AF` pour une
|
||||
clé FAT, ou `3d6f976c-1822-…` pour une clé ext4.
|
||||
Attention : **reformater la clé change son UUID** ; relancez alors
|
||||
`sudo ./installer.sh`.
|
||||
|
||||
## Tester
|
||||
|
||||
```bash
|
||||
sudo sauvegarde-cle-usb --simulation # montre ce qui serait copié/supprimé, sans rien faire
|
||||
sudo systemctl start sauvegarde-cle-usb # lance une vraie sauvegarde maintenant
|
||||
```
|
||||
|
||||
Pour vérifier la logique sans clé ni `sudo` (développeurs) :
|
||||
`./tester-sans-sudo.sh` — un dossier temporaire joue le rôle de la clé.
|
||||
|
||||
## Lire le journal
|
||||
|
||||
```bash
|
||||
journalctl -u sauvegarde-cle-usb # tout l'historique
|
||||
journalctl -u sauvegarde-cle-usb -b # depuis le démarrage
|
||||
journalctl -u sauvegarde-cle-usb -f # en direct, pendant une sauvegarde
|
||||
```
|
||||
|
||||
Ou plus simplement, ouvrez `derniere-sauvegarde.txt` sur la clé.
|
||||
|
||||
## Restaurer un fichier
|
||||
|
||||
**Un fichier qui existe encore dans la sauvegarde** : ouvrez la clé dans le
|
||||
gestionnaire de fichiers, dossier `Sauvegarde/<votre nom>/…`, et copiez le
|
||||
fichier où vous voulez (copier, pas déplacer).
|
||||
|
||||
**Un fichier supprimé ou une ancienne version** : il est dans la corbeille de
|
||||
la clé, un dossier caché. Dans le gestionnaire de fichiers, appuyez sur
|
||||
**Ctrl+H** pour voir les dossiers cachés, puis ouvrez `.corbeille/`. Chaque
|
||||
sous-dossier porte la date de la sauvegarde où le fichier a été retiré
|
||||
(ex. `2026-11-05_183012`) et reprend l'arborescence de vos dossiers.
|
||||
|
||||
Pour chercher un fichier par son nom :
|
||||
|
||||
```bash
|
||||
find /media/$USER/*/.corbeille -iname '*lettre*'
|
||||
```
|
||||
|
||||
**Tout restaurer** (nouvel ordinateur…) : copiez le contenu de
|
||||
`Sauvegarde/<votre nom>/` dans votre dossier personnel.
|
||||
|
||||
## Régler
|
||||
|
||||
Tout est dans `/etc/sauvegarde-cle-usb.conf`, commenté :
|
||||
`sudo xed /etc/sauvegarde-cle-usb.conf`
|
||||
|
||||
| Réglage | Rôle | Par défaut |
|
||||
|---|---|---|
|
||||
| `SOURCES` | dossiers à sauvegarder | le dossier personnel |
|
||||
| `DOSSIER_DESTINATION` | dossier sur la clé | `Sauvegarde` |
|
||||
| `CORBEILLE_JOURS` | durée de conservation de `.corbeille` ; `0` = miroir pur, sans filet (déconseillé) | `30` |
|
||||
| `EXCLUSIONS` | ce qu'on ne copie pas (enlever le `#` devant `Téléchargements/` pour l'exclure) | caches, corbeille, temporaires |
|
||||
| `VERIFIER_ESPACE` | vérifier la place libre avant de commencer | `oui` |
|
||||
|
||||
Pris en compte au prochain branchement.
|
||||
|
||||
## Désinstaller
|
||||
|
||||
```bash
|
||||
sudo ./installer.sh --desinstaller
|
||||
```
|
||||
|
||||
Retire le script, le service, la règle et la configuration. Les copies
|
||||
présentes sur la clé ne sont pas touchées.
|
||||
|
||||
## Limites à connaître
|
||||
|
||||
- **Une clé n'est qu'une copie, et elle reste à la maison.** Un cambriolage
|
||||
ou un dégât des eaux emporte l'ordinateur *et* la clé. Gardez aussi une
|
||||
copie **ailleurs** (une 2ᵉ clé chez un proche, au travail, ou en ligne).
|
||||
- **Attendez la notification** « Sauvegarde terminée » avant de retirer la
|
||||
clé. Retirée en cours de route, la copie est incomplète (elle se
|
||||
complétera au branchement suivant).
|
||||
- **Clé FAT32 / exFAT / NTFS** (les clés vendues en magasin) : les documents
|
||||
sont bien copiés, mais pas les droits Linux ni les liens symboliques
|
||||
(ignorés, ce n'est pas une erreur). En **FAT32**, pas de fichier de plus de
|
||||
**4 Go** (signalé par une notification) et certains caractères sont interdits
|
||||
dans les noms (`: * ? " < > | \`) : ces fichiers-là ne sont pas copiés, la
|
||||
sauvegarde est dite « avec avertissements » et, par prudence, rien n'est
|
||||
supprimé de la clé ce jour-là. Pour une copie fidèle, formatez la clé en
|
||||
**ext4** (Disques ou Formater dans le menu de la clé) — elle ne sera plus
|
||||
lisible sous Windows.
|
||||
- **Papiers sensibles** (impôts, banque, santé, pièces d'identité) : une clé
|
||||
se perd facilement. **Chiffrez-la** (Disques → Formater → « Protégé par mot
|
||||
de passe (LUKS) »). Après le déverrouillage, le démarrage automatique n'est
|
||||
pas garanti : lancez alors `sudo systemctl start sauvegarde-cle-usb`, et
|
||||
choisissez dans l'installateur la ligne `crypt` (clé déverrouillée).
|
||||
- La corbeille de la clé prend de la place. Si la clé est pleine, supprimez
|
||||
les plus vieux dossiers de `.corbeille/`.
|
||||
- Les fichiers en cours d'écriture pendant la copie (base de données de
|
||||
Thunderbird ou Firefox ouverts…) peuvent être copiés dans un état
|
||||
incohérent : fermez ces logiciels pour une copie parfaite.
|
||||
- Si la clé est branchée au démarrage de l'ordinateur, la sauvegarde part
|
||||
aussitôt, avant même la connexion (sans notification).
|
||||
|
||||
## Comment ça marche
|
||||
|
||||
```
|
||||
clé branchée ─► udev reconnaît l'UUID ─► systemd lance sauvegarde-cle-usb.service
|
||||
─► le script monte la clé sur /run/sauvegarde-cle-usb
|
||||
─► vérifications ─► rsync ─► purge des vieilles corbeilles
|
||||
─► sync, démontage ─► notification « vous pouvez retirer la clé »
|
||||
```
|
||||
|
||||
| Fichier (dans ce dossier) | Installé dans |
|
||||
|---|---|
|
||||
| `sauvegarde-cle-usb` (le script) | `/usr/local/sbin/` |
|
||||
| `sauvegarde-cle-usb.conf` (modèle) | `/etc/sauvegarde-cle-usb.conf` |
|
||||
| `sauvegarde-cle-usb.service` | `/etc/systemd/system/` |
|
||||
| `99-sauvegarde-cle-usb.rules.modele` | `/etc/udev/rules.d/99-sauvegarde-cle-usb.rules` |
|
||||
| `installer.sh`, `tester-sans-sudo.sh` | (non installés) |
|
||||
|
||||
**Pourquoi udev + systemd ?** udev voit la clé arriver, mais ne doit pas
|
||||
lancer lui-même une tâche longue (il la tuerait au bout de quelques
|
||||
secondes) : il demande donc à systemd de démarrer le service, qui tourne
|
||||
aussi longtemps que nécessaire et écrit dans le journal.
|
||||
|
||||
**Pourquoi un montage dédié ?** Quand on branche la clé, Cinnamon (Nemo,
|
||||
udisks) la monte souvent dans `/media/<utilisateur>/…`, mais pas toujours
|
||||
(personne de connecté, automontage désactivé) et pas forcément avant le
|
||||
démarrage de la sauvegarde. Le script monte donc lui-même la clé sur
|
||||
`/run/sauvegarde-cle-usb`, que Nemo l'ait montée ou non : Linux accepte
|
||||
plusieurs montages du même système de fichiers. Avantages : le chemin est
|
||||
toujours le même, les options sont maîtrisées (clé FAT montée au nom de
|
||||
l'utilisateur), pas de course avec l'automontage, et un « Éjecter » dans Nemo
|
||||
pendant la copie ne retire que le montage de Nemo, pas celui de la
|
||||
sauvegarde. Si ce montage échoue, le script réutilise le montage de Nemo
|
||||
s'il est en écriture. À la fin : `sync` puis démontage de son seul montage
|
||||
(celui de Nemo reste, la clé reste visible).
|
||||
|
||||
**Garde-fous** (une erreur de chemin avec suppression peut effacer des
|
||||
données) :
|
||||
|
||||
- la sauvegarde refuse de partir si la destination n'est pas un sous-dossier
|
||||
de la clé montée **et** portant l'UUID attendu (même à travers un lien
|
||||
symbolique) ;
|
||||
- refus si `SOURCES` est vide, si un dossier source est absent ou **vide**
|
||||
(disque non monté, dossier chiffré verrouillé : sinon tout serait retiré de
|
||||
la clé), ou si deux sources portent le même nom ;
|
||||
- jamais deux sauvegardes en même temps (verrou) ;
|
||||
- vérification de la place libre avant de commencer ;
|
||||
- si rsync rencontre une erreur de lecture, il ne supprime rien.
|
||||
|
||||
**Options rsync** : clé ext4 → `-aH` (tout est conservé) ;
|
||||
FAT/exFAT/NTFS → `-rt --modify-window=2` (dates à 2 s près, sans droits) ;
|
||||
toujours `--delete --one-file-system`, plus
|
||||
`--backup --backup-dir=.corbeille/<date>` si `CORBEILLE_JOURS` > 0.
|
||||
208
slides/sauvegardes/script/installer.sh
Executable file
208
slides/sauvegardes/script/installer.sh
Executable file
|
|
@ -0,0 +1,208 @@
|
|||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# installer.sh — installe (ou désinstalle) la sauvegarde automatique sur clé
|
||||
# -----------------------------------------------------------------------------
|
||||
# Utilisation, depuis ce dossier :
|
||||
# sudo ./installer.sh installation (questions posées)
|
||||
# sudo ./installer.sh --uuid E234-70AF --utilisateur marie
|
||||
# installation sans questions
|
||||
# sudo ./installer.sh --desinstaller tout retirer (la clé n'est pas touchée)
|
||||
#
|
||||
# On peut le relancer autant de fois que l'on veut (changer de clé, mettre
|
||||
# à jour le script) : les réglages personnels de la configuration existante
|
||||
# sont conservés, seuls l'UUID et l'utilisateur sont mis à jour.
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
export LC_ALL=C.UTF-8
|
||||
|
||||
ICI="$(cd "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
SCRIPT_CIBLE="/usr/local/sbin/sauvegarde-cle-usb"
|
||||
SERVICE_CIBLE="/etc/systemd/system/sauvegarde-cle-usb.service"
|
||||
REGLE_CIBLE="/etc/udev/rules.d/99-sauvegarde-cle-usb.rules"
|
||||
CONF_CIBLE="/etc/sauvegarde-cle-usb.conf"
|
||||
|
||||
UUID_CHOISI=""
|
||||
UTILISATEUR_CHOISI=""
|
||||
ACTION="installer"
|
||||
|
||||
erreur() { printf 'ERREUR : %s\n' "$*" >&2; exit 1; }
|
||||
titre() { printf '\n== %s ==\n' "$*"; }
|
||||
|
||||
aide() {
|
||||
sed -n '4,13p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--desinstaller|--désinstaller) ACTION="desinstaller" ;;
|
||||
--uuid) UUID_CHOISI="${2:-}"; shift ;;
|
||||
--utilisateur) UTILISATEUR_CHOISI="${2:-}"; shift ;;
|
||||
--aide|--help|-h) aide; exit 0 ;;
|
||||
*) aide >&2; erreur "option inconnue : $1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[[ "$(id -u)" -eq 0 ]] || erreur "lancez ce programme avec sudo : sudo $0"
|
||||
|
||||
recharger() {
|
||||
systemctl daemon-reload
|
||||
udevadm control --reload-rules
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Désinstallation
|
||||
# -----------------------------------------------------------------------------
|
||||
if [[ "$ACTION" == "desinstaller" ]]; then
|
||||
titre "Désinstallation"
|
||||
systemctl stop sauvegarde-cle-usb.service 2>/dev/null || true
|
||||
for f in "$REGLE_CIBLE" "$SERVICE_CIBLE" "$SCRIPT_CIBLE" "$CONF_CIBLE"; do
|
||||
if [[ -e "$f" ]]; then
|
||||
rm -f -- "$f"
|
||||
echo "Supprimé : $f"
|
||||
fi
|
||||
done
|
||||
recharger
|
||||
echo
|
||||
echo "La sauvegarde automatique est désinstallée."
|
||||
echo "Les copies déjà présentes sur la clé n'ont pas été touchées."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Vérifications préalables
|
||||
# -----------------------------------------------------------------------------
|
||||
for f in sauvegarde-cle-usb sauvegarde-cle-usb.conf sauvegarde-cle-usb.service 99-sauvegarde-cle-usb.rules.modele; do
|
||||
[[ -f "$ICI/$f" ]] || erreur "fichier manquant à côté de installer.sh : $f"
|
||||
done
|
||||
command -v rsync >/dev/null || erreur "rsync n'est pas installé : sudo apt install rsync"
|
||||
command -v notify-send >/dev/null || echo "Remarque : notify-send absent (sudo apt install libnotify-bin) : pas de notifications."
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Choix de la clé
|
||||
# -----------------------------------------------------------------------------
|
||||
# Une partition est candidate si elle a un UUID et si elle est (directement ou
|
||||
# non, ex. clé chiffrée déverrouillée) sur un périphérique USB.
|
||||
lister_candidats() {
|
||||
local nom type uuid fstype
|
||||
while read -r nom type uuid fstype; do
|
||||
[[ -n "$uuid" && -n "$fstype" ]] || continue
|
||||
case "$type" in part|disk|crypt) ;; *) continue ;; esac
|
||||
lsblk -nso TRAN "$nom" 2>/dev/null | grep -qx usb || continue
|
||||
printf '%s %s %s\n' "$nom" "$uuid" "$fstype"
|
||||
done < <(lsblk -rnpo NAME,TYPE,UUID,FSTYPE)
|
||||
}
|
||||
|
||||
if [[ -z "$UUID_CHOISI" ]]; then
|
||||
titre "Choix de la clé de sauvegarde"
|
||||
echo "Branchez la clé à utiliser (et, si elle est chiffrée, déverrouillez-la"
|
||||
echo "dans le gestionnaire de fichiers), puis appuyez sur Entrée."
|
||||
read -r _
|
||||
mapfile -t CANDIDATS < <(lister_candidats)
|
||||
[[ "${#CANDIDATS[@]}" -gt 0 ]] || erreur "aucune clé USB trouvée. Est-elle bien branchée ?"
|
||||
|
||||
echo "Clés USB trouvées :"
|
||||
echo
|
||||
i=0
|
||||
for c in "${CANDIDATS[@]}"; do
|
||||
i=$((i + 1))
|
||||
read -r nom _ _ <<<"$c"
|
||||
printf ' [%d] ' "$i"
|
||||
lsblk -dno NAME,LABEL,UUID,FSTYPE,SIZE "$nom" | head -n1
|
||||
done
|
||||
echo
|
||||
echo "(Colonnes : nom, étiquette, UUID, système de fichiers, taille)"
|
||||
echo "Pour comparer : lsblk -o NAME,LABEL,UUID,FSTYPE,SIZE,TRAN"
|
||||
read -r -p "Numéro de la clé de sauvegarde : " num
|
||||
[[ "$num" =~ ^[0-9]+$ && "$num" -ge 1 && "$num" -le "${#CANDIDATS[@]}" ]] || erreur "choix invalide."
|
||||
read -r _ UUID_CHOISI fstype <<<"${CANDIDATS[$((num - 1))]}"
|
||||
else
|
||||
periph="$(blkid -U "$UUID_CHOISI" 2>/dev/null || true)"
|
||||
if [[ -n "$periph" ]]; then
|
||||
fstype="$(blkid -o value -s TYPE "$periph" 2>/dev/null || true)"
|
||||
else
|
||||
fstype=""
|
||||
echo "Attention : aucune partition d'UUID $UUID_CHOISI n'est branchée actuellement."
|
||||
fi
|
||||
fi
|
||||
|
||||
[[ "$UUID_CHOISI" =~ ^[A-Za-z0-9-]+$ ]] || erreur "UUID invalide : $UUID_CHOISI"
|
||||
|
||||
periph="$(blkid -U "$UUID_CHOISI" 2>/dev/null || true)"
|
||||
if [[ -n "$periph" && "$(lsblk -dno TYPE "$periph" 2>/dev/null)" == "crypt" ]]; then
|
||||
echo "Clé chiffrée : le démarrage automatique n'est pas garanti après le"
|
||||
echo "déverrouillage. Lancez alors la sauvegarde avec :"
|
||||
echo " sudo systemctl start sauvegarde-cle-usb"
|
||||
fi
|
||||
|
||||
case "${fstype:-}" in
|
||||
crypto_LUKS)
|
||||
erreur "c'est l'enveloppe chiffrée de la clé. Déverrouillez-la dans le gestionnaire de fichiers, relancez l'installateur et choisissez la ligne « crypt » qui apparaît." ;;
|
||||
ext4|ext3|ext2|btrfs|xfs) echo "Clé au format Linux ($fstype) : droits et propriétaires seront conservés." ;;
|
||||
vfat) echo "Clé au format FAT32 : ni droits ni liens symboliques, fichiers de 4 Go maximum." ;;
|
||||
exfat|ntfs) echo "Clé au format $fstype : ni droits ni propriétaires (suffisant pour des documents)." ;;
|
||||
"") ;;
|
||||
*) echo "Format $fstype : non testé, copie prudente sans droits." ;;
|
||||
esac
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Choix de l'utilisateur
|
||||
# -----------------------------------------------------------------------------
|
||||
if [[ -z "$UTILISATEUR_CHOISI" ]]; then
|
||||
defaut="${SUDO_USER:-}"
|
||||
[[ "$defaut" == "root" ]] && defaut=""
|
||||
read -r -p "Utilisateur dont on sauvegarde le dossier personnel [${defaut}] : " UTILISATEUR_CHOISI
|
||||
UTILISATEUR_CHOISI="${UTILISATEUR_CHOISI:-$defaut}"
|
||||
fi
|
||||
[[ -n "$UTILISATEUR_CHOISI" ]] || erreur "aucun utilisateur indiqué."
|
||||
[[ "$UTILISATEUR_CHOISI" =~ ^[a-z_][a-z0-9_.-]*$ ]] || erreur "nom d'utilisateur invalide : $UTILISATEUR_CHOISI"
|
||||
id -u "$UTILISATEUR_CHOISI" >/dev/null 2>&1 || erreur "utilisateur inconnu : $UTILISATEUR_CHOISI"
|
||||
DOSSIER_PERSO="$(getent passwd "$UTILISATEUR_CHOISI" | cut -d: -f6)"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Installation des fichiers
|
||||
# -----------------------------------------------------------------------------
|
||||
titre "Installation"
|
||||
|
||||
install -o root -g root -m 755 "$ICI/sauvegarde-cle-usb" "$SCRIPT_CIBLE"
|
||||
echo "Script : $SCRIPT_CIBLE"
|
||||
|
||||
install -o root -g root -m 644 "$ICI/sauvegarde-cle-usb.service" "$SERVICE_CIBLE"
|
||||
echo "Service : $SERVICE_CIBLE"
|
||||
|
||||
if [[ -f "$CONF_CIBLE" ]]; then
|
||||
# Configuration existante : on garde les réglages, on met à jour la clé.
|
||||
sed -i -e "s|^UUID=.*|UUID=\"$UUID_CHOISI\"|" \
|
||||
-e "s|^UTILISATEUR=.*|UTILISATEUR=\"$UTILISATEUR_CHOISI\"|" "$CONF_CIBLE"
|
||||
echo "Réglages : $CONF_CIBLE (conservés, clé et utilisateur mis à jour)"
|
||||
echo " vérifiez la liste SOURCES si l'utilisateur a changé."
|
||||
else
|
||||
sed -e "s|^UUID=.*|UUID=\"$UUID_CHOISI\"|" \
|
||||
-e "s|^UTILISATEUR=.*|UTILISATEUR=\"$UTILISATEUR_CHOISI\"|" \
|
||||
-e "s|/home/marie|$DOSSIER_PERSO|g" \
|
||||
"$ICI/sauvegarde-cle-usb.conf" > "$CONF_CIBLE"
|
||||
echo "Réglages : $CONF_CIBLE"
|
||||
fi
|
||||
chown root:root "$CONF_CIBLE"
|
||||
chmod 644 "$CONF_CIBLE"
|
||||
|
||||
sed "s|@UUID@|$UUID_CHOISI|" "$ICI/99-sauvegarde-cle-usb.rules.modele" > "$REGLE_CIBLE"
|
||||
chmod 644 "$REGLE_CIBLE"
|
||||
echo "Règle udev : $REGLE_CIBLE"
|
||||
|
||||
recharger
|
||||
|
||||
titre "C'est prêt"
|
||||
cat <<FIN
|
||||
Clé de sauvegarde : UUID $UUID_CHOISI
|
||||
Utilisateur : $UTILISATEUR_CHOISI
|
||||
|
||||
1. Pour tester sans rien modifier : sudo sauvegarde-cle-usb --simulation
|
||||
2. Débranchez puis rebranchez la clé : la sauvegarde démarre toute seule.
|
||||
Attendez la notification « Sauvegarde terminée » avant de la retirer.
|
||||
3. Journal : journalctl -u sauvegarde-cle-usb
|
||||
|
||||
Réglages (dossiers, exclusions, corbeille) : sudo xed $CONF_CIBLE
|
||||
FIN
|
||||
475
slides/sauvegardes/script/sauvegarde-cle-usb
Executable file
475
slides/sauvegardes/script/sauvegarde-cle-usb
Executable file
|
|
@ -0,0 +1,475 @@
|
|||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# sauvegarde-cle-usb — sauvegarde automatique vers UNE clé USB précise
|
||||
# -----------------------------------------------------------------------------
|
||||
# Lancé par systemd (sauvegarde-cle-usb.service) quand udev voit apparaître
|
||||
# la clé déclarée dans /etc/sauvegarde-cle-usb.conf. Peut aussi être lancé
|
||||
# à la main pour tester :
|
||||
#
|
||||
# sudo systemctl start sauvegarde-cle-usb (recommandé : journal systemd)
|
||||
# sudo sauvegarde-cle-usb --simulation (montre ce qui serait fait)
|
||||
# sudo sauvegarde-cle-usb (sauvegarde réelle)
|
||||
#
|
||||
# Rappel : une sauvegarde « miroir » recopie aussi les suppressions.
|
||||
# C'est pourquoi, par défaut, tout ce qui disparaît ou change sur la clé
|
||||
# part d'abord dans <clé>/.corbeille/AAAA-MM-JJ_HHMMSS (gardée N jours).
|
||||
#
|
||||
# Mode test (sans sudo, sans clé, sans udev) :
|
||||
# SAUVEGARDE_TEST_DESTINATION=/tmp/fausse-cle \
|
||||
# SAUVEGARDE_CONF=./ma-conf-de-test.conf ./sauvegarde-cle-usb
|
||||
# Le dossier indiqué joue le rôle de la racine de la clé : pas de montage,
|
||||
# pas de vérification d'UUID, notifications affichées dans le terminal.
|
||||
# SAUVEGARDE_TEST_FSTYPE=vfat permet de simuler une clé FAT.
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
export LC_ALL=C.UTF-8 # messages et formats stables (dates, df)
|
||||
umask 022
|
||||
|
||||
NOM="sauvegarde-cle-usb"
|
||||
CONF="${SAUVEGARDE_CONF:-/etc/sauvegarde-cle-usb.conf}"
|
||||
POINT_MONTAGE_DEDIE="/run/sauvegarde-cle-usb"
|
||||
TEST_DESTINATION="${SAUVEGARDE_TEST_DESTINATION:-}"
|
||||
SIMULATION=0
|
||||
|
||||
# Variables d'état (utilisées par le nettoyage final)
|
||||
MONTE_PAR_NOUS=0
|
||||
RACINE_CLE=""
|
||||
FICHIER_SORTIE=""
|
||||
RESULTAT_ECRIT=0
|
||||
CLE_VERIFIEE=0 # passe à 1 quand on est sûr d'être sur la bonne clé
|
||||
UTILISATEUR_UID=""
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Petites fonctions utilitaires
|
||||
# -----------------------------------------------------------------------------
|
||||
journal() { printf '%s\n' "$*"; } # stdout -> journal systemd
|
||||
erreur() { printf 'ERREUR : %s\n' "$*" >&2; }
|
||||
|
||||
aide() {
|
||||
cat <<'FIN'
|
||||
Utilisation : sauvegarde-cle-usb [--simulation] [--aide]
|
||||
|
||||
--simulation montre ce qui serait copié/supprimé, sans rien modifier
|
||||
--aide affiche ce message
|
||||
|
||||
Configuration : /etc/sauvegarde-cle-usb.conf
|
||||
Journal : journalctl -u sauvegarde-cle-usb
|
||||
FIN
|
||||
}
|
||||
|
||||
# Notification de bureau dans la session graphique de l'utilisateur.
|
||||
# Ne fait jamais échouer le script (une notification ratée n'est pas grave).
|
||||
notifier() {
|
||||
local urgence="$1" titre="$2" message="$3"
|
||||
journal "[notification] $titre — $message"
|
||||
if [[ -n "$TEST_DESTINATION" ]]; then
|
||||
return 0 # mode test : terminal seulement
|
||||
fi
|
||||
[[ -n "$UTILISATEUR_UID" ]] || return 0
|
||||
local bus="/run/user/$UTILISATEUR_UID/bus"
|
||||
[[ -S "$bus" ]] || return 0 # personne n'est connecté
|
||||
command -v notify-send >/dev/null 2>&1 || return 0
|
||||
timeout 10 runuser -u "$UTILISATEUR" -- env \
|
||||
DBUS_SESSION_BUS_ADDRESS="unix:path=$bus" \
|
||||
notify-send --app-name="Sauvegarde" --icon=drive-removable-media \
|
||||
--urgency="$urgence" "$titre" "$message" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
# Écrit le petit compte rendu lisible sur la clé.
|
||||
ecrire_resultat() {
|
||||
local resultat="$1"
|
||||
[[ "$CLE_VERIFIEE" -eq 1 && -d "$RACINE_CLE" ]] || return 0
|
||||
[[ "$SIMULATION" -eq 0 ]] || return 0
|
||||
local f="$RACINE_CLE/derniere-sauvegarde.txt"
|
||||
{
|
||||
echo "Dernière sauvegarde : $(date '+%d/%m/%Y à %H:%M:%S')"
|
||||
echo "Ordinateur : $(hostname)"
|
||||
echo "Résultat : $resultat"
|
||||
echo "Dossiers sauvegardés :"
|
||||
local s
|
||||
for s in "${SOURCES[@]}"; do echo " - $s"; done
|
||||
echo "Copie sur la clé : $DOSSIER_DESTINATION/"
|
||||
if [[ "${CORBEILLE_JOURS:-0}" -gt 0 ]]; then
|
||||
echo "Fichiers supprimés ou remplacés : .corbeille/ (gardés $CORBEILLE_JOURS jours)"
|
||||
fi
|
||||
if [[ -n "$FICHIER_SORTIE" && -s "$FICHIER_SORTIE" ]]; then
|
||||
echo
|
||||
echo "--- Résumé rsync ---"
|
||||
grep -E '^(Number of|Total file size|Total transferred|Literal data|sent |total size)' \
|
||||
"$FICHIER_SORTIE" || true
|
||||
fi
|
||||
} > "$f" 2>/dev/null || true
|
||||
proprietaire_utilisateur "$f"
|
||||
RESULTAT_ECRIT=1
|
||||
}
|
||||
|
||||
# Sur une clé ext4, rend à l'utilisateur ce que root a créé (il pourra le
|
||||
# gérer depuis Nemo). Sur FAT/exFAT/NTFS, c'est l'option de montage uid= qui
|
||||
# s'en charge.
|
||||
proprietaire_utilisateur() {
|
||||
[[ -z "$TEST_DESTINATION" ]] || return 0
|
||||
[[ "${TYPE_FS:-}" == "ext"* ]] || return 0
|
||||
[[ "$(id -u)" -eq 0 ]] || return 0
|
||||
chown "$UTILISATEUR:" "$@" 2>/dev/null || true
|
||||
}
|
||||
|
||||
nettoyage() {
|
||||
local code=$?
|
||||
set +e
|
||||
if [[ "$code" -ne 0 && "$RESULTAT_ECRIT" -eq 0 ]]; then
|
||||
ecrire_resultat "ÉCHEC (code $code) — voir : journalctl -u $NOM"
|
||||
fi
|
||||
[[ -n "$FICHIER_SORTIE" ]] && rm -f "$FICHIER_SORTIE"
|
||||
if [[ -n "$RACINE_CLE" ]]; then
|
||||
sync -f "$RACINE_CLE/." 2>/dev/null || sync
|
||||
fi
|
||||
if [[ "$MONTE_PAR_NOUS" -eq 1 ]]; then
|
||||
if umount "$POINT_MONTAGE_DEDIE" 2>/dev/null; then
|
||||
journal "Clé démontée de $POINT_MONTAGE_DEDIE."
|
||||
else
|
||||
# Clé arrachée ou encore occupée : démontage « paresseux ».
|
||||
umount -l "$POINT_MONTAGE_DEDIE" 2>/dev/null
|
||||
journal "Démontage différé de $POINT_MONTAGE_DEDIE."
|
||||
fi
|
||||
rmdir "$POINT_MONTAGE_DEDIE" 2>/dev/null
|
||||
fi
|
||||
exit "$code"
|
||||
}
|
||||
|
||||
# Arrête tout, avec notification et message clair.
|
||||
abandon() {
|
||||
erreur "$1"
|
||||
notifier critical "Sauvegarde impossible" "$1"
|
||||
exit "${2:-1}"
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Lecture des options et de la configuration
|
||||
# -----------------------------------------------------------------------------
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--simulation|-n) SIMULATION=1 ;;
|
||||
--aide|--help|-h) aide; exit 0 ;;
|
||||
*) erreur "option inconnue : $arg"; aide >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
trap nettoyage EXIT
|
||||
|
||||
if [[ -z "$TEST_DESTINATION" && "$(id -u)" -ne 0 ]]; then
|
||||
erreur "ce script doit être lancé par root (sudo $NOM, ou sudo systemctl start $NOM)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[[ -r "$CONF" ]] || { erreur "configuration introuvable : $CONF (lancez installer.sh)"; exit 1; }
|
||||
|
||||
# La configuration est exécutée par root : elle doit appartenir à root et ne
|
||||
# pas être modifiable par d'autres (sinon n'importe qui pourrait y glisser
|
||||
# une commande).
|
||||
if [[ -z "$TEST_DESTINATION" ]]; then
|
||||
if [[ "$(stat -c '%u' "$CONF")" != 0 ]] || [[ -n "$(find "$CONF" -perm /022)" ]]; then
|
||||
erreur "$CONF doit appartenir à root et n'être modifiable que par lui (chmod 644)."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Valeurs par défaut, puis lecture du fichier
|
||||
UUID=""
|
||||
UTILISATEUR=""
|
||||
SOURCES=()
|
||||
DOSSIER_DESTINATION="Sauvegarde"
|
||||
CORBEILLE_JOURS=30
|
||||
EXCLUSIONS=()
|
||||
VERIFIER_ESPACE="oui"
|
||||
# shellcheck source=sauvegarde-cle-usb.conf
|
||||
. "$CONF"
|
||||
|
||||
# --- Contrôles de la configuration -------------------------------------------
|
||||
[[ -n "$UTILISATEUR" ]] || { erreur "UTILISATEUR est vide dans $CONF"; exit 1; }
|
||||
id -u "$UTILISATEUR" >/dev/null 2>&1 || { erreur "utilisateur inconnu : $UTILISATEUR"; exit 1; }
|
||||
UTILISATEUR_UID="$(id -u "$UTILISATEUR")"
|
||||
UTILISATEUR_GID="$(id -g "$UTILISATEUR")"
|
||||
|
||||
[[ "$CORBEILLE_JOURS" =~ ^[0-9]+$ ]] || abandon "CORBEILLE_JOURS doit être un nombre (0 = pas de corbeille)."
|
||||
|
||||
if [[ "${#SOURCES[@]}" -eq 0 ]]; then
|
||||
abandon "aucun dossier à sauvegarder (SOURCES est vide dans $CONF)."
|
||||
fi
|
||||
|
||||
# Chaque source doit exister, être un dossier, et ne pas être vide : un
|
||||
# dossier personnel vide (disque non monté, dossier chiffré verrouillé...)
|
||||
# combiné à --delete viderait la clé !
|
||||
declare -A NOMS_VUS=()
|
||||
SOURCES_PROPRES=()
|
||||
for s in "${SOURCES[@]}"; do
|
||||
[[ -n "$s" ]] || abandon "une entrée de SOURCES est vide."
|
||||
[[ -d "$s" ]] || abandon "le dossier à sauvegarder n'existe pas : $s"
|
||||
s="$(realpath -e -- "$s")"
|
||||
[[ "$s" != "/" ]] || abandon "sauvegarder / entier n'est pas prévu par cet outil."
|
||||
if [[ -z "$(find "$s" -mindepth 1 -maxdepth 1 -print -quit 2>/dev/null)" ]]; then
|
||||
abandon "le dossier $s est vide : sauvegarde annulée par prudence."
|
||||
fi
|
||||
base="$(basename -- "$s")"
|
||||
if [[ -n "${NOMS_VUS[$base]:-}" ]]; then
|
||||
abandon "deux dossiers de SOURCES portent le même nom « $base » : ils s'écraseraient sur la clé."
|
||||
fi
|
||||
NOMS_VUS[$base]=1
|
||||
SOURCES_PROPRES+=("$s")
|
||||
done
|
||||
SOURCES=("${SOURCES_PROPRES[@]}")
|
||||
|
||||
# DOSSIER_DESTINATION : un nom de dossier simple, relatif à la racine de la clé.
|
||||
case "$DOSSIER_DESTINATION" in
|
||||
""|"/"|"."|".."|/*|*/../*|../*|*/..|.corbeille|.corbeille/*)
|
||||
abandon "DOSSIER_DESTINATION invalide : « $DOSSIER_DESTINATION » (exemple : Sauvegarde)." ;;
|
||||
esac
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Verrou : jamais deux sauvegardes en même temps
|
||||
# -----------------------------------------------------------------------------
|
||||
if [[ -n "$TEST_DESTINATION" ]]; then
|
||||
VERROU="${TMPDIR:-/tmp}/$NOM-test.lock"
|
||||
else
|
||||
VERROU="/run/lock/$NOM.lock"
|
||||
fi
|
||||
exec 9>"$VERROU"
|
||||
if ! flock -n 9; then
|
||||
erreur "une sauvegarde est déjà en cours."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Trouver (ou monter) la clé
|
||||
# -----------------------------------------------------------------------------
|
||||
if [[ -n "$TEST_DESTINATION" ]]; then
|
||||
journal "*** MODE TEST : la clé est simulée par $TEST_DESTINATION ***"
|
||||
[[ -d "$TEST_DESTINATION" ]] || abandon "destination de test introuvable : $TEST_DESTINATION"
|
||||
RACINE_CLE="$(realpath -e -- "$TEST_DESTINATION")"
|
||||
[[ "$RACINE_CLE" != "/" ]] || abandon "destination de test « / » refusée."
|
||||
TYPE_FS="${SAUVEGARDE_TEST_FSTYPE:-$(findmnt -n -o FSTYPE --target "$RACINE_CLE" | head -n1)}"
|
||||
else
|
||||
[[ "$UUID" =~ ^[A-Za-z0-9-]+$ ]] || abandon "UUID absent ou invalide dans $CONF."
|
||||
PERIPH_LIEN="/dev/disk/by-uuid/$UUID"
|
||||
|
||||
# udev peut nous lancer une fraction de seconde avant que le lien existe.
|
||||
for _ in $(seq 1 20); do
|
||||
[[ -b "$PERIPH_LIEN" ]] && break
|
||||
sleep 0.5
|
||||
done
|
||||
[[ -b "$PERIPH_LIEN" ]] || abandon "la clé de sauvegarde (UUID $UUID) n'est pas branchée."
|
||||
PERIPH="$(readlink -f "$PERIPH_LIEN")"
|
||||
TYPE_FS="$(blkid -o value -s TYPE "$PERIPH" 2>/dev/null || true)"
|
||||
[[ -n "$TYPE_FS" ]] || abandon "système de fichiers illisible sur $PERIPH."
|
||||
|
||||
notifier normal "Sauvegarde en cours…" "Clé détectée. Ne la retirez pas avant le message « Sauvegarde terminée »."
|
||||
|
||||
# Choix : on monte la clé NOUS-MÊMES sur un point dédié, même si
|
||||
# Nemo/udisks l'a déjà montée dans /media/<utilisateur>/. Linux accepte
|
||||
# plusieurs montages du même système de fichiers ; ainsi le chemin est
|
||||
# connu, les options maîtrisées, et un « Éjecter » dans Nemo ne coupe
|
||||
# pas la sauvegarde en plein milieu (udisks voit la clé encore utilisée).
|
||||
mkdir -p "$POINT_MONTAGE_DEDIE"
|
||||
chmod 755 "$POINT_MONTAGE_DEDIE"
|
||||
if mountpoint -q "$POINT_MONTAGE_DEDIE"; then
|
||||
# Reste d'une exécution interrompue : on vérifie que c'est la bonne clé.
|
||||
if [[ "$(findmnt -n -o UUID --mountpoint "$POINT_MONTAGE_DEDIE")" != "$UUID" ]]; then
|
||||
abandon "$POINT_MONTAGE_DEDIE est occupé par un autre disque."
|
||||
fi
|
||||
MONTE_PAR_NOUS=1
|
||||
else
|
||||
case "$TYPE_FS" in
|
||||
vfat|exfat|ntfs|ntfs3)
|
||||
OPTS="rw,nosuid,nodev,noatime,uid=$UTILISATEUR_UID,gid=$UTILISATEUR_GID,umask=022" ;;
|
||||
*)
|
||||
OPTS="rw,nosuid,nodev,noatime" ;;
|
||||
esac
|
||||
if mount -o "$OPTS" "$PERIPH" "$POINT_MONTAGE_DEDIE"; then
|
||||
MONTE_PAR_NOUS=1
|
||||
RACINE_CLE="$POINT_MONTAGE_DEDIE"
|
||||
else
|
||||
# Plan B : réutiliser un montage existant (udisks) en écriture.
|
||||
rmdir "$POINT_MONTAGE_DEDIE" 2>/dev/null || true
|
||||
EXISTANT="$(findmnt -n -o TARGET,OPTIONS --source "$PERIPH" | awk '$2 ~ /(^|,)rw(,|$)/ {print $1; exit}')"
|
||||
[[ -n "$EXISTANT" ]] || abandon "impossible de monter la clé ($PERIPH, $TYPE_FS)."
|
||||
journal "Montage dédié impossible, réutilisation du montage existant : $EXISTANT"
|
||||
RACINE_CLE="$EXISTANT"
|
||||
fi
|
||||
fi
|
||||
RACINE_CLE="${RACINE_CLE:-$POINT_MONTAGE_DEDIE}"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# GARDE-FOU : la destination doit être SUR la clé, et nulle part ailleurs
|
||||
# -----------------------------------------------------------------------------
|
||||
# Un rsync --delete vers un mauvais dossier effacerait des données : on
|
||||
# vérifie tout, et on refuse au moindre doute.
|
||||
DESTINATION="$RACINE_CLE/$DOSSIER_DESTINATION"
|
||||
if [[ -z "$TEST_DESTINATION" ]]; then
|
||||
mountpoint -q "$RACINE_CLE" || abandon "la clé n'est pas montée sur $RACINE_CLE."
|
||||
UUID_MONTE="$(findmnt -n -o UUID --mountpoint "$RACINE_CLE" | head -n1)"
|
||||
[[ "$UUID_MONTE" == "$UUID" ]] || abandon "$RACINE_CLE n'est pas la clé attendue (UUID $UUID_MONTE au lieu de $UUID)."
|
||||
fi
|
||||
|
||||
if [[ "$SIMULATION" -eq 0 ]]; then
|
||||
mkdir -p -- "$DESTINATION" || abandon "impossible de créer $DESTINATION (clé protégée en écriture ?)"
|
||||
proprietaire_utilisateur "$DESTINATION"
|
||||
fi
|
||||
if [[ -e "$DESTINATION" ]]; then
|
||||
DEST_REELLE="$(realpath -e -- "$DESTINATION")"
|
||||
else
|
||||
DEST_REELLE="$(realpath -m -- "$DESTINATION")"
|
||||
fi
|
||||
case "$DEST_REELLE/" in
|
||||
"$RACINE_CLE"/?*/) ;; # bien un sous-dossier de la racine de la clé
|
||||
*) abandon "destination hors de la clé : $DEST_REELLE (refusé)." ;;
|
||||
esac
|
||||
if [[ -z "$TEST_DESTINATION" && -e "$DEST_REELLE" ]]; then
|
||||
# Même un lien symbolique piégé ne doit pas nous faire sortir de la clé.
|
||||
[[ "$(findmnt -n -o UUID --target "$DEST_REELLE" | head -n1)" == "$UUID" ]] \
|
||||
|| abandon "la destination $DEST_REELLE n'est pas sur la clé (refusé)."
|
||||
fi
|
||||
for s in "${SOURCES[@]}"; do
|
||||
case "$DEST_REELLE/" in "$s"/*) abandon "la destination est à l'intérieur de $s (refusé)." ;; esac
|
||||
case "$s/" in "$DEST_REELLE"/*) abandon "$s est à l'intérieur de la destination (refusé)." ;; esac
|
||||
done
|
||||
DESTINATION="$DEST_REELLE"
|
||||
CLE_VERIFIEE=1
|
||||
journal "Clé : $RACINE_CLE ($TYPE_FS) — copie dans : $DESTINATION"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Options rsync
|
||||
# -----------------------------------------------------------------------------
|
||||
HORODATAGE="$(date '+%Y-%m-%d_%H%M%S')"
|
||||
CORBEILLE_RACINE="$RACINE_CLE/.corbeille"
|
||||
CORBEILLE="$CORBEILLE_RACINE/$HORODATAGE"
|
||||
|
||||
OPTS_RSYNC=(--delete --one-file-system --human-readable --stats --info=del1 --info=name0)
|
||||
|
||||
case "$TYPE_FS" in
|
||||
ext2|ext3|ext4|btrfs|xfs|f2fs)
|
||||
# Système de fichiers Linux : on garde tout (droits, propriétaires,
|
||||
# dates, liens symboliques) ; -H : liens physiques.
|
||||
OPTS_RSYNC+=(-a -H) ;;
|
||||
vfat|exfat|ntfs|ntfs3|fuseblk)
|
||||
# FAT/exFAT/NTFS : ni droits ni propriétaires ; dates à 2 s près.
|
||||
# Les liens symboliques sont ignorés (rsync affiche « skipping
|
||||
# non-regular file ») : ce n'est pas une erreur.
|
||||
OPTS_RSYNC+=(-rt --modify-window=2 --no-perms --no-owner --no-group)
|
||||
if [[ "$TYPE_FS" == "vfat" ]]; then
|
||||
# FAT32 : 4 Go maximum par fichier.
|
||||
OPTS_RSYNC+=(--max-size=4095M)
|
||||
fi ;;
|
||||
*)
|
||||
journal "Système de fichiers « $TYPE_FS » non prévu : copie prudente (-rt)."
|
||||
OPTS_RSYNC+=(-rt --modify-window=2) ;;
|
||||
esac
|
||||
|
||||
if [[ "$CORBEILLE_JOURS" -gt 0 ]]; then
|
||||
# Rien n'est perdu d'un coup : fichiers supprimés ou remplacés -> .corbeille/
|
||||
OPTS_RSYNC+=(--backup "--backup-dir=$CORBEILLE")
|
||||
fi
|
||||
|
||||
# Exclusions : ce qui ne sert à rien de sauvegarder
|
||||
for e in "${EXCLUSIONS[@]}"; do
|
||||
[[ -n "$e" ]] && OPTS_RSYNC+=("--exclude=$e")
|
||||
done
|
||||
|
||||
if [[ "$SIMULATION" -eq 1 ]]; then
|
||||
OPTS_RSYNC+=(--dry-run --itemize-changes --info=name1)
|
||||
journal "*** SIMULATION : rien ne sera modifié sur la clé ***"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Vérification de l'espace libre (estimation par un passage à blanc)
|
||||
# -----------------------------------------------------------------------------
|
||||
# Approximation : avec la corbeille, les fichiers remplacés ne libèrent pas
|
||||
# de place ; on compare donc la quantité à copier à l'espace libre.
|
||||
if [[ "$VERIFIER_ESPACE" == "oui" && "$SIMULATION" -eq 0 ]]; then
|
||||
A_COPIER="$(rsync "${OPTS_RSYNC[@]}" --dry-run --info=del0 --no-human-readable "${SOURCES[@]}" "$DESTINATION/" 2>/dev/null \
|
||||
| sed -n 's/^Total transferred file size: \([0-9,]*\).*/\1/p' | tr -d ',' || true)"
|
||||
LIBRE="$(df -B1 --output=avail "$RACINE_CLE" | tail -n1 | tr -d ' ')"
|
||||
if [[ "$A_COPIER" =~ ^[0-9]+$ && "$LIBRE" =~ ^[0-9]+$ ]]; then
|
||||
journal "À copier : $(numfmt --to=iec "$A_COPIER") — libre sur la clé : $(numfmt --to=iec "$LIBRE")"
|
||||
if (( A_COPIER > LIBRE )); then
|
||||
abandon "clé trop petite ou pleine : il faut $(numfmt --to=iec "$A_COPIER"), il reste $(numfmt --to=iec "$LIBRE")." 3
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Avertissement FAT32 : fichiers de plus de 4 Go non copiés
|
||||
if [[ "$TYPE_FS" == "vfat" ]]; then
|
||||
GROS="$(find "${SOURCES[@]}" -xdev -type f -size +4095M 2>/dev/null | head -n 5 || true)"
|
||||
if [[ -n "$GROS" ]]; then
|
||||
journal "Fichiers de plus de 4 Go NON copiés (limite FAT32) :"
|
||||
journal "$GROS"
|
||||
notifier normal "Sauvegarde : fichiers trop gros" "Certains fichiers dépassent 4 Go et ne tiennent pas sur cette clé (FAT32). Voir le journal."
|
||||
fi
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# La sauvegarde
|
||||
# -----------------------------------------------------------------------------
|
||||
DEBUT="$(date +%s)"
|
||||
FICHIER_SORTIE="$(mktemp)"
|
||||
journal "Sauvegarde de : ${SOURCES[*]}"
|
||||
|
||||
set +e
|
||||
rsync "${OPTS_RSYNC[@]}" "${SOURCES[@]}" "$DESTINATION/" 2>&1 | tee "$FICHIER_SORTIE"
|
||||
CODE_RSYNC="${PIPESTATUS[0]}"
|
||||
set -e
|
||||
DUREE=$(( $(date +%s) - DEBUT ))
|
||||
|
||||
if [[ "$SIMULATION" -eq 1 ]]; then
|
||||
journal "Simulation terminée (code rsync $CODE_RSYNC). Rien n'a été modifié."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Rend à l'utilisateur la corbeille créée par root (clé ext4).
|
||||
if [[ -d "$CORBEILLE" ]]; then
|
||||
proprietaire_utilisateur "$CORBEILLE_RACINE" "$CORBEILLE"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Purge des vieilles corbeilles
|
||||
# -----------------------------------------------------------------------------
|
||||
if [[ "$CORBEILLE_JOURS" -gt 0 && -d "$CORBEILLE_RACINE" ]]; then
|
||||
# Uniquement les dossiers au format de date attendu, directement dans .corbeille
|
||||
while IFS= read -r -d '' vieux; do
|
||||
journal "Purge de l'ancienne corbeille : $(basename "$vieux")"
|
||||
rm -rf -- "$vieux"
|
||||
done < <(find "$CORBEILLE_RACINE" -mindepth 1 -maxdepth 1 -type d \
|
||||
-name '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]_[0-9][0-9][0-9][0-9][0-9][0-9]' \
|
||||
-mtime +"$CORBEILLE_JOURS" -print0)
|
||||
fi
|
||||
|
||||
sync -f "$DESTINATION/." 2>/dev/null || sync
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Bilan
|
||||
# -----------------------------------------------------------------------------
|
||||
case "$CODE_RSYNC" in
|
||||
0|24)
|
||||
# 24 = fichiers disparus pendant la copie (normal si on travaille)
|
||||
ecrire_resultat "OK ($DUREE s)"
|
||||
journal "Sauvegarde terminée en $DUREE s."
|
||||
notifier normal "Sauvegarde terminée" "Vous pouvez retirer la clé."
|
||||
;;
|
||||
23)
|
||||
# Copie partielle : souvent des noms interdits sur FAT (: ? * " < > | \)
|
||||
# ou des fichiers illisibles. Les suppressions n'ont PAS été faites.
|
||||
ecrire_resultat "PARTIELLE ($DUREE s) — certains fichiers n'ont pas pu être copiés"
|
||||
journal "Sauvegarde PARTIELLE : certains fichiers n'ont pas été copiés (voir ci-dessus)."
|
||||
notifier normal "Sauvegarde terminée avec des avertissements" \
|
||||
"Quelques fichiers n'ont pas pu être copiés (noms interdits sur la clé ?). Détails : journalctl -u $NOM. Vous pouvez retirer la clé."
|
||||
exit 23
|
||||
;;
|
||||
*)
|
||||
if grep -q 'No space left on device' "$FICHIER_SORTIE"; then
|
||||
ecrire_resultat "ÉCHEC : clé pleine"
|
||||
abandon "la clé est pleine. Libérez de la place (ou videz .corbeille) puis rebranchez-la." 3
|
||||
fi
|
||||
ecrire_resultat "ÉCHEC (code rsync $CODE_RSYNC)"
|
||||
abandon "la sauvegarde a échoué (code rsync $CODE_RSYNC). Détails : journalctl -u $NOM" "$CODE_RSYNC"
|
||||
;;
|
||||
esac
|
||||
60
slides/sauvegardes/script/sauvegarde-cle-usb.conf
Normal file
60
slides/sauvegardes/script/sauvegarde-cle-usb.conf
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# shellcheck shell=bash disable=SC2034
|
||||
# =============================================================================
|
||||
# /etc/sauvegarde-cle-usb.conf — réglages de la sauvegarde automatique
|
||||
# -----------------------------------------------------------------------------
|
||||
# Ce fichier est écrit par installer.sh. Vous pouvez le modifier ensuite :
|
||||
# sudo xed /etc/sauvegarde-cle-usb.conf
|
||||
# Les changements sont pris en compte au prochain branchement de la clé.
|
||||
# Si vous changez l'UUID, relancez plutôt : sudo ./installer.sh
|
||||
# Syntaxe : celle de bash (NOM="valeur", listes entre parenthèses).
|
||||
# =============================================================================
|
||||
|
||||
# Identifiant (UUID) du système de fichiers de LA clé de sauvegarde.
|
||||
# Une autre clé ne déclenche rien. Pour le trouver : lsblk -o NAME,LABEL,UUID
|
||||
UUID="XXXX-XXXX"
|
||||
|
||||
# Utilisateur à prévenir par des notifications (et propriétaire des copies).
|
||||
UTILISATEUR="marie"
|
||||
|
||||
# Dossiers à sauvegarder (un par ligne, entre guillemets).
|
||||
# Par défaut : tout le dossier personnel.
|
||||
SOURCES=(
|
||||
"/home/marie"
|
||||
)
|
||||
# Exemple pour ne garder que l'essentiel :
|
||||
# SOURCES=(
|
||||
# "/home/marie/Documents"
|
||||
# "/home/marie/Images"
|
||||
# "/home/marie/Bureau"
|
||||
# )
|
||||
|
||||
# Dossier, sur la clé, qui reçoit la copie (ex. la clé contiendra
|
||||
# Sauvegarde/marie/Documents/...).
|
||||
DOSSIER_DESTINATION="Sauvegarde"
|
||||
|
||||
# Ce qui n'existe plus sur l'ordinateur est retiré de la clé. Mais avant,
|
||||
# ces fichiers (et les anciennes versions des fichiers modifiés) sont mis de
|
||||
# côté dans .corbeille/AAAA-MM-JJ_HHMMSS sur la clé, pendant ce nombre de
|
||||
# jours. 0 = miroir pur : ce qui est supprimé sur l'ordinateur est supprimé
|
||||
# de la clé, sans filet. (Déconseillé.)
|
||||
CORBEILLE_JOURS=30
|
||||
|
||||
# Ce qu'on ne sauvegarde pas (motifs rsync, relatifs aux dossiers ci-dessus).
|
||||
# « dossier/ » = un dossier de ce nom, où qu'il soit
|
||||
# « *.tmp » = tous les fichiers finissant par .tmp
|
||||
EXCLUSIONS=(
|
||||
".cache/" # caches des logiciels (se recréent tout seuls)
|
||||
".local/share/Trash/" # la corbeille de l'ordinateur
|
||||
".thumbnails/" # miniatures d'images
|
||||
".gvfs/"
|
||||
"*.tmp" # fichiers temporaires
|
||||
"*~" # copies de secours des éditeurs
|
||||
".~lock.*#" # verrous LibreOffice (document ouvert)
|
||||
"*.part" # téléchargements inachevés
|
||||
# "Téléchargements/" # enlevez le # pour ne pas sauvegarder ce dossier
|
||||
# ".local/share/Steam/" # jeux : très gros, se retéléchargent
|
||||
)
|
||||
|
||||
# Vérifier avant de commencer qu'il y a assez de place sur la clé ("oui"/"non").
|
||||
# Cela demande un premier passage rapide (lecture seule) des dossiers.
|
||||
VERIFIER_ESPACE="oui"
|
||||
18
slides/sauvegardes/script/sauvegarde-cle-usb.service
Normal file
18
slides/sauvegardes/script/sauvegarde-cle-usb.service
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# /etc/systemd/system/sauvegarde-cle-usb.service
|
||||
# Démarré par la règle udev 99-sauvegarde-cle-usb.rules quand LA clé de
|
||||
# sauvegarde est branchée (udev ne doit pas lancer lui-même de tâche longue).
|
||||
# Lancement manuel : sudo systemctl start sauvegarde-cle-usb
|
||||
# Journal : journalctl -u sauvegarde-cle-usb
|
||||
|
||||
[Unit]
|
||||
Description=Sauvegarde automatique sur clé USB
|
||||
After=local-fs.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/sauvegarde-cle-usb
|
||||
# Une première sauvegarde peut être longue : pas de limite de temps.
|
||||
TimeoutStartSec=infinity
|
||||
# Rester discret pendant que l'ordinateur sert à autre chose.
|
||||
Nice=10
|
||||
IOSchedulingClass=idle
|
||||
167
slides/sauvegardes/script/tester-sans-sudo.sh
Executable file
167
slides/sauvegardes/script/tester-sans-sudo.sh
Executable file
|
|
@ -0,0 +1,167 @@
|
|||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# tester-sans-sudo.sh — vérifie la logique de sauvegarde, sans clé ni sudo
|
||||
# -----------------------------------------------------------------------------
|
||||
# Utilise le mode test de sauvegarde-cle-usb (SAUVEGARDE_TEST_DESTINATION) :
|
||||
# un dossier temporaire joue le rôle de la clé. Rien n'est monté, rien n'est
|
||||
# écrit dans /etc. Usage : ./tester-sans-sudo.sh [dossier-de-travail]
|
||||
# =============================================================================
|
||||
|
||||
# shellcheck disable=SC2016 # les bash -c reçoivent leurs arguments via $1, voulu
|
||||
set -euo pipefail
|
||||
ICI="$(cd "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SCRIPT="$ICI/sauvegarde-cle-usb"
|
||||
BASE="${1:-$(mktemp -d)}"
|
||||
TRAVAIL="$BASE/essai-$$"
|
||||
SRC="$TRAVAIL/maison/marie"
|
||||
CLE="$TRAVAIL/cle"
|
||||
CONF="$TRAVAIL/test.conf"
|
||||
export TMPDIR="$TRAVAIL"
|
||||
|
||||
REUSSIS=0
|
||||
RATES=0
|
||||
ok() { echo " OK $*"; REUSSIS=$((REUSSIS + 1)); }
|
||||
rate() { echo " RATÉ $*"; RATES=$((RATES + 1)); }
|
||||
verifier() { local desc="$1"; shift; if "$@"; then ok "$desc"; else rate "$desc"; fi; }
|
||||
|
||||
ecrire_conf() { # $1 = CORBEILLE_JOURS, $2 = DOSSIER_DESTINATION, $3.. = sources
|
||||
local jours="$1" dest="$2"; shift 2
|
||||
{
|
||||
echo "UUID=\"TEST\""
|
||||
echo "UTILISATEUR=\"$(id -un)\""
|
||||
printf 'SOURCES=('; printf ' "%s"' "$@"; echo ' )'
|
||||
echo "DOSSIER_DESTINATION=\"$dest\""
|
||||
echo "CORBEILLE_JOURS=$jours"
|
||||
echo 'EXCLUSIONS=( ".cache/" ".local/share/Trash/" "*.tmp" )'
|
||||
echo 'VERIFIER_ESPACE="oui"'
|
||||
} > "$CONF"
|
||||
}
|
||||
|
||||
lancer() { # lance le script en mode test ; sortie dans $TRAVAIL/sortie.txt
|
||||
sleep 1 # horodatage à la seconde : une corbeille par lancement
|
||||
set +e
|
||||
SAUVEGARDE_CONF="$CONF" SAUVEGARDE_TEST_DESTINATION="${DEST_TEST:-$CLE}" \
|
||||
"$SCRIPT" "$@" > "$TRAVAIL/sortie.txt" 2>&1
|
||||
CODE=$?
|
||||
set -e
|
||||
}
|
||||
|
||||
nb_corbeilles() { find "$CLE/.corbeille" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l; }
|
||||
|
||||
mkdir -p "$SRC/Documents" "$SRC/Images" "$SRC/.cache/firefox" "$SRC/.local/share/Trash/files" "$CLE"
|
||||
echo "lettre v1" > "$SRC/Documents/lettre.txt"
|
||||
echo "impôts 2026" > "$SRC/Documents/impots.txt"
|
||||
echo "photo" > "$SRC/Images/vacances.jpg"
|
||||
echo "cache" > "$SRC/.cache/firefox/gros-cache"
|
||||
echo "jeté" > "$SRC/.local/share/Trash/files/vieux.txt"
|
||||
echo "temporaire" > "$SRC/Documents/brouillon.tmp"
|
||||
ecrire_conf 30 Sauvegarde "$SRC"
|
||||
COPIE="$CLE/Sauvegarde/marie"
|
||||
|
||||
echo "Dossier de test : $TRAVAIL"
|
||||
|
||||
echo "1. Première sauvegarde"
|
||||
lancer
|
||||
verifier "code de sortie 0" test "$CODE" -eq 0
|
||||
verifier "lettre.txt copiée" test -f "$COPIE/Documents/lettre.txt"
|
||||
verifier "vacances.jpg copiée" test -f "$COPIE/Images/vacances.jpg"
|
||||
verifier ".cache exclu" test ! -e "$COPIE/.cache"
|
||||
verifier "corbeille locale exclue" test ! -e "$COPIE/.local/share/Trash"
|
||||
verifier "*.tmp exclu" test ! -e "$COPIE/Documents/brouillon.tmp"
|
||||
verifier "derniere-sauvegarde.txt = OK" grep -q 'Résultat *: OK' "$CLE/derniere-sauvegarde.txt"
|
||||
|
||||
echo "2. Simulation (--simulation) après suppression de impots.txt"
|
||||
rm "$SRC/Documents/impots.txt"
|
||||
lancer --simulation
|
||||
verifier "code de sortie 0" test "$CODE" -eq 0
|
||||
verifier "annonce la suppression" grep -q 'deleting.*impots.txt' "$TRAVAIL/sortie.txt"
|
||||
verifier "impots.txt toujours sur la clé" test -f "$COPIE/Documents/impots.txt"
|
||||
verifier "aucune corbeille créée" test "$(nb_corbeilles)" -eq 0
|
||||
|
||||
echo "3. Suppression côté ordinateur → retiré de la copie, gardé en corbeille"
|
||||
lancer
|
||||
verifier "code de sortie 0" test "$CODE" -eq 0
|
||||
verifier "impots.txt absent de la copie" test ! -e "$COPIE/Documents/impots.txt"
|
||||
CORB1="$(find "$CLE/.corbeille" -mindepth 1 -maxdepth 1 -type d | head -n1)"
|
||||
verifier "nom de corbeille AAAA-MM-JJ_HHMMSS" bash -c '[[ "$(basename "$1")" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{6}$ ]]' _ "$CORB1"
|
||||
verifier "impots.txt dans .corbeille/<date>" test -f "$CORB1/marie/Documents/impots.txt"
|
||||
|
||||
echo "4. Modification d'un fichier → nouvelle version copiée, ancienne en corbeille"
|
||||
echo "lettre v2, bien plus longue" > "$SRC/Documents/lettre.txt"
|
||||
lancer
|
||||
verifier "code de sortie 0" test "$CODE" -eq 0
|
||||
verifier "copie = v2" grep -q 'v2' "$COPIE/Documents/lettre.txt"
|
||||
verifier "ancienne v1 en corbeille" bash -c 'grep -rqs "lettre v1" "$1"/.corbeille/*/marie/Documents/lettre.txt' _ "$CLE"
|
||||
verifier "deux corbeilles" test "$(nb_corbeilles)" -eq 2
|
||||
|
||||
echo "5. Purge des corbeilles de plus de 30 jours"
|
||||
mkdir -p "$CLE/.corbeille/2026-09-01_120000/marie" "$CLE/.corbeille/2026-10-30_120000"
|
||||
echo vieux > "$CLE/.corbeille/2026-09-01_120000/marie/vieux.txt"
|
||||
touch -d '40 days ago' "$CLE/.corbeille/2026-09-01_120000"
|
||||
touch -d '6 days ago' "$CLE/.corbeille/2026-10-30_120000"
|
||||
mkdir -p "$CLE/.corbeille/a-moi"; touch -d '90 days ago' "$CLE/.corbeille/a-moi"
|
||||
lancer
|
||||
verifier "code de sortie 0" test "$CODE" -eq 0
|
||||
verifier "corbeille de 40 jours purgée" test ! -e "$CLE/.corbeille/2026-09-01_120000"
|
||||
verifier "corbeille de 6 jours gardée" test -d "$CLE/.corbeille/2026-10-30_120000"
|
||||
verifier "dossier au nom inattendu épargné" test -d "$CLE/.corbeille/a-moi"
|
||||
verifier "purge annoncée dans le journal" grep -q 'Purge.*2026-09-01_120000' "$TRAVAIL/sortie.txt"
|
||||
|
||||
echo "6. CORBEILLE_JOURS=0 : miroir pur, sans corbeille"
|
||||
ecrire_conf 0 Sauvegarde "$SRC"
|
||||
avant="$(nb_corbeilles)"
|
||||
rm "$SRC/Images/vacances.jpg"
|
||||
lancer
|
||||
verifier "code de sortie 0" test "$CODE" -eq 0
|
||||
verifier "vacances.jpg absent de la copie" test ! -e "$COPIE/Images/vacances.jpg"
|
||||
verifier "aucune nouvelle corbeille" test "$(nb_corbeilles)" -eq "$avant"
|
||||
verifier "vacances.jpg nulle part sur la clé" test -z "$(find "$CLE" -name vacances.jpg)"
|
||||
ecrire_conf 30 Sauvegarde "$SRC"
|
||||
|
||||
echo "7. Clé FAT simulée : lien symbolique ignoré sans erreur"
|
||||
ln -s Documents/lettre.txt "$SRC/raccourci"
|
||||
SAUVEGARDE_TEST_FSTYPE=vfat lancer
|
||||
verifier "code de sortie 0" test "$CODE" -eq 0
|
||||
verifier "lien non copié" test ! -e "$COPIE/raccourci" -a ! -L "$COPIE/raccourci"
|
||||
rm "$SRC/raccourci"
|
||||
|
||||
echo "8. Garde-fous (tous doivent REFUSER, sans rien toucher)"
|
||||
empreinte() { find "$CLE" "$SRC" -printf '%p %s\n' | sort | md5sum; }
|
||||
E0="$(empreinte)"
|
||||
DEST_TEST="$TRAVAIL/nexiste-pas" lancer
|
||||
verifier "destination de test absente → refus" test "$CODE" -ne 0
|
||||
DEST_TEST="/" lancer
|
||||
verifier "destination « / » → refus" test "$CODE" -ne 0
|
||||
for d in "../evasion" "a/../../evasion" "/tmp/x" "." ".corbeille" ""; do
|
||||
ecrire_conf 30 "$d" "$SRC"; lancer
|
||||
verifier "DOSSIER_DESTINATION=« $d » → refus" test "$CODE" -ne 0
|
||||
done
|
||||
verifier "rien n'a été créé hors de la clé" test ! -e "$TRAVAIL/evasion" -a ! -e "$TRAVAIL/x"
|
||||
ln -s "$TRAVAIL/maison" "$CLE/piege"
|
||||
ecrire_conf 30 piege "$SRC"; lancer
|
||||
verifier "lien sur la clé vers l'extérieur → refus" test "$CODE" -ne 0
|
||||
rm "$CLE/piege"
|
||||
ecrire_conf 30 Sauvegarde; lancer
|
||||
verifier "SOURCES vide → refus" test "$CODE" -ne 0
|
||||
mkdir -p "$TRAVAIL/vide"
|
||||
ecrire_conf 30 Sauvegarde "$TRAVAIL/vide"; lancer
|
||||
verifier "source vide → refus" test "$CODE" -ne 0
|
||||
ecrire_conf 30 Sauvegarde "$TRAVAIL/absent"; lancer
|
||||
verifier "source inexistante → refus" test "$CODE" -ne 0
|
||||
ecrire_conf 30 Sauvegarde "$CLE"; lancer
|
||||
verifier "source contenant la clé → refus" test "$CODE" -ne 0
|
||||
ecrire_conf 30 Sauvegarde "$SRC/Documents" "$TRAVAIL/maison/marie/../marie/Documents"; lancer
|
||||
verifier "deux sources au même nom → refus" test "$CODE" -ne 0
|
||||
verifier "clé et sources inchangées" test "$(empreinte)" = "$E0"
|
||||
ecrire_conf 30 Sauvegarde "$SRC"
|
||||
|
||||
echo "9. Verrou : deux sauvegardes simultanées impossibles"
|
||||
exec 8>"$TRAVAIL/sauvegarde-cle-usb-test.lock"; flock -n 8
|
||||
lancer
|
||||
verifier "2e exécution s'arrête sans rien faire" grep -q 'déjà en cours' "$TRAVAIL/sortie.txt"
|
||||
exec 8>&-
|
||||
|
||||
echo
|
||||
echo "Bilan : $REUSSIS réussis, $RATES ratés."
|
||||
[[ "$RATES" -eq 0 ]] && rm -rf "$TRAVAIL"
|
||||
[[ "$RATES" -eq 0 ]]
|
||||
Loading…
Reference in a new issue