ScriptCraft

Automatización Linux, scripting Bash y herramientas para sysadmins

Backup incremental automático con rsync y systemd timer

El problema

Todo sysadmin ha pasado por esto: tienes backups que se ejecutan… o eso crees. Hasta que pasa algo y descubres que el cronjob dejó de funcionar hace tres meses y nadie se enteró.

La solución canónica es rsync + cron. Pero cron tiene limitaciones: no sabes si falló, los logs son opacos, y si el servidor estaba apagado a la hora programada, simplemente no se ejecuta.

Aquí entra systemd timer: más robusto, con logs integrados en journalctl, soporte para eventos perdidos (Persistent=true) y aleatorización (RandomizedDelaySec) para no saturar discos cuando tienes muchas máquinas.

El script

Guarda esto como /usr/local/bin/backup-incremental.sh:

#!/usr/bin/env bash
set -euo pipefail

# === CONFIGURACIÓN ===
SOURCE_DIR="${SOURCE_DIR:-/srv/data}"
BACKUP_ROOT="${BACKUP_ROOT:-/backups/data}"
RETENTION_DAYS="${RETENTION_DAYS:-30}"
# =====================

TIMESTAMP=$(date +%Y-%m-%d_%H%M%S)
LATEST_LINK="${BACKUP_ROOT}/latest"
DEST_DIR="${BACKUP_ROOT}/${TIMESTAMP}"

mkdir -p "${BACKUP_ROOT}"

rsync -avh --delete \
    --link-dest="${LATEST_LINK}" \
    "${SOURCE_DIR}/" \
    "${DEST_DIR}/"

# Actualizar el enlace simbólico 'latest'
rm -f "${LATEST_LINK}"
ln -s "${DEST_DIR}" "${LATEST_LINK}"

# Rotar backups antiguos
find "${BACKUP_ROOT}" -maxdepth 1 -type d -name '20*' -mtime "+${RETENTION_DAYS}" \
    -exec rm -rf {} \; 2>/dev/null || true

echo "Backup completado: ${DEST_DIR}"

¿Qué hace cada parte?

LíneaExplicación
set -euo pipefailEl script muere ante cualquier error. Sin esto, rsync puede fallar y el script reportaría éxito.
SOURCE_DIR / BACKUP_ROOTConfigurables por variable de entorno. Si no se pasan, usan valores por defecto.
--link-destLa magia de rsync: crea hard links a archivos que no cambiaron. Backup 30 ocupa casi lo mismo que backup 1 si los datos no cambiaron.
rm -f "${LATEST_LINK}"Borramos el enlace anterior antes de crear el nuevo. ln -sf no es atómico — mejor explícito.
find ... -mtimeBorra backups más viejos que RETENTION_DAYS. Usa -mtime (fecha de modificación), no -ctime.

El timer de systemd

Crea dos archivos. Primero el servicio (/etc/systemd/system/backup-incremental.service):

[Unit]
Description=Backup incremental con rsync
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-incremental.sh
Environment="SOURCE_DIR=/srv/produccion"
Environment="BACKUP_ROOT=/backups/produccion"
Environment="RETENTION_DAYS=30"
User=root
Nice=19
IOSchedulingClass=idle

Y luego el timer (/etc/systemd/system/backup-incremental.timer):

[Unit]
Description=Ejecuta backup incremental diario a las 03:00
Requires=backup-incremental.service

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1800

[Install]
WantedBy=timers.target

Puntos importantes

Actívalo:

systemctl daemon-reload
systemctl enable --now backup-incremental.timer

Verificar que funciona

# ¿El timer está activo?
systemctl status backup-incremental.timer

# ¿Cuándo se disparó la última vez?
systemctl list-timers backup-incremental.timer

# ¿Falló algo?
journalctl -u backup-incremental.service --since "1 day ago"

Resumen

Aspectocronsystemd timer
LogsHay que redirigir manualmentejournalctl automático
Máquina apagadaSe pierde la ejecuciónPersistent=true la recupera
DependenciasNingunaAfter=, Wants=
AleatorizaciónManual con sleep $RANDOMRandomizedDelaySec nativo
MonitoreoScript externosystemctl status + métricas exportables

Este es el tipo de contenido que rankea: responde una pregunta específica, da código funcional, y resuelve un dolor real de un sysadmin.

¿Quieres probar el script en tu máquina antes de publicarlo?