Como te comenté en el capítulo anterior, cuando empiezas a automatizar Git con Bash te das cuenta de que hay un tipo de script que separa al aficionado del profesional: el backup. Un backup casero es un tar czf suelto o un cp -r manual. Pero, ¿y si pierdes datos y no puedes recuperarlos? Ahí es donde entra el backup profesional.
Un backup profesional es planificado (sigue una estrategia full + incremental), rotado (no acumula infinitamente, aplica GFS), verificado (comprueba integridad con checksums), cifrado (protege datos sensibles con GPG), notificado (informa del resultado por Telegram o email), configurable (se adapta a distintos entornos sin tocar el código), remoto (replica en SSH o cloud) y restaurable (tiene un script de restauración que no requiere conocimientos del sistema original).
En este capítulo vas a construir exactamente eso: un sistema de backup profesional completo, modular y listo para producción.
La filosofía es clara: no se trata de copiar archivos. Se trata de diseñar un sistema que garantice que puedes recuperar tus datos cuando realmente los necesites. El backup que no se prueba no es un backup.
Estrategias de backup
Backup completo (full)
Copia todos los datos seleccionados. Es la base sobre la que se apoyan las demás estrategias.
Ventajas: Restauración más simple (un solo archivo), no depende de otros backups.
Desventajas: Ocupa más espacio y tiempo. Si solo haces fulls diarios, multiplicas el almacenamiento.
# Backup full con tar
tar czf "backup_full_$(date +%Y%m%d).tar.gz" /ruta/datos/
Backup incremental
Copia solo los datos que han cambiado desde el último backup (sea full o incremental).
Ventajas: Muy rápido y eficiente en espacio. Ideal para backups frecuentes.
Desventajas: Restauración lenta (necesitas el full + todos los incrementales en orden). Si falla un incremental intermedio, pierdes los siguientes.
Restauración incremental:
full (domingo) → inc1 (lunes) → inc2 (martes) → inc3 (miércoles)
Para restaurar a miércoles: necesitas full + inc1 + inc2 + inc3
Backup diferencial
Copia solo los datos que han cambiado desde el último full.
Ventajas: Restauración más rápida que incremental (solo full + último diferencial). Menos riesgo de pérdida de cadena.
Desventajas: Cada diferencial crece hasta el siguiente full. Ocupa más espacio que los incrementales a largo plazo.
Restauración diferencial:
full (domingo) → dif1 (lunes) → dif2 (martes) → dif3 (miércoles)
Para restaurar a miércoles: necesitas full + dif3
¿Cuándo usar cada uno?
- Full: Frecuencia semanal/mensual. Espacio alto. Backup lento, restore rápido. Complejidad baja.
- Incremental: Frecuencia diaria/cada hora. Espacio bajo. Backup rápido, restore lento. Complejidad media.
- Diferencial: Frecuencia diaria. Espacio medio. Backup medio, restore medio. Complejidad media.
Estrategia recomendada (GFS): un full semanal + incrementales diarios. O bien: un full mensual + diferenciales semanales.
Rotación GFS (Grandfather-Father-Son)
El esquema de rotación
GFS es el estándar de la industria para rotación de backups. Sus tres niveles representan:
- Son (Hijo): Backup diario. Cada día. Retención: 7 días (última semana).
- Father (Padre): Backup semanal. Cada semana. Retención: 4 semanas (último mes).
- Grandfather (Abuelo): Backup mensual. Cada mes. Retención: 12 meses (último año).
Cómo funciona:
- Los backups diarios (Son) se guardan en carpetas
lun,mar,mie, etc. Cada día se sobrescribe el de la semana anterior. - El backup del domingo se promociona a Father: se copia a la carpeta
semana-1,semana-2, etc. - El último backup del mes se promociona a Grandfather: se copia a
mes-01,mes-02, etc.
Implementación práctica
# Estructura de directorios GFS
backups/
├── daily/ # Son: backups diarios (7 días)
│ ├── 0-mon/
│ ├── 1-tue/
│ ├── 2-wed/
│ ├── 3-thu/
│ ├── 4-fri/
│ ├── 5-sat/
│ └── 6-sun/
├── weekly/ # Father: backups semanales (4 semanas)
│ ├── semana-01/
│ ├── semana-02/
│ ├── semana-03/
│ └── semana-04/
└── monthly/ # Grandfather: backups mensuales (12 meses)
├── mes-01/
├── mes-02/
...
└── mes-12/
# Función para determinar el directorio Daily (Son)
dia_semana() {
echo "$(date +%u)" # 1=lunes, 7=domingo
}
# Diario: usar el número del día como carpeta
DAILY_DIR="backups/daily/$(($(date +%u) - 1))-$(date +%A | tr '[:upper:]' '[:lower:]')"
# Semanal: si es domingo, copiar a weekly (Father)
if [[ $(date +%u) -eq 7 ]]; then
WEEKLY_DIR="backups/weekly/semana-$(date +%V)" # Nº de semana
fi
# Mensual: si es día 1, copiar a monthly (Grandfather)
if [[ $(date +%d) -eq 1 ]]; then
MONTHLY_DIR="backups/monthly/mes-$(date +%m)"
fi
Políticas de retención
- Diario (Son): 7 días. Al día 8 se sobrescribe.
- Semanal (Father): 4-8 semanas. Al cumplir N semanas se borra.
- Mensual (Grandfather): 12 meses. Al año se archiva o borra.
- Anual: 3-7 años. Fuera del GFS normal.
rsync — El caballo de batalla
rsync local y remoto (SSH)
rsync es la herramienta estrella del backup profesional. Copia solo las diferencias, comprime en tránsito y puede reanudar transferencias interrumpidas.
# rsync local
rsync -avh --delete /origen/datos/ /destino/backup/
# rsync remoto por SSH
rsync -avhz --delete -e "ssh -p 2222" /origen/datos/ usuario@servidor:/destino/backup/
# rsync con enlace simbólico para backups incrementales
rsync -avh --delete --link-dest=/backups/full_semanal/ /origen/datos/ /backups/incremental_hoy/
Opciones clave de rsync:
-a: Modo archivo (preserva permisos, dueño, grupo, tiempos, etc.)-v: Verboso.-h: Legible para humanos.-z: Comprime durante la transferencia.--delete: Elimina en destino lo que no existe en origen.--link-dest: Crea hardlinks a archivos sin cambios (incremental instantáneo).-e: Especifica shell remoto (normalmente SSH).
–link-dest para backups incrementales
--link-dest es la joya de rsync para backups incrementales eficientes. Crea hardlinks a archivos que no han cambiado, ocupando cero espacio adicional:
#!/bin/bash
# Backup incremental con --link-dest
ORIGEN="/home/usuario/documentos"
DESTINO="/backups/documentos"
FECHA=$(date +%Y%m%d)
ULTIMO=$(ls -1 "$DESTINO" | tail -1)
# Si existe un backup anterior, usar --link-dest
if [[ -n "$ULTIMO" && -d "$DESTINO/$ULTIMO" ]]; then
rsync -ah --delete --link-dest="$DESTINO/$ULTIMO" "$ORIGEN/" "$DESTINO/$FECHA/"
else
# Primer backup: full
rsync -ah "$ORIGEN/" "$DESTINO/$FECHA/"
fi
Con --link-dest, cada backup parece completo (todos los archivos están presentes), pero los archivos sin cambios no ocupan espacio porque son hardlinks al backup anterior.
rsync con filtros y exclusiones
# Excluir patrones con --exclude
rsync -avh --delete \
--exclude='*.tmp' \
--exclude='node_modules/' \
--exclude='.cache/' \
--exclude='*.log' \
/proyecto/ /backups/proyecto/
# Usar archivo de exclusiones
rsync -avh --delete --exclude-from=/etc/backup_exclude.txt /proyecto/ /backups/proyecto/
# Incluir/excluir por patrón
rsync -avh --include='*/' --include='*.pdf' --exclude='*' /documentos/ /backups/pdfs/
tar y compresión
gzip / bzip2 / xz — Cuándo usar cada uno
- gzip (
.tar.gz): Ratio bajo, velocidad rápida. Ideal para backups diarios y logs. - bzip2 (
.tar.bz2): Ratio medio, velocidad media. Ideal para backups semanales. - xz (
.tar.xz): Ratio alto, velocidad lenta. Ideal para backups mensuales y archivado.
# Compresión con gzip (rápido)
tar czf backup_$(date +%Y%m%d).tar.gz /ruta/datos/
# Compresión con bzip2 (ratio medio)
tar cjf backup_$(date +%Y%m%d).tar.bz2 /ruta/datos/
# Compresión con xz (máxima compresión)
tar cJf backup_$(date +%Y%m%d).tar.xz /ruta/datos/
# Con nivel de compresión explícito
GZIP=-9 tar czf backup_max.tar.gz /ruta/datos/
XZ_OPT=-9 tar cJf backup_max.tar.xz /ruta/datos/
Archivos comprimidos con tar
Opciones avanzadas de tar para backups profesionales:
# Backup con metadatos (dueño, permisos, ACLs, xattrs)
tar --acls --xattrs -czf backup_con_metadatos.tar.gz /ruta/datos/
# Backup excluyendo patrones
tar --exclude='*.log' --exclude='cache/' -czf backup.tar.gz /ruta/datos/
# Backup desde lista de archivos
tar czf backup_lista.tar.gz -T /etc/backup_include.txt
# Dividir en volúmenes (para DVDs, cintas, etc.)
tar cvzf - /ruta/datos/ | split -b 4G - backup_parte_
# Ver contenido sin extraer
tar tzf backup.tar.gz | head -20
Backups con verificación
# Crear backup y generar checksum simultáneamente
tar czf backup_$(date +%Y%m%d).tar.gz /ruta/datos/ \
&& sha256sum backup_$(date +%Y%m%d).tar.gz > backup_$(date +%Y%m%d).sha256
# Verificar integridad del tar
tar tzf backup.tar.gz > /dev/null && echo "✓ Backup íntegro" || echo "✗ Backup corrupto"
# Extraer y verificar contenido
tar -tf backup.tar.gz | sort > /tmp/contenido_esperado.txt
diff /tmp/contenido_esperado.txt /tmp/manifiesto_original.txt && echo "✓ Contenido coincide"
Cifrado con GPG
Cifrar backups con gpg
# Cifrado simétrico (contraseña)
gpg --symmetric --cipher-algo AES256 --batch --passphrase "MiClaveSecreta" \
-o backup_$(date +%Y%m%d).tar.gz.gpg backup_$(date +%Y%m%d).tar.gz
# Cifrado asimétrico (clave pública)
gpg --encrypt --recipient "mi-email@ejemplo.com" \
-o backup.tar.gz.gpg backup.tar.gz
# Cifrado con gpg2 (en sistemas modernos)
gpg2 --symmetric --cipher-algo AES256 -o backup.tar.gz.gpg backup.tar.gz
Automatización con claves simétricas
Para automatizar el cifrado sin intervención humana, usa un archivo de contraseñas:
# Archivo: /etc/backup/passphrase.gpg (protegido con permisos 600)
# Lo creas una vez:
gpg --symmetric --cipher-algo AES256 \
-o /etc/backup/passphrase.gpg /etc/backup/passphrase.txt
# En el script, lo descifras al vuelo:
PASS=$(gpg --decrypt --batch /etc/backup/passphrase.gpg 2>/dev/null)
# Cifrar el backup
gpg --symmetric --cipher-algo AES256 --batch --passphrase "$PASS" \
-o "backup_$(date +%Y%m%d).tar.gz.gpg" backup.tar.gz
# Limpiar variable
unset PASS
Descifrado y verificación
# Descifrar
gpg --decrypt -o backup_recuperado.tar.gz backup.tar.gz.gpg
# Verificar que el descifrado funciona antes de borrar el original
gpg --decrypt --batch --passphrase "$PASS" \
-o /tmp/verify_backup.tar.gz backup.tar.gz.gpg \
&& echo "✓ Descifrado correcto" \
|| echo "✗ Error al descifrar"
# Comparar checksum
sha256sum backup.tar.gz /tmp/verify_backup.tar.gz
Verificación e integridad
Checksums y manifiestos
Cada backup debería generar un manifiesto que permita verificar su integridad más adelante:
# Generar checksums de todos los archivos del backup
crear_manifiesto() {
local backup_dir="$1"
local manifiesto="$2"
find "$backup_dir" -type f -exec sha256sum {} \; > "$manifiesto"
echo "✓ Manifiesto creado: $manifiesto ($(wc -l < "$manifiesto") archivos)"
}
# Verificar integridad contra el manifiesto
verificar_integridad() {
local manifiesto="$1"
if sha256sum -c "$manifiesto" --quiet 2>/dev/null; then
echo "✓ Todos los archivos íntegros"
return 0
else
echo "✗ Error de integridad detectado"
sha256sum -c "$manifiesto" 2>/dev/null | grep -v "OK$"
return 1
fi
}
Pruebas de restauración
Un backup que no se prueba no es un backup. Esto te puede ahorrar más de un disgusto. Automatiza una prueba de restauración:
probar_restauracion() {
local backup_file="$1"
local temp_dir
temp_dir=$(mktemp -d)
echo "🧪 Probando restauración en $temp_dir..."
# Extraer el backup
case "$backup_file" in
*.tar.gz) tar xzf "$backup_file" -C "$temp_dir" ;;
*.tar.bz2) tar xjf "$backup_file" -C "$temp_dir" ;;
*.tar.xz) tar xJf "$backup_file" -C "$temp_dir" ;;
*.gpg) gpg --decrypt -o /dev/null "$backup_file" \
&& echo "✓ Backup cifrado se descifra correctamente" ;;
esac
# Verificar que se extrajo contenido
local count
count=$(find "$temp_dir" -type f 2>/dev/null | wc -l)
if [[ "$count" -gt 0 ]]; then
echo "✓ Restauración correcta: $count archivos extraídos"
rm -rf "$temp_dir"
return 0
else
echo "✗ Fallo en restauración: sin archivos extraídos"
rm -rf "$temp_dir"
return 1
fi
}
Logging y notificaciones
Logging con logger/syslog
# Logging al syslog del sistema
logger -t backup_pro -p user.info "Backup iniciado: $(date)"
logger -t backup_pro -p user.notice "Backup completado: $SIZE bytes en $TIME segundos"
logger -t backup_pro -p user.err "Backup FALLIDO: $ERROR_MSG"
# Logging a archivo propio
log_backup() {
local nivel="$1"
local mensaje="$2"
local logfile="/var/log/backup_pro.log"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$nivel] $mensaje" >> "$logfile"
# También enviar a syslog
logger -t backup_pro -p "user.${nivel,,}" "$mensaje"
}
Notificaciones por email
notificar_email() {
local asunto="$1"
local cuerpo="$2"
local destinatario="${BACKUP_EMAIL:-admin@ejemplo.com}"
echo "$cuerpo" | mail -s "[Backup] $asunto" "$destinatario"
}
# Uso
notificar_email "Backup exitoso" "Backup de $ORIGEN completado.\nTamaño: $SIZE\nDuración: $TIME segundos"
Notificaciones por Telegram
notificar_telegram() {
local mensaje="$1"
local token="${TELEGRAM_TOKEN:-}"
local chat_id="${TELEGRAM_CHAT_ID:-}"
[[ -z "$token" || -z "$chat_id" ]] && return 0
curl -s -X POST "https://api.telegram.org/bot${token}/sendMessage" \
-d "chat_id=${chat_id}" \
-d "text=${mensaje}" \
-d "parse_mode=Markdown" > /dev/null
}
# Uso
notificar_telegram "✅ *Backup completado*
* Origen: /home/usuario
* Tamaño: 2.3 GB
* Duración: 45s
* Destino: rsync://server/backups"
Notificaciones desktop (notify-send)
notificar_desktop() {
local tipo="$1" # success, error, info
local mensaje="$2"
case "$tipo" in
success) icono="dialog-information" ;;
error) icono="dialog-error" ;;
info) icono="dialog-information" ;;
esac
notify-send -i "$icono" "Backup Pro" "$mensaje"
}
# Solo si hay entorno gráfico
if [[ -n "$DISPLAY" ]]; then
notificar_desktop "success" "Backup completado correctamente"
fi
Backups configurables
Archivos de configuración (INI/conf)
Un script profesional nunca tiene rutas hardcodeadas. Te recomiendo usar un archivo de configuración:
# Archivo: /etc/backup_pro.conf
# Configuración del sistema de backup profesional
# Origen y destino
ORIGENES=(
"/home/usuario/documentos"
"/etc"
"/var/www"
)
DESTINO_LOCAL="/backups"
DESTINO_REMOTO="usuario@servidor:/backups"
# Estrategia
ESTRATEGIA="GFS" # GFS | SIMPLE
COMPRESION="gzip" # gzip | bzip2 | xz | none
CIFRAR=true
VERIFICAR=true
# Rotación
RETENCION_DIARIA=7
RETENCION_SEMANAL=4
RETENCION_MENSUAL=12
# Notificaciones
NOTIFICAR_EMAIL=true
EMAIL_DESTINO="admin@ejemplo.com"
NOTIFICAR_TELEGRAM=false
TELEGRAM_TOKEN=""
TELEGRAM_CHAT_ID=""
# Exclusiones
EXCLUIR_ARCHIVOS=(
"*.tmp"
"*.log"
"*.cache"
"node_modules"
".git"
)
# SSH
PUERTO_SSH=22
CLAVE_SSH="/root/.ssh/backup_key"
Cargar configuración en el script
# Cargar configuración
CONFIG_FILE="${1:-/etc/backup_pro.conf}"
if [[ -f "$CONFIG_FILE" ]]; then
source "$CONFIG_FILE"
else
echo "ERROR: Archivo de configuración no encontrado: $CONFIG_FILE"
exit 1
fi
Backups remotos y cloud
rsync sobre SSH
# Backup remoto completo con rsync+SSH
backup_remoto_rsync() {
local origen="$1"
local destino="$2"
local ssh_key="${CLAVE_SSH:-}"
local ssh_port="${PUERTO_SSH:-22}"
local ssh_cmd="ssh -p $ssh_port"
[[ -n "$ssh_key" ]] && ssh_cmd="$ssh_cmd -i $ssh_key"
rsync -avhz --delete -e "$ssh_cmd" "$origen" "$destino"
return $?
}
rclone (Google Drive, Dropbox, S3)
rclone es la navaja suiza del cloud. Soporta 40+ proveedores.
# Configurar rclone (una vez)
rclone config
# Backup a Google Drive
rclone sync /backups/diario/ remote:drive/backups/ \
--progress --verbose --checksum
# Backup a Dropbox
rclone sync /backups/diario/ remote:dropbox/backups/ \
--progress --verbose
# Backup a S3 compatible (Backblaze B2, Wasabi, etc.)
rclone sync /backups/diario/ remote:s3-bucket/backups/ \
--progress --verbose
s3cmd (AWS S3)
# Backup a AWS S3
s3cmd sync /backups/diario/ s3://mi-bucket/backups/ \
--progress --verbose --delete-removed
# Con opciones de cifrado del lado del servidor
s3cmd sync /backups/diario/ s3://mi-bucket/backups/ \
--server-side-encryption --progress
restic — Backup moderno con deduplicación
restic es la herramienta más moderna: deduplicación, cifrado nativo, instantáneas y compatible con múltiples backends (local, SFTP, S3, Google Cloud, Azure, etc.).
# Inicializar repositorio restic
restic init --repo /backups/restic-repo
# Backup con restic (deduplicación automática)
restic --repo /backups/restic-repo backup /home/usuario/documentos
# Backup a S3
restic -r s3:s3.amazonaws.com/mi-bucket/restic backup /home/usuario/documentos
# Listar instantáneas
restic -r /backups/restic-repo snapshots
# Restaurar instantánea específica
restic -r /backups/restic-repo restore latest --target /tmp/restauracion/
# Olvidar instantáneas antiguas (política de retención)
restic -r /backups/restic-repo forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12
Backups en paralelo
Cuanto más datos tengas, más necesitarás paralelizar. xargs -P es tu aliado:
# Backup en paralelo de múltiples directorios
respaldar_directorio() {
local dir="$1"
local destino="$2"
local nombre
nombre=$(basename "$dir")
echo "→ Respaldo de $nombre..."
rsync -avh "$dir/" "${destino}/${nombre}/" \
--log-file="/tmp/backup_${nombre}.log" 2>/dev/null
if [[ $? -eq 0 ]]; then
echo "✓ $nombre completado"
return 0
else
echo "✗ $nombre FALLIDO"
return 1
fi
}
export -f respaldar_directorio # Necesario para xargs
# Directorios a respaldar
DIRECTORIOS=(
"/home/usuario/documentos"
"/home/usuario/fotos"
"/home/usuario/musica"
"/etc"
"/var/www"
)
# Ejecutar hasta 3 en paralelo
printf "%s\0" "${DIRECTORIOS[@]}" | xargs -0 -I {} -P 3 \
bash -c 'respaldar_directorio "$@"' _ {} "/backups/diarios"
echo "✓ Todos los backups paralelos completados"
Programación
Cron scheduling
# /etc/crontab o crontab -e
# Backup full semanal (domingo 3 AM)
0 3 * * 7 /usr/local/bin/backup_pro.sh /etc/backup_pro.conf full
# Backup incremental diario (lunes-sábado 3 AM)
0 3 * * 1-6 /usr/local/bin/backup_pro.sh /etc/backup_pro.conf incremental
# Verificación semanal de integridad (domingo 5 AM)
0 5 * * 7 /usr/local/bin/backup_pro.sh /etc/backup_pro.conf verify
Systemd timer
/etc/systemd/system/backup-pro.service:
[Unit]
Description=Backup Profesional
Documentation=https://atareao.es/tutorial/scripts-en-bash
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup_pro.sh /etc/backup_pro.conf
User=root
StandardOutput=journal
StandardError=journal
/etc/systemd/system/backup-pro.timer:
[Unit]
Description=Timer para Backup Profesional
Requires=backup-pro.service
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=30m
[Install]
WantedBy=timers.target
# Activar
sudo systemctl daemon-reload
sudo systemctl enable --now backup-pro.timer
# Ver estado
sudo systemctl list-timers backup-pro.timer
sudo journalctl -u backup-pro.service -n 50
Script de restauración
Un backup sin plan de restauración es solo una ilusión. Este script permite restaurar desde cualquier punto:
#!/bin/bash
# restore_pro.sh — Script de restauración profesional
# Uso: restore_pro.sh [opciones] <archivo_o_directorio>
# restore_pro.sh --latest
# restore_pro.sh --date 2026-06-15
set -euo pipefail
CONFIG="${1:-/etc/backup_pro.conf}"
[[ -f "$CONFIG" ]] && source "$CONFIG"
DESTINO_RESTORE="/tmp/restore_$(date +%Y%m%d_%H%M%S)"
FECHA=""
ARCHIVO=""
MODO=""
usage() {
cat << EOF
Uso: restore_pro.sh [opciones]
Opciones:
--latest Restaurar el backup más reciente
--date YYYY-MM-DD Restaurar backup de una fecha específica
--file <path> Restaurar un archivo específico del backup
--list Listar backups disponibles
--dest <dir> Directorio destino de la restauración
-h, --help Muestra esta ayuda
EOF
exit 0
}
listar_backups() {
echo "=== Backups disponibles ==="
echo ""
echo "--- Diarios (Son) ---"
ls -1 "$DESTINO_LOCAL/daily/" 2>/dev/null | while read -r dia; do
echo " $dia"
done
echo ""
echo "--- Semanales (Father) ---"
ls -1 "$DESTINO_LOCAL/weekly/" 2>/dev/null
echo ""
echo "--- Mensuales (Grandfather) ---"
ls -1 "$DESTINO_LOCAL/monthly/" 2>/dev/null
}
restaurar() {
local origen="$1"
local destino="${2:-$DESTINO_RESTORE}"
mkdir -p "$destino"
echo "🔄 Restaurando desde: $origen"
echo "📂 Destino: $destino"
if [[ -d "$origen" ]]; then
rsync -avh "$origen/" "$destino/"
elif [[ -f "$origen" ]]; then
case "$origen" in
*.tar.gz) tar xzf "$origen" -C "$destino" ;;
*.tar.bz2) tar xjf "$origen" -C "$destino" ;;
*.tar.xz) tar xJf "$origen" -C "$destino" ;;
*.gpg) gpg --decrypt -o "${origen%.gpg}" "$origen" \
&& tar xzf "${origen%.gpg}" -C "$destino" ;;
*) echo "Formato no soportado: $origen"; return 1 ;;
esac
fi
if [[ $? -eq 0 ]]; then
echo "✓ Restauración completada en: $destino"
echo " Archivos: $(find "$destino" -type f | wc -l)"
else
echo "✗ Error en la restauración"
return 1
fi
}
# Parsear argumentos
while [[ $# -gt 0 ]]; do
case "$1" in
--latest) MODO="latest"; shift ;;
--date) FECHA="$2"; MODO="date"; shift 2 ;;
--file) ARCHIVO="$2"; shift 2 ;;
--list) listar_backups; exit 0 ;;
--dest) DESTINO_RESTORE="$2"; shift 2 ;;
-h|--help) usage ;;
*) echo "Opción desconocida: $1"; usage ;;
esac
done
case "$MODO" in
latest)
ULTIMO=$(find "$DESTINO_LOCAL" -maxdepth 3 -type d -name "*" \
-printf '%T@ %p\n' 2>/dev/null | sort -n | tail -1 | cut -d' ' -f2-)
restaurar "$ULTIMO"
;;
date)
BUSCAR="${DESTINO_LOCAL}/daily/*/${FECHA//-/}"
ENCONTRADO=$(ls -1d $BUSCAR 2>/dev/null | head -1)
if [[ -z "$ENCONTRADO" ]]; then
echo "No se encontró backup para la fecha: $FECHA"
exit 1
fi
restaurar "$ENCONTRADO"
;;
*)
if [[ -n "$ARCHIVO" ]]; then
restaurar "$ARCHIVO"
else
usage
fi
;;
esac
Reportes de backup
Un backup profesional genera reportes automáticos con métricas clave:
generar_reporte() {
local estado="$1" # EXITOSO | FALLIDO | PARCIAL
local origen="$2"
local destino="$3"
local tamano="$4"
local duracion="$5"
local archivos="$6"
local errores="${7:-0}"
local reporte="/var/log/backup_reporte_$(date +%Y%m%d).md"
cat > "$reporte" << EOF
# Reporte de Backup — $(date '+%Y-%m-%d %H:%M:%S')
## Resumen
- **Estado**: $estado
- **Origen**: $origen
- **Destino**: $destino
- **Tamaño**: $tamano
- **Duración**: ${duracion}s
- **Archivos**: $archivos
- **Errores**: $errores
## Detalles
- **Estrategia**: ${ESTRATEGIA:-GFS}
- **Compresión**: ${COMPRESION:-gzip}
- **Cifrado**: ${CIFRAR:-no}
- **Verificación**: ${VERIFICAR:-no}
## Próximo backup
- **Full**: $(date -d 'next sunday 03:00' '+%Y-%m-%d %H:%M')
- **Siguiente**: $(date -d 'tomorrow 03:00' '+%Y-%m-%d %H:%M')
EOF
echo "$reporte"
}
Funciones profesionales
Funciones reutilizables para tu biblioteca personal de backup:
# ──────────────────────────────────────────────
# Funciones profesionales para backups
# ──────────────────────────────────────────────
# backup_rsync_incremental — Backup incremental con --link-dest
# Uso: backup_rsync_incremental <origen> <destino_base>
backup_rsync_incremental() {
local origen="$1"
local destino_base="$2"
local fecha
fecha=$(date +%Y%m%d_%H%M%S)
local destino="${destino_base}/${fecha}"
local ultimo
ultimo=$(ls -1 "$destino_base" 2>/dev/null | sort -r | head -1)
mkdir -p "$destino"
if [[ -n "$ultimo" && -d "${destino_base}/${ultimo}" ]]; then
echo "→ Backup incremental desde: ${destino_base}/${ultimo}"
rsync -ah --delete --link-dest="${destino_base}/${ultimo}" \
"$origen/" "$destino/"
else
echo "→ Primer backup (full) en: $destino"
rsync -ah "$origen/" "$destino/"
fi
echo "✓ Backup completado: $destino"
}
# backup_cifrado_gpg — Cifra un archivo con GPG
# Uso: backup_cifrado_gpg <archivo> [passphrase_file]
backup_cifrado_gpg() {
local archivo="$1"
local pass_file="${2:-/etc/backup/passphrase.gpg}"
if [[ ! -f "$archivo" ]]; then
echo "ERROR: Archivo no encontrado: $archivo"
return 1
fi
local pass
pass=$(gpg --decrypt --batch "$pass_file" 2>/dev/null) || {
echo "ERROR: No se pudo descifrar el archivo de contraseña"
return 1
}
gpg --symmetric --cipher-algo AES256 --batch --passphrase "$pass" \
-o "${archivo}.gpg" "$archivo" || {
echo "ERROR: Falló el cifrado"
return 1
}
unset pass
echo "✓ Archivo cifrado: ${archivo}.gpg"
}
# backup_verificar — Verifica integridad de un backup
# Uso: backup_verificar <archivo_backup>
backup_verificar() {
local backup="$1"
if [[ ! -f "$backup" ]]; then
echo "ERROR: Backup no encontrado: $backup"
return 1
fi
echo "🔍 Verificando: $backup..."
case "$backup" in
*.tar|*.tar.gz|*.tar.bz2|*.tar.xz)
if tar tzf "$backup" > /dev/null 2>&1; then
echo "✓ Backup íntegro"
return 0
fi
;;
*.gpg)
if gpg --list-packets "$backup" > /dev/null 2>&1; then
echo "✓ Archivo cifrado válido"
return 0
fi
;;
*)
if sha256sum -c "${backup}.sha256" > /dev/null 2>&1; then
echo "✓ Checksum verificado"
return 0
fi
;;
esac
echo "✗ Backup CORRUPTO o inválido"
return 1
}
# backup_rotar_gfs — Aplica rotación GFS al directorio de backups
# Uso: backup_rotar_gfs <base_dir> [daily_retention] [weekly_retention] [monthly_retention]
backup_rotar_gfs() {
local base_dir="$1"
local daily_ret="${2:-7}"
local weekly_ret="${3:-4}"
local monthly_ret="${4:-12}"
echo "🔄 Aplicando rotación GFS en: $base_dir"
# Limpiar diarios (mantener solo N más recientes)
if [[ -d "${base_dir}/daily" ]]; then
local diarios
diarios=$(ls -1 "${base_dir}/daily/" | sort | head -n -"$daily_ret")
for d in $diarios; do
rm -rf "${base_dir}/daily/$d"
echo " 🗑 Eliminado daily: $d"
done
fi
# Limpiar semanales
if [[ -d "${base_dir}/weekly" ]]; then
local semanales
semanales=$(ls -1 "${base_dir}/weekly/" | sort | head -n -"$weekly_ret")
for s in $semanales; do
rm -rf "${base_dir}/weekly/$s"
echo " 🗑 Eliminado weekly: $s"
done
fi
# Limpiar mensuales
if [[ -d "${base_dir}/monthly" ]]; then
local mensuales
mensuales=$(ls -1 "${base_dir}/monthly/" | sort | head -n -"$monthly_ret")
for m in $mensuales; do
rm -rf "${base_dir}/monthly/$m"
echo " 🗑 Eliminado monthly: $m"
done
fi
echo "✓ Rotación GFS completada"
}
Script completo: backup_pro.sh
#!/bin/bash
# ═══════════════════════════════════════════════════════════
# backup_pro.sh — Sistema de Backup Profesional
# Versión: 1.0.0
# Uso: backup_pro.sh [config_file] [modo]
# ═══════════════════════════════════════════════════════════
set -euo pipefail
# ── Constantes ────────────────────────────────────────────
SCRIPT_NOMBRE=$(basename "$0")
SCRIPT_DIR=$(dirname "$(readlink -f "$0")")
PID=$$
TIMESTAMP_INICIO=$(date +%Y%m%d_%H%M%S)
FECHA_LOG=$(date '+%Y-%m-%d %H:%M:%S')
# ── Configuración por defecto ──────────────────────────────
CONFIG_FILE="${1:-/etc/backup_pro.conf}"
MODO="${2:-incremental}" # full | incremental | verify | report
LOG_FILE="/var/log/backup_pro.log"
ERROR_COUNT=0
ARCHIVOS_COUNT=0
TAMANO_TOTAL=0
TIEMPO_INICIO=0
TIEMPO_FIN=0
# ── Colores (para output legible) ─────────────────────────
VERDE='\033[0;32m'
ROJO='\033[0;31m'
AMARILLO='\033[1;33m'
AZUL='\033[0;34m'
NC='\033[0m'
# ── Funciones auxiliares ──────────────────────────────────
_uso() {
cat << EOF
Uso: $SCRIPT_NOMBRE [config_file] [modo]
Modos:
full Backup completo
incremental Backup incremental (por defecto)
verify Verificar integridad del último backup
report Generar reporte de backups
Config file: /etc/backup_pro.conf (por defecto)
o ruta personalizada
Ejemplos:
$SCRIPT_NOMBRE # Modo incremental
$SCRIPT_NOMBRE /etc/backup_pro.conf full # Backup completo
$SCRIPT_NOMBRE ~/mi_config.conf verify # Verificar
EOF
exit 0
}
_log() {
local nivel="$1"
local mensaje="$2"
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$nivel] $mensaje" >> "$LOG_FILE"
logger -t backup_pro -p "user.${nivel,,}" "$mensaje"
}
_msg() {
local tipo="$1"
local mensaje="$2"
local color=""
case "$tipo" in
ok) color="$VERDE" ;;
error) color="$ROJO" ;;
warn) color="$AMARILLO" ;;
info) color="$AZUL" ;;
esac
echo -e "${color}${mensaje}${NC}"
_log "${tipo^^}" "$mensaje"
}
_notificar() {
local estado="$1" # EXITOSO | FALLIDO
local mensaje="$2"
# Telegram
if [[ "${TELEGRAM_TOKEN:-}" && "${TELEGRAM_CHAT_ID:-}" ]]; then
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
-d "chat_id=${TELEGRAM_CHAT_ID}" \
-d "text=${mensaje}" \
-d "parse_mode=Markdown" > /dev/null
fi
# Email
if [[ "${NOTIFICAR_EMAIL:-false}" == "true" && -n "${EMAIL_DESTINO:-}" ]]; then
echo "$mensaje" | mail -s "[Backup] ${estado} - $(hostname)" "$EMAIL_DESTINO"
fi
# Desktop
if [[ -n "${DISPLAY:-}" ]]; then
local icono
[[ "$estado" == "EXITOSO" ]] && icono="dialog-information" || icono="dialog-error"
notify-send -i "$icono" "Backup Pro" "$mensaje"
fi
}
# ── Cargar configuración ────────────────────────────────
cargar_config() {
if [[ ! -f "$CONFIG_FILE" ]]; then
_msg error "✗ Archivo de configuración no encontrado: $CONFIG_FILE"
exit 1
fi
source "$CONFIG_FILE"
_msg info "✓ Configuración cargada: $CONFIG_FILE"
}
# ── Backup Full ──────────────────────────────────────────
backup_full() {
_msg info "═══ INICIANDO BACKUP FULL ═══"
local destino_full="${DESTINO_LOCAL}/daily/$(date +%u)-$(date +%A | tr '[:upper:]' '[:lower:]')"
mkdir -p "$destino_full"
for origen in "${ORIGENES[@]}"; do
local nombre
nombre=$(basename "$origen")
_msg info "→ Respaldando: $origen"
local inicio
inicio=$(date +%s)
if [[ "${COMPRESION:-gzip}" == "gzip" && "$origen" != "/etc"* ]]; then
tar czf "${destino_full}/${nombre}_${TIMESTAMP_INICIO}.tar.gz" -C "$(dirname "$origen")" "$nombre" 2>/dev/null
else
rsync -avh --delete "$origen/" "${destino_full}/${nombre}/" 2>/dev/null
fi
local fin
fin=$(date +%s)
local duracion=$((fin - inicio))
if [[ $? -eq 0 ]]; then
_msg ok " ✓ $nombre respaldado (${duracion}s)"
ARCHIVOS_COUNT=$((ARCHIVOS_COUNT + 1))
else
_msg error " ✗ Error al respaldar $nombre"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
done
TAMANO_TOTAL=$(du -sh "$destino_full" 2>/dev/null | cut -f1)
_msg info "═══ BACKUP FULL COMPLETADO ═══"
}
# ── Backup Incremental ──────────────────────────────────
backup_incremental() {
_msg info "═══ INICIANDO BACKUP INCREMENTAL ═══"
local destino_inc="${DESTINO_LOCAL}/daily/$(date +%u)-$(date +%A | tr '[:upper:]' '[:lower:]')"
mkdir -p "$destino_inc"
local referencia
referencia=$(find "$DESTINO_LOCAL/daily" -maxdepth 1 -type d -name "0-*" | sort | tail -1)
if [[ -z "$referencia" ]]; then
_msg warn "⚠ Sin backup de referencia. Ejecutando full..."
backup_full
return
fi
for origen in "${ORIGENES[@]}"; do
local nombre
nombre=$(basename "$origen")
_msg info "→ Incremental: $origen"
local inicio
inicio=$(date +%s)
if [[ "$referencia" != "$destino_inc" ]]; then
rsync -avh --delete --link-dest="$referencia/$nombre" \
"$origen/" "${destino_inc}/${nombre}/" 2>/dev/null
else
rsync -avh --delete "$origen/" "${destino_inc}/${nombre}/" 2>/dev/null
fi
local fin
fin=$(date +%s)
local duracion=$((fin - inicio))
if [[ $? -eq 0 ]]; then
_msg ok " ✓ $nombre incrementado (${duracion}s)"
ARCHIVOS_COUNT=$((ARCHIVOS_COUNT + 1))
else
_msg error " ✗ Error en incremental de $nombre"
ERROR_COUNT=$((ERROR_COUNT + 1))
fi
done
TAMANO_TOTAL=$(du -sh "$destino_inc" 2>/dev/null | cut -f1)
_msg info "═══ BACKUP INCREMENTAL COMPLETADO ═══"
}
# ── Verificación ────────────────────────────────────────
backup_verificar() {
_msg info "═══ VERIFICANDO BACKUPS ═══"
local verificados=0
local corruptos=0
while IFS= read -r -d '' archivo; do
if tar tzf "$archivo" > /dev/null 2>&1; then
_msg ok " ✓ $archivo"
verificados=$((verificados + 1))
else
_msg error " ✗ CORRUPTO: $archivo"
corruptos=$((corruptos + 1))
fi
done < <(find "$DESTINO_LOCAL" -name "*.tar.gz" -type f -print0 2>/dev/null)
while IFS= read -r -d '' archivo; do
if gpg --list-packets "$archivo" > /dev/null 2>&1; then
_msg ok " ✓ $archivo (cifrado)"
verificados=$((verificados + 1))
else
_msg error " ✗ CORRUPTO: $archivo (cifrado)"
corruptos=$((corruptos + 1))
fi
done < <(find "$DESTINO_LOCAL" -name "*.gpg" -type f -print0 2>/dev/null)
_msg info "═══ VERIFICACIÓN COMPLETADA ═══"
_msg info " ✓ Válidos: $verificados"
[[ "$corruptos" -gt 0 ]] && _msg error " ✗ Corruptos: $corruptos" || _msg ok " ✓ Sin corruptos"
}
# ── Rotación GFS ─────────────────────────────────────────
rotar_gfs() {
_msg info "═══ ROTACIÓN GFS ═══"
local base="$DESTINO_LOCAL"
local ret_diaria="${RETENCION_DIARIA:-7}"
local ret_semanal="${RETENCION_SEMANAL:-4}"
local ret_mensual="${RETENCION_MENSUAL:-12}"
# Promover el último diario a semanal si es domingo
if [[ $(date +%u) -eq 7 ]]; then
local sem_actual
sem_actual=$(date +%V)
mkdir -p "${base}/weekly/semana-${sem_actual}"
rsync -a --delete "${base}/daily/6-sun/" "${base}/weekly/semana-${sem_actual}/"
_msg ok " ✓ Promovido a semanal: semana-${sem_actual}"
fi
# Promover a mensual si es día 1
if [[ $(date +%d) -eq 1 ]]; then
local mes_actual
mes_actual=$(date +%m)
mkdir -p "${base}/monthly/mes-${mes_actual}"
rsync -a --delete "${base}/weekly/semana-$(date +%V)/" "${base}/monthly/mes-${mes_actual}/"
_msg ok " ✓ Promovido a mensual: mes-${mes_actual}"
fi
backup_rotar_gfs "$base" "$ret_diaria" "$ret_semanal" "$ret_mensual"
_msg info "═══ ROTACIÓN COMPLETADA ═══"
}
# ── Reporte ─────────────────────────────────────────────
generar_reporte() {
local estado="$1"
local reporte
reporte=$(cat << EOF
# Reporte de Backup
# Fecha: $FECHA_LOG
# Host: $(hostname)
# Script: $SCRIPT_NOMBRE (PID: $PID)
## Estado: $estado
- Modo: $MODO
- Archivos respaldados: $ARCHIVOS_COUNT
- Tamaño total: $TAMANO_TOTAL
- Errores: $ERROR_COUNT
- Duración: $((TIEMPO_FIN - TIEMPO_INICIO))s
- Directorio destino: $DESTINO_LOCAL
- Archivo de configuración: $CONFIG_FILE
EOF
)
echo "$reporte" > "/var/log/backup_reporte_${TIMESTAMP_INICIO}.md"
local emoji="✅"
[[ "$estado" == "FALLIDO" ]] && emoji="❌"
local notificacion="${emoji} *Backup ${estado}*
* Host: $(hostname)
* Modo: $MODO
* Archivos: $ARCHIVOS_COUNT
* Tamaño: $TAMANO_TOTAL
* Duración: $((TIEMPO_FIN - TIEMPO_INICIO))s
* Errores: $ERROR_COUNT"
_notificar "$estado" "$notificacion"
}
# ── Main ────────────────────────────────────────────────
main() {
[[ "$1" == "-h" || "$1" == "--help" ]] && _uso
cargar_config
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
TIEMPO_INICIO=$(date +%s)
_msg info "=== Script iniciado ==="
_msg info "PID: $PID | Modo: $MODO | Config: $CONFIG_FILE"
case "$MODO" in
full)
backup_full
rotar_gfs
[[ "${VERIFICAR:-false}" == "true" ]] && backup_verificar
;;
incremental)
backup_incremental
rotar_gfs
[[ "${VERIFICAR:-false}" == "true" ]] && backup_verificar
;;
verify)
backup_verificar
;;
report)
generar_reporte "EXITOSO"
;;
*)
_msg error "Modo desconocido: $MODO"
_uso
;;
esac
TIEMPO_FIN=$(date +%s)
local duracion=$((TIEMPO_FIN - TIEMPO_INICIO))
if [[ "$ERROR_COUNT" -eq 0 ]]; then
generar_reporte "EXITOSO"
_msg ok "✓ Backup completado exitosamente en ${duracion}s"
else
generar_reporte "FALLIDO"
_msg error "✗ Backup completado con $ERROR_COUNT errores en ${duracion}s"
fi
_msg info "=== Script finalizado ==="
exit $ERROR_COUNT
}
main "$@"
Cómo usarlo:
chmod +x backup_pro.sh
# Backup incremental (por defecto)
sudo ./backup_pro.sh
# Backup full
sudo ./backup_pro.sh /etc/backup_pro.conf full
# Verificar integridad
sudo ./backup_pro.sh /etc/backup_pro.conf verify
# Generar reporte
sudo ./backup_pro.sh /etc/backup_pro.conf report
# Configuración personalizada
sudo ./backup_pro.sh ~/mi_empresa/backup.conf full
Errores comunes
Error 1: No verificar el backup después de crearlo
# ❌ Creas el backup pero no verificas
tar czf backup.tar.gz /datos/
rm -rf /datos/ # ¡Confías ciegamente!
# ✅ Verificar antes de confiar
tar czf backup.tar.gz /datos/ && tar tzf backup.tar.gz > /dev/null && echo "OK"
Error 2: Rutas relativas en scripts de backup programados
# ❌ Cron ejecuta con PATH mínimo, ruta relativa falla
0 3 * * * ./backup_pro.sh # ¡FALLA! No encuentra el script
# ✅ Siempre rutas absolutas
0 3 * * * /usr/local/bin/backup_pro.sh /etc/backup_pro.conf full
Error 3: Olvidar excluir directorios problemáticos
# ❌ Respaldar /home incluye cachés, basura, temporales
rsync -a /home/usuario/ /backups/home/
# ✅ Excluir lo no respaldable
rsync -a --exclude='.cache/' --exclude='.local/share/Trash/' \
--exclude='node_modules/' --exclude='*.tmp' \
/home/usuario/ /backups/home/
Error 4: Confiar en un solo destino
# ❌ Un solo disco duro externo
rsync -a /datos/ /mnt/disco_externo/backup/
# ✅ Regla 3-2-1: 3 copias, 2 medios, 1 fuera del sitio
rsync -a /datos/ /backups/local/ # Copia local
rsync -a /datos/ usuario@servidor:/backups/ # Copia remota
rclone sync /datos/ remote:cloud-backup/datos/ # Copia cloud
Error 5: Cifrar y olvidar la contraseña
# ❌ Cifras con contraseña que nadie recuerda
gpg --symmetric --cipher-algo AES256 backup.tar.gz
# ✅ Guardar la contraseña cifrada con GPG
gpg --symmetric -o /etc/backup/passphrase.gpg /etc/backup/passphrase.txt
Error 6: Rotar sin verificar que hay suficientes backups
# ❌ Rotación agresiva que elimina demasiado pronto
find /backups/daily -mtime +7 -delete
# ✅ Verificar que quedan suficientes antes de rotar
if [[ $(ls -1 /backups/daily/ | wc -l) -gt 7 ]]; then
find /backups/daily -maxdepth 1 -type d | sort | head -n -7 | xargs rm -rf
fi
Error 7: No probar la restauración periódicamente
# ✅ Prueba automática de restauración (script semanal)
probar_restauracion() {
local tmp
tmp=$(mktemp -d)
tar xzf /backups/ultimo.tar.gz -C "$tmp" && \
echo "✓ Restauración exitosa" || \
echo "✗ FALLO EN RESTAURACIÓN"
rm -rf "$tmp"
}
Error 8: Ignorar los límites de espacio en destino
# ✅ Verificar espacio antes de empezar
verificar_espacio() {
local necesario
necesario=$(du -sb /datos/ | cut -f1)
local disponible
disponible=$(df -B1 /backups/ | tail -1 | awk '{print $4}')
if [[ $necesario -gt $disponible ]]; then
echo "ERROR: Espacio insuficiente en /backups/"
exit 1
fi
}
Error 9: No preservar permisos y metadatos
# ❌ cp no preserva dueño, permisos, ACLs
cp -r /etc/ /backups/etc/
# ✅ rsync con modo archivo preserva todo
rsync -a /etc/ /backups/etc/
# ✅ tar con ACLs y xattrs
tar --acls --xattrs -czf etc_backup.tar.gz /etc/
Error 10: No tener un plan de recuperación documentado
# ✅ Incluir un README en el directorio de backups
cat > /backups/LEEME_RESTAURACION.txt << 'EOF'
=== CÓMO RESTAURAR ===
1. Backup completo más reciente:
tar xzf /backups/full_20260615.tar.gz -C /tmp/restore/
2. Desde rsync:
rsync -a /backups/daily/3-thu/ /tmp/restore/
3. Backup cifrado:
gpg --decrypt backup.tar.gz.gpg | tar xz -C /tmp/restore/
4. Script de restauración automática:
/usr/local/bin/restore_pro.sh --latest --dest /tmp/restore/
EOF
Resumen
- Backup full:
tar czf/rsync -a. Copia completa de datos. - Backup incremental:
rsync --link-dest. Solo cambios desde último backup. - Backup diferencial:
rsync --link-dest. Solo cambios desde último full. - Rotación GFS: Daily (Son) + Weekly (Father) + Monthly (Grandfather). Retención organizada.
- rsync local:
rsync -avh --delete /origen/ /destino/. Sincronización local eficiente. - rsync remoto:
rsync -avhz -e ssh /origen/ user@host:/destino/. Sincronización remota segura. - –link-dest:
--link-dest=/ruta/ref/. Incremental con hardlinks, cero espacio extra. - Tar + gzip:
tar czf. Backup comprimido rápido. - Tar + bzip2:
tar cjf. Backup comprimido ratio medio. - Tar + xz:
tar cJf. Backup comprimido máximo ratio. - Cifrado GPG:
gpg --symmetric --cipher-algo AES256. Protección de datos sensibles. - Checksum:
sha256sum archivo > archivo.sha256. Verificación de integridad. - Verificación tar:
tar tzf archivo.tar.gz. Comprobar que el tar no está corrupto. - Logger:
logger -t backup -p user.info "mensaje". Enviar logs al syslog. - Notificación Telegram:
curl -X POST https://api.telegram.org/botTOKEN/sendMessage. Notificación en tiempo real. - Notificación email:
echo "msg" | mail -s "asunto" user@dom.com. Notificación tradicional. - Paralelismo:
xargs -P N. Múltiples backups simultáneos. - rclone:
rclone sync /origen/ remote:destino/. Backup a 40+ proveedores cloud. - restic:
restic -r /repo backup /datos/. Backup moderno con deduplicación. - s3cmd:
s3cmd sync /dir/ s3://bucket/. Backup a AWS S3. - Config-driven:
source /etc/backup_pro.conf. Script configurable sin hardcode. - Regla 3-2-1: 3 copias, 2 medios, 1 fuera del sitio. Estándar de la industria.
- Prueba de restauración: Extraer a temp dir y verificar. Confirmar que el backup sirve.
En el próximo capítulo exploraremos programación avanzada con Bash: señales y traps, ejecución en background y foreground, procesos hijos, pipes y fifos, y gestión de procesos para escribir scripts robustos que se mantengan ejecutándose en producción sin sorpresas.
Más información,
- rclone — Lista de proveedores cloud soportados — 40+ servicios de almacenamiento
- restic — Getting Started — Primeros pasos con restic
- GFS Backup Strategy (Wikipedia) — Explicación completa del esquema GFS
- s3cmd (AWS S3) — Herramienta CLI para S3
- Backblaze B2 + restic — Combinación popular y económica
- Regla 3-2-1 de backups — El estándar de la industria
- Crontab Guru — Generador interactivo de expresiones cron
- Systemd Timer Calculator — Calculadora interactiva de OnCalendar
Algunos libros,
- «The Practice of System and Network Administration» de Thomas A. Limoncelli — Capítulos sobre backup y recuperación
- «UNIX and Linux System Administration Handbook» de Nemeth et al. — Capítulo sobre backups y restauración
- «Bash Cookbook» de Carl Albing — Recetas de backup y automatización
- «Linux System Administration» de Tom Adelstein — Estrategias de backup en producción
- «Backup & Recovery» de W. Curtis Preston — La biblia de los backups (independiente del SO)