Come evitare un rm -rf per errore sui server Linux

Usa più livelli, perché ognuno copre uno sbaglio diverso. Fai chiedere conferma a rm interattivo con alias rm='rm -I --preserve-root=all', elenca in safe-rm i percorsi che non devono mai sparire, proteggi le variabili degli script con "${DIR:?}", rendi immutabili i file chiave con chattr +i e sposta i file in un cestino invece di cancellarli. Nessuno di questi sostituisce snapshot e backup provati.

Aggiornata il . rm -I, --preserve-root=all, gli alias, la domanda di zsh prima di rm *, safe-rm 1.1 di Debian, set -u, ${VAR:?}, chattr +i, trash-cli 0.24 e il recupero tramite /proc e lsof sono stati eseguiti su container Debian 13 con GNU coreutils 9.7, bash 5.2 e zsh 5.9.

Cosa rm rifiuta già, e cosa no

GNU rm rifiuta rm -rf / per impostazione predefinita. Questo protegge un solo percorso letterale. Gli incidenti comuni sono diversi:

  • rm -rf /*: la shell espande prima l'asterisco, quindi rm riceve /bin, /etc e il resto, e nessuno di questi è /.
  • rm -rf /var/app /*: uno spazio di troppo aggiunge tutto il sistema all'elenco.
  • Una variabile vuota in uno script: rm -rf "$DIR"/* diventa rm -rf /*.
  • Il comando giusto nel terminale sbagliato, su un server che credevi fosse lo staging.

--preserve-root=all fa un passo in più e rifiuta qualsiasi cartella che sta su un disco diverso da quello della cartella madre, come un volume montato:

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

Abitudini come elencare un glob con echo prima di cancellarlo sono in come evitare errori sui server di produzione.

Far chiedere a rm una volta sola

Metti queste due righe in ~/.bashrc sul server, e in /root/.bashrc:

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

-I chiede una volta prima di una cancellazione ricorsiva o prima di togliere più di tre file. Qualsiasi risposta diversa da y non cancella nulla. Lo spazio finale nell'alias di sudo dice a bash di espandere come alias anche la parola dopo, così chiede anche sudo rm:

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

zsh chiede da solo prima di rm * e rm percorso/*, a meno che non sia impostato RM_STAR_SILENT:

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

Gli alias valgono solo nelle shell interattive. Script, cron e ssh server 'rm ...' usano il vero rm, e \rm o command rm saltano l'alias di proposito. Evita alias rm='rm -i': una domanda per ogni file abitua a scrivere -f.

safe-rm: percorsi che rm salterà

safe-rm mette davanti a rm un programma che salta tutti i percorsi di un elenco protetto. Su Debian e Ubuntu:

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

L'elenco include già cartelle di sistema come /etc, /usr e /var. La nostra prova:

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

Conosci i suoi limiti prima di contarci:

  • Protegge il percorso elencato, non quello che c'è dentro. rm -rf /srv/app/shared/* ha comunque svuotato la cartella. Una riga come /srv/data/* protegge ogni elemento direttamente sotto /srv/data.
  • Esce con 0 anche quando ha saltato qualcosa, quindi uno script non se ne accorge.
  • Il pacchetto aggiunge la sua cartella al PATH in /etc/profile.d, quindi funziona nelle shell di login e con sudo -i. sudo rm usa il secure_path di sudo e nella nostra prova ha eseguito il vero rm, e lo stesso vale per ssh server 'rm ...'.

Proteggere le variabili negli script

La maggior parte dei disastri negli script nasce da una variabile vuota o non impostata. Ferma lo script prima che arrivi a 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:?} si ferma con un errore quando la variabile è vuota o manca, e set -u fa lo stesso per ogni variabile non impostata nello script. -- chiude le opzioni, così un file chiamato -rf viene trattato come un file.

Permessi e file immutabili

La protezione più forte è non poter cancellare. Lavora come utente normale e usa sudo solo per i comandi che lo richiedono, così uno sbaglio nella tua cartella home non arriva al sistema. Vedi come creare un utente SSH con sudo.

Per i file che non devono cambiare nemmeno per root, imposta l'attributo immutabile, su ext4 e sugli altri filesystem che lo supportano:

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

Lo stesso errore ha fermato anche rm -rf sulla cartella che lo contiene. Togli l'attributo con sudo chattr -i prima di una modifica prevista. Nemmeno aggiornamenti ed editor possono scrivere nel file, quindi usalo solo per i file che ne hanno davvero bisogno.

Un cestino al posto di rm

Quando fai pulizia a mano, sposta i file in un cestino. trash-cli usa lo stesso cestino di Linux desktop:

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

trash-restore chiede quale elemento rimettere al suo posto. trash-empty 30 toglie ciò che sta nel cestino da più di 30 giorni, ed è adatto a un cron. Fino ad allora i file occupano ancora spazio su disco. Non fare un alias di rm verso trash-put: le opzioni sono diverse, e l'abitudine tradisce sui server dove non c'è.

Snapshot, backup, e se è già successo

Ogni livello qui sopra si può aggirare. Uno snapshot non si cancella con rm. Fanne uno prima di un lavoro rischioso: zfs snapshot pool/data@before-cleanup su ZFS, btrfs subvolume snapshot -r su btrfs, lvcreate --snapshot su LVM, o uno snapshot del volume dalla console del tuo cloud. Copia i backup su un'altra macchina, per esempio con rsync via SSH, e prova ogni tanto un ripristino.

Se i file sono già spariti, smetti di scrivere su quel disco: i dati nuovi possono sovrascrivere ciò che uno strumento di recupero potrebbe ancora trovare. Poi ripristina da uno snapshot o da un backup. Un caso si salva subito: un file ancora aperto da un programma in esecuzione. Trovalo e ricopialo prima che il programma si fermi:

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)

Il numero dopo /proc/ è il PID, quello dopo fd/ è il numero della colonna FD senza la lettera.

Domande

rm -rf / funziona ancora su Linux?

Non con GNU rm: rifiuta / se non aggiungi --no-preserve-root. Però rm -rf /* viene espanso dalla shell in /bin, /etc e il resto, e quello non viene rifiutato.

Si può annullare un rm -rf?

Non con rm. Ripristina da uno snapshot o da un backup. Un file ancora aperto da un programma in esecuzione si può ricopiare da /proc/PID/fd finché quel programma non lo chiude.

alias rm='rm -i' è una buona idea?

Chiede conferma per ogni file, così si impara a rispondere sì senza leggere o ad aggiungere -f. rm -I chiede una volta sola, per tre o più file o per una cancellazione ricorsiva, e la domanda resta significativa.

safe-rm protegge i file dentro una cartella?

No. Salta i percorsi elencati nella sua configurazione. rm -rf /srv/app/shared/* svuota comunque una /srv/app/shared protetta, a meno di elencare anche il contenuto, per esempio con /srv/app/shared/*.

Fonti

Altro in Mettere in sicurezza un server

Tutte le guide