Comment éviter un rm -rf accidentel sur un serveur Linux

Empilez les protections, car chacune couvre une erreur différente. Faites demander rm en interactif avec alias rm='rm -I --preserve-root=all', listez dans safe-rm les chemins à ne jamais supprimer, protégez les variables des scripts avec "${DIR:?}", rendez les fichiers clés immuables avec chattr +i, et déplacez les fichiers vers une corbeille au lieu de les supprimer. Rien de tout cela ne remplace des snapshots et des sauvegardes testées.

Mis à jour le . rm -I, --preserve-root=all, les alias, la question de zsh avant rm *, safe-rm 1.1 de Debian, set -u, ${VAR:?}, chattr +i, trash-cli 0.24 et la récupération via /proc et lsof ont été exécutés sur des conteneurs Debian 13 avec GNU coreutils 9.7, bash 5.2 et zsh 5.9.

Ce que rm refuse déjà, et ce qu'il ne refuse pas

Le rm de GNU refuse rm -rf / par défaut. Cela protège un seul chemin littéral. Les accidents courants ont une autre forme :

  • rm -rf /* : le shell remplace d'abord l'étoile, donc rm reçoit /bin, /etc et le reste, et aucun d'eux n'est /.
  • rm -rf /var/app /* : une espace de trop ajoute tout le système à la liste.
  • Une variable vide dans un script : rm -rf "$DIR"/* devient rm -rf /*.
  • La bonne commande dans le mauvais terminal, sur un serveur que vous pensiez être la préproduction.

--preserve-root=all va plus loin et refuse tout dossier situé sur un autre disque que son parent, comme un volume monté :

rm: skipping '/dev/shm', since it's on a different device
rm: and --preserve-root=all is in effect

Les habitudes comme lister un glob avec echo avant de le supprimer sont dans comment éviter les erreurs sur les serveurs de production.

Faire demander rm une seule fois

Mettez ces deux lignes dans ~/.bashrc sur le serveur, et dans /root/.bashrc :

alias rm='rm -I --preserve-root=all'
alias sudo='sudo '

-I demande une fois avant une suppression récursive ou avant de supprimer plus de trois fichiers. Toute réponse autre que y ne supprime rien. L'espace finale dans l'alias sudo dit à bash de traiter aussi le mot suivant comme un alias, donc sudo rm demande aussi :

$ sudo rm /tmp/s/*
rm: remove 4 arguments? n

zsh demande de lui-même avant rm * et rm path/*, sauf si RM_STAR_SILENT est activé :

zsh: sure you want to delete all 2 files in /tmp/z [yn]? n

Les alias ne s'appliquent qu'aux shells interactifs. Les scripts, les tâches cron et ssh server 'rm ...' utilisent le vrai rm, et \rm ou command rm contournent l'alias exprès. Évitez alias rm='rm -i' : une question pour chaque fichier habitue à taper -f.

safe-rm : des chemins que rm ignorera

safe-rm place une enveloppe devant rm qui ignore tout chemin d'une liste protégée. Sous Debian et Ubuntu :

sudo apt install safe-rm
echo '/srv/app/shared' | sudo tee -a /etc/safe-rm.conf

La liste contient déjà les dossiers système comme /etc, /usr et /var. Notre test :

$ rm -rf /srv/app/shared
safe-rm: Skipping /srv/app/shared.

Connaissez ses limites avant de compter dessus :

  • Il protège le chemin listé, pas son contenu. rm -rf /srv/app/shared/* a quand même vidé le dossier. Une ligne comme /srv/data/* protège chaque élément placé directement sous /srv/data.
  • Il sort avec 0 même quand il a ignoré quelque chose, donc un script ne s'en aperçoit pas.
  • Le paquet ajoute son dossier au PATH dans /etc/profile.d, donc il marche dans les shells de connexion et avec sudo -i. sudo rm utilise le secure_path de sudo et a lancé le vrai rm dans notre test, tout comme ssh server 'rm ...'.

Protéger les variables dans les scripts

La plupart des catastrophes en script viennent d'une variable vide ou non définie. Arrêtez le script avant qu'il n'atteigne rm :

#!/usr/bin/env bash
set -euo pipefail
rm -rf -- "${BUILD_DIR:?}/"
s.sh: line 3: BUILD_DIR: parameter null or not set

${BUILD_DIR:?} s'arrête sur une erreur quand la variable est vide ou absente, et set -u fait de même pour toute variable non définie du script. -- termine les options, donc un fichier nommé -rf est traité comme un fichier.

Droits et fichiers immuables

La meilleure protection, c'est de ne pas pouvoir supprimer. Travaillez avec un utilisateur normal et n'utilisez sudo que pour les commandes qui en ont besoin, pour qu'une erreur dans votre dossier personnel ne puisse pas toucher le système. Voir comment créer un utilisateur SSH avec sudo.

Pour les fichiers qui ne doivent pas changer, même pour root, posez l'attribut immuable, sur ext4 et les autres systèmes de fichiers qui le gèrent :

sudo chattr +i /etc/app/config.yml
lsattr /etc/app/config.yml
----i---------e------- /etc/app/config.yml
rm: cannot remove '/etc/app/config.yml': Operation not permitted

La même erreur a arrêté rm -rf sur le dossier qui le contient. Retirez l'attribut avec sudo chattr -i avant une modification prévue. Les mises à jour et les éditeurs ne peuvent pas non plus écrire dans le fichier, alors gardez l'attribut pour les fichiers qui en ont vraiment besoin.

Une corbeille au lieu de rm

Quand vous faites le ménage à la main, déplacez plutôt les fichiers vers une corbeille. trash-cli utilise la même corbeille que les bureaux Linux :

sudo apt install trash-cli
trash-put old-release/
trash-list
trash-restore
trash-empty 30

trash-restore demande quel élément remettre en place. trash-empty 30 supprime ce qui est dans la corbeille depuis plus de 30 jours, ce qui convient à une tâche cron. Jusque-là, les fichiers occupent toujours de l'espace disque. Ne faites pas de rm un alias de trash-put : les options diffèrent, et l'habitude vous lâche sur les serveurs qui ne l'ont pas.

Snapshots, sauvegardes, et si c'est déjà arrivé

Chaque protection ci-dessus peut être contournée. Un snapshot, lui, ne se supprime pas avec rm. Prenez-en un avant un travail risqué : zfs snapshot pool/data@before-cleanup sur ZFS, btrfs subvolume snapshot -r sur btrfs, lvcreate --snapshot sur LVM, ou un snapshot de volume dans la console de votre hébergeur. Copiez les sauvegardes sur une autre machine, par exemple avec rsync en SSH, et testez une restauration de temps en temps.

Si des fichiers ont déjà disparu, arrêtez d'écrire sur ce disque : de nouvelles données peuvent écraser ce qu'un outil de récupération pourrait encore trouver. Restaurez ensuite depuis un snapshot ou une sauvegarde. Un cas peut être sauvé tout de suite : un fichier encore ouvert par un programme en cours. Trouvez-le et recopiez-le avant que ce programme ne s'arrête :

lsof +L1
cp /proc/374/fd/3 /root/data.recovered
COMMAND PID USER FD   TYPE DEVICE SIZE/OFF NLINK     NODE NAME
tail    374 root 3r   REG  0,186        2     0 12873355 /srv/data.txt (deleted)

Le nombre après /proc/ est le PID, et celui après fd/ est le nombre de la colonne FD sans sa lettre.

Questions

rm -rf / fonctionne-t-il encore sous Linux ?

Pas avec le rm de GNU : il refuse / sauf avec --no-preserve-root. Mais le shell transforme rm -rf /* en /bin, /etc et le reste, et cela n'est pas refusé.

Peut-on annuler un rm -rf ?

Pas avec rm lui-même. Restaurez depuis un snapshot ou une sauvegarde. Un fichier encore ouvert par un programme en cours peut être recopié depuis /proc/PID/fd tant que ce programme ne le ferme pas.

alias rm='rm -i' est-il une bonne idée ?

Il demande pour chaque fichier, alors on prend l'habitude de répondre oui sans lire ou d'ajouter -f. rm -I demande une seule fois, pour trois fichiers ou plus ou pour une suppression récursive, ce qui garde la question utile.

safe-rm protège-t-il les fichiers à l'intérieur d'un dossier ?

Non. Il ignore les chemins listés dans sa configuration. rm -rf /srv/app/shared/* vide quand même un /srv/app/shared protégé, sauf si son contenu est listé aussi, par exemple avec /srv/app/shared/*.

Sources

Autres guides : Sécuriser un serveur

Tous les guides