Linux: Mini Projeto Fase 4 — Script de Backup Automatizado

O projeto final da Fase 4. Junte gerenciamento de processos, gerenciador de pacotes, arquivamento e compactação e todas as habilidades de scripting Bash para construir uma ferramenta de backup automatizada que dá pra usar de verdade.

📋 Pré-requisitos: Você já deve conhecer

1. O Que Você Vai Aprender


2. O Disco do Servidor do Xiaoming Que Deu Defeito

(1) O Problema: Disco Queimou, Dados Perdidos

O disco do servidor do Xiaoming queimou de uma hora pra outra, levando 3 dias de dados junto. Aí ele percebeu que não tinha backup nenhum — e fazer backup manual era tedioso demais, ele sempre esquecia.

(2) Solução: Backup Automatizado

Ele usou tudo que aprendeu na Fase 4 pra montar uma ferramenta de backup completa:

BASH
# Backup local
./backup.sh -s /var/www -d /backup

# Backup remoto
./backup.sh -s /var/www -d user@backup:/backups

# Adiciona no cron pra rodar todo dia
0 2 * * * /opt/scripts/backup.sh -s /var/www -d /backup

(3) O Benefício: Sem Mais Medo de Perder Dados

O script de backup roda automaticamente todo dia às 2 da manhã, sincronizando de forma incremental com um destino remoto. Agora o Xiaoming consegue dormir em paz.


3. Pontos-Chave

(1) Arquitetura de um Script de Backup

Um bom script de backup precisa ser modular:

TEXT

backup.sh
├── Carregamento de configuração
├── Checagens (diretório/dependências/espaço)
├── Compactação (origem → tar.gz)
├── Sincronização (rsync local ou remoto)
├── Limpeza (apagar backups expirados)
├── Logging
└── Notificação (sucesso/falha)

(2) Estratégia de Backup Incremental

TEXT
# Método 1: rsync --link-dest (recomendado)
# Usa hard links; cada dia parece um diretório completo, mas só guarda as mudanças
rsync -av --link-dest=/backup/yesterday/ /data/ /backup/today/

> 💡 Dica: O princípio do backup incremental com `rsync --link-dest` aproveita os hard links — os arquivos inalterados no backup de "hoje" são apenas hard links apontando pros arquivos de "ontem", sem consumir espaço extra em disco; só os arquivos modificados são realmente gravados como dados novos. Por fora, cada dia parece um diretório completo, mas o uso de disco é praticamente incremental.

# Método 2: transferência incremental do rsync
# O rsync só transfere as partes modificadas por padrão
rsync -avz --delete /data/ user@host:/backup/

> ⚠️ Atenção: O `rsync --delete` faz o destino ficar idêntico à origem — arquivos no destino que não existem na origem serão apagados. Se você errar o caminho da origem (por exemplo, esquecer a `/` no final), pode esvaziar o destino sem querer. Rode com `--dry-run` antes pra previsualizar.

# Método 3: arquivos nomeados por data
backup-2026-07-07.tar.gz
backup-2026-07-08.tar.gz  # Dois arquivos independentes, mas ocupam mais espaço

ℹ️ Nota: Estratégias profissionais de backup seguem a "regra 3-2-1" — 3 cópias dos dados, 2 mídias de armazenamento diferentes, 1 backup off-site. Backup local + rsync remoto formam a base dessa estratégia. Confiar só em tar.gz local não basta — uma falha no disco destruiria a origem e o backup juntos.

(3) Agendamento com cron

TEXT
# formato do cron: minute hour day month weekday command
# Roda o backup todo dia às 2:00 da manhã
0 2 * * * /opt/scripts/backup.sh > /dev/null 2>&1

# A cada hora
0 * * * * /opt/scripts/backup-hourly.sh

# Toda segunda às 3:00
0 3 * * 1 /opt/scripts/backup-weekly.sh

(4) ▶ Exemplo: Verificar Espaço em Disco Para Backup

BASH
#!/bin/bash
# Verifica se o diretório de backup tem espaço suficiente

check_space() {
    local dest="$1"
    local min_gb="${2:-1}"
    
    local available=$(df -BG "$dest" | tail -1 | awk '{print $4}' | tr -d 'G')
    
    if [ "$available" -lt "$min_gb" ]; then
        echo "Error: Insufficient disk space (available: ${available}G, needed: ${min_gb}G)"
        return 1
    fi
    
    echo "Disk space sufficient: ${available}G"
    return 0
}

Saída:

TEXT
Disk space sufficient: 50G

(5) ▶ Exemplo: Função de Logging

TEXT
#!/bin/bash

LOG_FILE="/var/log/backup.log"

log() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

log "INFO" "Backup started"
log "ERROR" "Backup failed: insufficient disk space"
log "INFO" "Backup complete"

(6) ▶ Exemplo: Mecanismo de Lock Para Evitar Execuções Duplas

TEXT
#!/bin/bash

LOCK_FILE="/tmp/backup.lock"

# Tenta adquirir o lock
exec 200>"$LOCK_FILE"
flock -n 200 || {
    echo "Backup script is already running (lock file: $LOCK_FILE)"
    exit 1
}

