Script para Restic

Hace unos meses escribí un artículo para realizar la copia de seguridad de mis contenedores con Restic. Ahora retomo el tema dándole una vuelta de tuerca al script inicial para añadirle una cuantas mejoras:
Aquí dejo el script que he mejorado con la ayuda de la IA.
Modularidad y Soporte Multi-configuración
Antes: El script original tenía las rutas (~/dockers), los repositorios y las contraseñas escritas directamente “a fuego” (hardcoded) en el código. Si querías respaldar otra carpeta u otra nube, tenías que duplicar el script entero.
Ahora: El script actúa como una plantilla genérica. Acepta un archivo de configuración externo como argumento ($1). Si no se le pasa ninguno, usa uno por defecto gracias a la línea:
CONFIG_FILE="${1:-$HOME/.restic-backup}"
Esta característica te permite gestionar infinitas copias de seguridad distintas utilizando un único script ejecutable sin más que apuntar a un archivo de configuración distinto.
Seguridad Mejorada (Adiós a los archivos de clave secundarios)
Antes: El script original requería gestionar dos archivos externos: el script en sí y un archivo .restic-password que contenía la contraseña en texto plano, obligando a Restic a leer el disco con el argumento --password-file.
Ahora: Toda la configuración se centraliza en un único lugar. La contraseña se almacena como una variable dentro del archivo .restic-backup y se inyecta directamente en la memoria del sistema mediante el uso nativo de la variable de entorno de Restic:
export RESTIC_PASSWORD="$RESTIC_PASSWORD"
Portabilidad Total con la variable $HOME
Antes: El script original utilizaba rutas absolutas estáticas que apuntaban explícitamente a /home/sherlockes/. Si clonabas el script en otro servidor con un usuario diferente, el script fallaba de inmediato.
Ahora: Al implementar $HOME, el script se adapta automáticamente al directorio del usuario que lo esté ejecutando, facilitando enormemente que los lectores de tu blog puedan descargar tu script y usarlo en sus sistemas sin editar una sola línea de código.
Red de Seguridad: Inicialización Automática
Antes: Si el repositorio remoto en la nube de Mega no estaba inicializado previamente con el comando manual init, el script original fallaba catastróficamente lanzando un error y deteniendo el proceso.
Ahora: El script incluye una rutina de comprobación inteligente en segundo plano:
sudo -E restic -r "$REPO_REMOTE" snapshots > /dev/null 2>&1
Si detecta que el repositorio en la nube no existe o no es accesible, lo inicializa de forma automática, asegurando que la tarea no falle.
Sistema de Alertas y Notificaciones
Antes: El script original era completamente “silencioso”. Si el backup fallaba por falta de espacio, caída de red o errores de permisos, no te enterabas a menos que leyeses manualmente el archivo backup.log.
Ahora: Se integra una función nativa de telemetría por Telegram que envía alertas críticas inmediatas si ocurre un error en el backup o en las políticas de retención, así como un mensaje de confirmación verde (🟢) cuando todo concluye con éxito.
Estrategia de Backup Híbrida (Local + Remoto)
Antes: El script original únicamente realizaba la copia de seguridad hacia el almacenamiento remoto en la nube de Mega.
Ahora: Aplica la regla de respaldo profesional (3-2-1), realizando primero la validación y copia en un repositorio Local (para recuperaciones instantáneas a gran velocidad) y posteriormente replicando el proceso hacia el repositorio Remoto en Mega (protección ante desastres físicos en el servidor).
El problema del sudo
No he sido capaz de eliminar la necesidad de root para llamar al comando restic así que tenemos que seguir añadiendo una línea en sudo visudo que será la siguiente:
sherlockes ALL=(ALL) NOPASSWD:SETENV: /usr/bin/restic
Script completo
#!/bin/bash
###################################################################
#Script Name: restic-backup.sh
#Description: Copia de seguridad 100% modular con Restic
#Args: [Ruta al archivo de configuración] (Opcional)
#Creation/Update: 20260518/20260611
#Author: www.sherblog.es
#Email: sherlockes@gmail.com
###################################################################
# 1. Cargar archivo de configuración del backup (Portabilidad total con $HOME)
CONFIG_FILE="${1:-$HOME/.restic-backup}"
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
else
echo "❌ Error: No se encuentra el archivo de configuración en $CONFIG_FILE"
exit 1
fi
# 2. Inyección de variables de entorno para Rclone y Restic
export RCLONE_CONFIG="$RCLONE_CONFIG_PATH"
export RESTIC_PASSWORD="$RESTIC_PASSWORD"
# Función para enviar notificaciones a Telegram
enviar_telegram() {
local mensaje="$1"
# Solo envía si las variables de Telegram están configuradas en este archivo
if [ ! -z "$TG_BOT_TOKEN" ] && [ ! -z "$TG_CHAT_ID" ]; then
curl -s -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
-d "chat_id=${TG_CHAT_ID}" \
-d "parse_mode=Markdown" \
-d "text=${mensaje}" > /dev/null
fi
}
# ==========================================
# 1. BACKUP EN REMOTO (MEGA)
# ==========================================
echo "Comprobando repositorio remoto..."
sudo -E restic -r "$REPO_REMOTE" snapshots > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "Inicializando repositorio remoto..."
sudo -E restic -r "$REPO_REMOTE" init
if [ $? -ne 0 ]; then
enviar_telegram "🚨 *FALLO CRÍTICO $(hostname)*: No se pudo inicializar el repositorio REMOTO."
exit 1
fi
fi
echo "Iniciando backup en REMOTO: $(date)"
sudo -E restic -r "$REPO_REMOTE" backup "$BACKUP_SOURCE" --exclude="*.mp3" --exclude="*.mp4"
if [ $? -ne 0 ]; then
enviar_telegram "🚨 *FALLO CRÍTICO $(hostname)*: El backup en *REMOTO* ha fallado."
exit 1
fi
echo "Aplicando política de retención en REMOTO"
sudo -E restic -r "$REPO_REMOTE" forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
if [ $? -ne 0 ]; then
enviar_telegram "⚠️ *Aviso $(hostname)*: El backup remoto se hizo, pero falló la limpieza (Prune REMOTO)."
fi
# ==========================================
# 2. BACKUP EN LOCAL
# ==========================================
# Auto-inicialización del repositorio local si no existe
if [ ! -f "$REPO_LOCAL/config" ]; then
echo "Inicializando repositorio local..."
sudo -E restic -r "$REPO_LOCAL" init
if [ $? -ne 0 ]; then
enviar_telegram "🚨 *FALLO CRÍTICO $(hostname)*: No se pudo inicializar el repositorio local."
exit 1
fi
fi
echo "Iniciando backup en LOCAL: $(date)"
sudo -E restic -r "$REPO_LOCAL" backup "$BACKUP_SOURCE" --exclude="*.mp3" --exclude="*.mp4"
if [ $? -ne 0 ]; then
enviar_telegram "🚨 *FALLO CRÍTICO $(hostname)*: El backup en *LOCAL* ha fallado."
exit 1
fi
echo "Aplicando política de retención en LOCAL"
sudo -E restic -r "$REPO_LOCAL" forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
if [ $? -ne 0 ]; then
enviar_telegram "⚠️ *Aviso $(hostname)*: El backup local se hizo, pero falló la limpieza (Prune LOCAL)."
fi
# ==========================================
# NOTIFICACIÓN DE ÉXITO GENERAL
# ==========================================
echo "Todos los backups completados con éxito: $(date)"
enviar_telegram "🟢 *Backup Completado ($(hostname))*: Las copias de [$BACKUP_SOURCE] en LOCAL y REMOTO se han realizado y purgado correctamente."