>_ josetilabs.com
← blog

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.

bash
#!/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:

text
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.

Terminal com o ciclo de backup com rsync: três snapshots de uma origem de 4,1 MB ocupam 4,1 MB no total graças aos hardlinks do --link-dest, a rotação apaga a versão mais antiga e a restauração do nota.txt é conferida por sha256
O ciclo completo: quatro execuções, a quarta apaga a mais antiga, e a restauração confere o hash.

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

Leia também

// Por e-mail

Os textos publicados aqui também vão por e-mail. O envio ainda não começou: quem deixar o endereço agora entra na primeira lista.

Guardo só o seu e-mail, a data e o registro desta autorização. Sem cookies, sem rastreador e nada compartilhado com terceiros. Detalhes na política de privacidade.