# ... lógica de backup ...

# O lock é liberado automaticamente quando o script termina

(7) ▶ Exemplo: Limpar Backups Expirados

TEXT
#!/bin/bash

cleanup_old_backups() {
    local backup_dir="$1"
    local retention_days="${2:-30}"
    
    log "INFO" "Cleaning backups older than $retention_days days..."
    
    local count=0
    while IFS= read -r -d '' file; do
        rm -f "$file"
        count=$((count + 1))
    done < <(find "$backup_dir" -name "*.tar.gz" -type f -mtime +$retention_days -print0)
    
    if [ $count -gt 0 ]; then
        log "INFO" "Cleaned up $count expired files"
    fi
}

(8) ▶ Exemplo Abrangente: Script Completo de Backup

BASH
#!/bin/bash
# backup.sh - Ferramenta automatizada de backup
# Uso: ./backup.sh -s <diretorio_origem> -d <diretorio_destino> [-r <dias_retencao>] [-e <padrao_excluir>] [-v]

set -euo pipefail

# ====== Configuração ======
VERSION="1.0.0"
LOG_FILE="/var/log/backup.log"
LOCK_FILE="/tmp/backup.lock"
DEFAULT_RETENTION=30

# ====== Cores ======
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# ====== Funções ======

usage() {
    cat << EOF
Ferramenta de Backup v${VERSION}

Uso: $0 -s <diretorio_origem> -d <diretorio_destino> [opcoes]

Obrigatorio:
  -s <diretorio_origem>      Diretorio de origem para backup
  -d <diretorio_destino>     Destino do backup (caminho local ou usuario@host:/caminho)

Opcoes:
  -r <dias>                  Dias de retencao (padrao: $DEFAULT_RETENTION)
  -e <padrao>                Padrao para excluir (pode ser usado varias vezes, ex.: -e node_modules -e .git)
  -v                         Saida verbosa
  -t                         Modo de teste (nenhum backup e criado)
  -h                         Mostrar ajuda
EOF
    exit 0
}

log() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo -e "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

info()    { log "INFO" "$1"; }
success() { log "INFO" "${GREEN}$1${NC}"; }
warn()    { log "WARN" "${YELLOW}$1${NC}"; }
error()   { log "ERROR" "${RED}$1${NC}"; }

check_deps() {
    for cmd in rsync tar; do
        if ! command -v "$cmd" &> /dev/null; then
            error "Required command '$cmd' not found"
            exit 1
        fi
    done
}

check_disk_space() {
    local dir="$1"
    local min_gb="${2:-1}"
    
    if [[ "$dir" != *:* ]]; then
        local avail=$(df -BG "$dir" 2>/dev/null | tail -1 | awk '{print $4}' | tr -d 'G')
        if [ "$avail" -lt "$min_gb" ]; then
            error "Insufficient disk space: ${avail}G available, ${min_gb}G needed"
            return 1
        fi
        info "Disk space: ${avail}G available"
    fi
}

cleanup_old() {
    local dir="$1"
    local days="$2"
    
    if [[ "$dir" != *:* ]]; then
        local count=$(find "$dir" -name "*.tar.gz" -type f -mtime +$days -delete -print | wc -l)
        if [ $count -gt 0 ]; then
            info "Cleaned up $count expired backups"
        fi
    fi
}

# ====== Mecanismo de lock ======
exec 200>"$LOCK_FILE"
flock -n 200 || {
    error "Backup script is already running (PID: $(cat $LOCK_FILE 2>/dev/null))"
    exit 1
}
echo $$ > "$LOCK_FILE"

# ====== Tratamento de sinais ======
cleanup() {
    rm -f "$LOCK_FILE"
    info "Backup script exiting"
}
trap cleanup EXIT INT TERM

# ====== Parsing de parâmetros ======
SOURCE=""
DEST=""
RETENTION=$DEFAULT_RETENTION
EXCLUDE=()
VERBOSE=false
DRY_RUN=false

while getopts "s:d:r:e:vth" opt; do
    case $opt in
        s) SOURCE="$OPTARG" ;;
        d) DEST="$OPTARG" ;;
        r) RETENTION="$OPTARG" ;;
        e) EXCLUDE+=("$OPTARG") ;;
        v) VERBOSE=true ;;
        t) DRY_RUN=true ;;
        h) usage ;;
        *) usage ;;
    esac
done

# ====== Validação ======
if [ -z "$SOURCE" ] || [ -z "$DEST" ]; then
    error "Must specify -s (source directory) and -d (destination)"
    usage
fi

if [ ! -d "$SOURCE" ]; then
    error "Source directory '$SOURCE' does not exist"
    exit 1
fi

# ====== Lógica principal ======
info "========================================"
info "Backup Tool v${VERSION}"
info "Source: $SOURCE"
info "Destination: $DEST"
info "Retention: $RETENTION days"
if [ "$DRY_RUN" = true ]; then
    warn "Test mode - no actual backup will be created"
fi
info "========================================"

