12 out 2026 · infraestrutura, linux, backup · 4 min
Backup só vale se você testar a restauração
Fiz um backup de uma pasta de 4,1 MB com o --link-dest do rsync, apaguei um arquivo e o devolvi a partir do snapshot: o hash sha256 bateu. O teste inteiro leva alguns segundos. É a única prova de que o backup serve para alguma coisa, e a maioria descobre que não servia justamente no dia em que precisa dele.
Um backup que você nunca testou é só uma promessa. O rsync resolve a parte do espaço com o --link-dest: arquivos que não mudaram viram hardlink da versão anterior, então cada snapshot custa só o que mudou. Na medição, três snapshots de uma origem de 4,1 MB ocuparam 4,1 MB no total, contra 12,3 MB de três cópias completas. O segundo e o terceiro snapshot ficaram com 8 KB cada, só o texto alterado entre uma execução e outra.
O script
O backup.sh abaixo cria o snapshot com data e hora, reaproveita o anterior com --link-dest, apaga as versões que passaram do limite e prova a restauração no final. Sem argumentos, ele roda a demonstração num diretório temporário, sem tocar nos seus arquivos.
#!/usr/bin/env bash
# backup.sh - snapshot com rotacao e restauracao testavel.
#
# Uso:
# ./backup.sh demo em diretorio temporario
# ./backup.sh /origem /destino snapshot de verdade, guardando 7 versoes
# ./backup.sh /origem /destino N guardando N versoes
#
# O que faz: copia a origem para um subdiretorio com data e hora. Arquivos que
# nao mudaram viram hardlink da versao anterior (rsync --link-dest), entao cada
# snapshot ocupa so o que mudou. Versoes que passam do limite sao apagadas. O
# demo prova a restauracao: apaga um arquivo da origem e o devolve do snapshot.
set -u
KEEP_DEFAULT=7
RSYNC=""
detecta() {
if command -v rsync >/dev/null 2>&1; then
RSYNC="$(command -v rsync)"
fi
}
snapshot() {
local src="$1" novo="$2" ultimo="$3"
if [ -n "$RSYNC" ]; then
if [ -n "$ultimo" ]; then
"$RSYNC" -a --delete --link-dest="$ultimo" "$src/" "$novo/"
else
"$RSYNC" -a --delete "$src/" "$novo/"
fi
else
mkdir -p "$novo"
cp -a "$src/." "$novo/"
fi
}
rotaciona() {
local dest="$1" manter="$2"
local snapshots total excedente antigos d vazio
snapshots=$(ls -1d "$dest"/*/ 2>/dev/null | sort)
total=$(printf '%s\n' "$snapshots" | sed '/^$/d' | wc -l)
if [ "$total" -le "$manter" ]; then
return
fi
excedente=$((total - manter))
antigos=$(printf '%s\n' "$snapshots" | sed '/^$/d' | head -n "$excedente")
if [ -z "$RSYNC" ]; then
printf 'rotacao: sem rsync, nao vou apagar as %s versoes excedentes\n' "$excedente"
return
fi
printf 'rotacao: apagando %s versao(oes) antiga(s)\n' "$excedente"
for d in $antigos; do
vazio=$(mktemp -d)
"$RSYNC" -a --delete "$vazio/" "$d/"
rmdir "$vazio"
rmdir "$d"
printf ' removida %s\n' "$d"
done
}
run() {
local src="$1" dest="$2" manter="$3"
mkdir -p "$dest"
local ts novo ultimo
ts=$(date +%Y%m%d-%H%M%S)
novo="$dest/$ts"
ultimo=$(ls -1d "$dest"/*/ 2>/dev/null | sort | tail -n1)
if [ -n "$RSYNC" ]; then
printf 'copiador: %s\n' "$("$RSYNC" --version | head -n1)"
else
printf 'copiador: cp (rsync nao encontrado; sem dedup por hardlink)\n'
fi
snapshot "$src" "$novo" "$ultimo"
printf 'snapshot criado: %s\n' "$novo"
rotaciona "$dest" "$manter"
}
demo() {
local tmp src dest hash_antes snap hash_depois
tmp=$(mktemp -d)
src="$tmp/origem"
dest="$tmp/snapshots"
mkdir -p "$src"
echo 'relatorio de vendas de outubro' > "$src/nota.txt"
echo 'registro de acessos' > "$src/outro.txt"
hash_antes=$(sha256sum "$src/nota.txt" | cut -d' ' -f1)
run "$src" "$dest" 3
printf '== testando a restauracao ==\n'
unlink "$src/nota.txt"
printf 'nota.txt apagado da origem\n'
snap=$(ls -1d "$dest"/*/ | sort | tail -n1)
cp "$snap/nota.txt" "$src/nota.txt"
hash_depois=$(sha256sum "$src/nota.txt" | cut -d' ' -f1)
if [ "$hash_antes" = "$hash_depois" ]; then
printf 'restauracao OK: nota.txt voltou identico (sha256 %s)\n' "$hash_depois"
else
printf 'restauracao FALHOU: hash antes %s, depois %s\n' "$hash_antes" "$hash_depois" >&2
exit 1
fi
}
detecta
if [ "$#" -eq 0 ]; then
demo
elif [ "$#" -ge 2 ]; then
run "$1" "$2" "${3:-$KEEP_DEFAULT}"
else
echo "uso: $0 [origem destino [manter]]" >&2
exit 2
fi
O trecho que importa é o --link-dest="$ultimo". Ele aponta para o snapshot anterior: o rsync compara e, para cada arquivo idêntico, cria um hardlink em vez de copiar. O custo de cada versão vira só o delta, e é isso que faz o backup diário caber no mesmo disco.
Como verificar
Rode ./backup.sh sem argumentos. Você vê o copiador, o snapshot criado e, no fim, a confirmação da restauração:
copiador: rsync version 3.2.7
snapshot criado: .../snapshots/20261012-165916
== testando a restauração ==
nota.txt apagado da origem
restauração OK: nota.txt voltou idêntico (sha256 9bce...e443)
O número que interessa é o último: o hash bateu com o de antes do apagamento. Se bater, o backup restaura. Se não bater, você acha um problema que um backup não testado esconderia. A mesma lógica de que apagar não resolve de imediato vale para certificado que expira sem avisar: o teste é o que separa o que funciona do que parece funcionar.
Se der errado
Dois snapshots com o mesmo nome. O nome usa date +%Y%m%d-%H%M%S, que só muda a cada segundo. Duas execuções no mesmo segundo sobrescrevem a primeira. Rode com sleep 1 entre elas ou acrescente um contador ao nome.
rsync: command not found. Sem o rsync, o script cai numa cópia completa com cp, que funciona mas perde o dedup. Instale o rsync para ter o --link-dest.
restauração FALHOU. O arquivo no snapshot não é igual ao que você apagou. Confira se o snapshot escolhido, com ls -1d destino/*/ | sort | tail -n1, é o que você esperava.
rsync contra as alternativas
| opção | dedup | restauração | instalação |
|---|---|---|---|
| rsync –link-dest | hardlink | cp de volta | já vem na distro |
| rsnapshot | hardlink, mesmo princípio | arquivos visíveis | perl + config |
| cópia com cp | nenhum | cp de volta | nada |
O rsnapshot é o mesmo princípio empacotado: configuração em arquivo, agendamento e rotação prontos. Se você quer algo que roda sozinho todo dia sem escrever script, ele resolve. O script manual existe para quem quer ver o que acontece por dentro, uma linha de cada vez. Para o resto da rotina do host, vale a mesma regra de deixar a infraestrutura se manter sozinha.
FAQ
Com que frequência testo a restauração? Toda vez que mudar o script ou o agendamento, e uma vez por mês no mínimo, de preferência automática no mesmo cron que roda o backup.
Hardlink gasta espaço?
Não. Dois hardlinks apontam para o mesmo bloco em disco, e o du conta uma vez. O risco é editar o arquivo no lugar e alterar o snapshot antigo junto, então o rsync copia em vez de sobrescrever quando vê mudança.
Funciona para backup remoto?
Sim. Troque a origem por usuario@host:/caminho e o rsync usa ssh. O --link-dest continua apontando para um snapshot no destino.
Quantas versões o script guarda?
Sete por padrão. O terceiro argumento muda: ./backup.sh origem destino 14.
Fontes
- rsync(1), página do manual, opção
--link-dest. - rsnapshot, snapshots com hardlinks sobre rsync.