# 1. Checar dependências e espaço
check_deps
check_disk_space "$DEST"

# 2. Gerar nome do arquivo
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_$(basename "$SOURCE")_${TIMESTAMP}.tar.gz"

# 3. Montar argumentos de exclusão
EXCLUDE_ARGS=""
for pattern in "${EXCLUDE[@]}"; do
    EXCLUDE_ARGS="$EXCLUDE_ARGS --exclude=$pattern"
done

# 4. Criar backup compactado
if [ "$DRY_RUN" = false ]; then
    info "Creating compressed backup: $BACKUP_NAME"
    
    # Backup local
    if [[ "$DEST" != *:* ]]; then
        mkdir -p "$DEST"
        eval tar -czf "${DEST}/${BACKUP_NAME}" $EXCLUDE_ARGS "$SOURCE"
        success "✅ Local backup complete: ${DEST}/${BACKUP_NAME}"
        
        # Sincronizar com remoto
        if [ "$VERBOSE" = true ]; then
            ls -lh "${DEST}/${BACKUP_NAME}"
        fi
    else
        # Backup remoto - compacta local e depois usa rsync
        local_temp="/tmp/${BACKUP_NAME}"
        eval tar -czf "$local_temp" $EXCLUDE_ARGS "$SOURCE"
        info "Transferring to remote: $DEST"
        rsync -avz --progress "$local_temp" "${DEST}/${BACKUP_NAME}"
        rm -f "$local_temp"
        success "✅ Remote backup complete: ${DEST}/${BACKUP_NAME}"
    fi
else
    warn "Test mode: Skipping backup creation"
fi

# 5. Limpar backups expirados
if [ "$DRY_RUN" = false ] && [ $RETENTION -gt 0 ]; then
    cleanup_old "$DEST" $RETENTION
fi

# 6. Resumo do backup
info "Backup summary:"
info "  Source: $SOURCE"
info "  Destination: $DEST"
if [ "$DRY_RUN" = false ]; then
    info "  File: ${BACKUP_NAME}"
    if [[ "$DEST" != *:* ]]; then
        info "  Size: $(du -h "${DEST}/${BACKUP_NAME}" | cut -f1)"
    fi
fi
info "========================================"

success "✅ Backup operation complete"
TEXT
# Exemplos de uso

# 1. Uso básico
./backup.sh -s /var/www/myapp -d /backup

# 2. Excluir diretórios + reter 7 dias
./backup.sh -s /var/www/myapp -d /backup -e node_modules -e .git -r 7

# 3. Backup remoto
./backup.sh -s /var/www -d user@backup-server:/backups -v

# 4. Modo de teste (sem criar backup de verdade)
./backup.sh -s /var/www -d /backup -t

# 5. Adicionar ao cron (todo dia às 2h)
crontab -e
# Adicione:
0 2 * * * /opt/scripts/backup.sh -s /var/www/myapp -d /backup -e node_modules -r 30 > /dev/null 2>&1

❓ Perguntas Frequentes

P: Como evitar que o script de backup rode duas vezes ao mesmo tempo? R: Use file locking (flock): quando o script inicia, ele tenta criar um arquivo de lock. Se o lock já existe, o script sai. Assim, mesmo que o cron dispare de novo antes do backup terminar, dois backups não rodam em paralelo.

P: Por quanto tempo guardar os arquivos de backup? R: Depende do espaço disponível e das exigências de conformidade. Estratégia comum: backups diários por 30 dias, semanais por 3 meses, mensais por 1 ano. Use --link-dest ou frequências diferentes no cron pra implementar.

P: Como verificar a integridade do backup? R: Use sha256sum pra gerar um arquivo de checksum: sha256sum backup.tar.gz > backup.sha256. Na hora de restaurar, valide com sha256sum -c backup.sha256.

P: O rsync continua de onde parou se a transferência for interrompida? R: Sim. O rsync transfere de forma incremental por padrão. Mesmo que um arquivo em transferência seja interrompido, a próxima execução do rsync só vai transferir o trecho que faltou. Adicione --partial pra manter os arquivos parcialmente transferidos.

P: O que é preciso configurar pra fazer backup num servidor remoto? R: 1) Autenticação por chave SSH (ssh-keygen + ssh-copy-id). 2) O diretório de destino remoto precisa existir. 3) O usuário remoto precisa ter permissão de escrita. 4) Opcional: configure login SSH sem senha.


📖 Resumo


📝 Exercícios

  1. Básico (⭐): Crie um diretório de teste com alguns arquivos, use backup.sh pra fazer backup em /tmp/test-backup/ e depois adicione -e pra excluir arquivos *.log e confirme que a exclusão funcionou
  2. Intermediário (⭐⭐): Configure o cron pra rodar um backup a cada hora (observe pelo menos 3 execuções) e troque o backup para rsync remoto (se você tiver mais de uma máquina disponível)
  3. Desafio (⭐⭐⭐): Adicione notificações por e-mail ou do sistema (wall, mail ou webhook) no script, implemente verificação de integridade com sha256sum e dispare alertas automáticos em caso de falha
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%