Backup incremental con deduplicación real, sin cliente residente

Infraestructura & Sistemas

Backup incremental con deduplicación real, sin cliente residente

Cómo reemplazamos un backup basado en nextcloudcmd por restic + rclone
para respaldar varios terabytes de carpetas corporativas hacia Nextcloud, en un servidor Linux sin
entorno gráfico.

Origen: servidor Linux headless (Debian 13)
Destino: Nextcloud remoto (WebDAV)
Volumen: ~4 TB / 25 carpetas

Este artículo documenta el razonamiento técnico y la implementación completa del sistema de backup
que usamos hoy en CAB Group SRL para respaldar carpetas Samba corporativas hacia un servidor Nextcloud
ubicado en otra sede — desde por qué descartamos el cliente oficial de línea de comandos de Nextcloud,
hasta cómo se restaura un archivo puntual sin tocar el resto del repositorio.

01El punto de partida

Necesitábamos mantener una copia de seguridad externa de nuestras carpetas compartidas por Samba
(varios terabytes distribuidos en más de 20 carpetas) en un servidor Nextcloud alojado en otra
ubicación física. La restricción principal: el servidor origen es un Linux sin entorno
gráfico
, así que no existe la posibilidad de dejar corriendo un cliente Nextcloud de
escritorio en segundo plano, como el que cualquiera instala en una PC con Windows o Linux con interfaz.

La única herramienta oficial disponible en consola es nextcloudcmd, el cliente de línea
de comandos de Nextcloud. Nuestro primer enfoque lo disparaba por cron: copiaba cada carpeta a un
directorio temporal, normalizaba los nombres de archivo problemáticos para WebDAV, y sincronizaba
contra el remoto con reintentos manuales. Funcionaba, pero arrastraba tres problemas estructurales
que terminaron definiendo el cambio de herramienta.

02Por qué restic + rclone y no nextcloudcmd por cron

El problema de fondo. nextcloudcmd está pensado para dos escenarios:
correr como proceso residente con entorno gráfico, manteniendo un archivo de estado persistente entre
corridas (el journal de sincronización), o para una sincronización bidireccional puntual.
Ninguno de los dos aplica a un servidor headless que dispara un backup una vez por día vía cron.

  • Sin entorno gráfico, la única forma viable de usarlo es invocarlo desde cron, corrida completa por vez.
  • Sin estado persistente entre corridas: si el directorio temporal se arma y se borra en cada ejecución, no queda journal — cada corrida tiene que re-comparar el árbol completo contra el remoto para saber qué subir.
  • Sin dry-run confiable ni logging estructurado, dificulta saber de antemano qué va a pasar antes de tocar datos de producción.

La solución: restic (backup) + rclone (transporte). restic es una herramienta de
backup diseñada específicamente para correr por cron en servidores sin interfaz: se ejecuta, hace su
trabajo, termina — no necesita quedar residente en memoria. rclone actúa como transporte hacia el
WebDAV de Nextcloud; restic no necesita saber nada específico de Nextcloud, solo delega el «cómo
llegar» a rclone.

  • Corre por cron de forma nativa — exactamente el modelo para el que fue construido.
  • Versionado real, no un espejo: cada corrida genera un snapshot inmutable identificado por ID, restaurable de forma independiente.
  • Credenciales fuera del comando: usuario y clave quedan en un archivo de entorno con permisos restringidos, no expuestos en la línea de comandos.

Soluciona el problema de caracteres raros sin normalizar nada. El script original
necesitaba una función completa para reemplazar caracteres prohibidos en WebDAV/Windows
(\ : * ? " < > | # % & { } ~ `) antes de subir cualquier archivo, porque el
destino final era una estructura de carpetas y archivos reales, con nombres que tenían que ser válidos
para ese sistema de archivos remoto.

restic no funciona así: guarda el contenido como bloques cifrados direccionados por
hash
(SHA-256) dentro de su propio repositorio. El nombre original del archivo queda como
metadato interno, no como un nombre de archivo real en el sistema remoto — Nextcloud nunca ve una ruta
con caracteres raros que tenga que validar. La función de normalización de nombres dejó de ser
necesaria.

Es más rápido en volúmenes grandes. La diferencia está en el nivel de granularidad
de la comparación:

  • nextcloudcmd / rsync comparan a nivel de archivo completo (tamaño + fecha de modificación). Si un archivo de 2 GB cambia en un byte, se retransfiere entero.
  • restic divide cada archivo en bloques de tamaño variable y compara a ese nivel. Si un archivo de 2 GB tiene 10 MB modificados, sube solo esos 10 MB.

En producción, la segunda corrida sobre una de nuestras carpetas más grandes (7.4 GB, más de
35.000 archivos) recorrió y comparó todo el árbol en menos de 2 minutos, subiendo
0 bytes porque nada había cambiado — contra los más de 25 minutos que tomó la primera
carga completa.

03Arquitectura
Servidor origen (Linux headless)          Nextcloud remoto (cloud.****.com.ar)
┌───────────────────────────┐          ┌───────────────────────────┐
│ /srv/****/  (~4 TB)        │          │ /****/                    │
│  ├── carpeta-01            │          │  └── restic-backup/       │
│  ├── carpeta-02            │          │       (repositorio        │
│  ├── ... (25 carpetas)     │  HTTPS   │        cifrado)           │
│                            │─────────▶│                           │
│ restic ──▶ rclone          │  WebDAV  │                           │
│ (dedup + cifrado local)    │          │                           │
└───────────────────────────┘          └───────────────────────────┘

restic lee el origen, chunkea y cifra localmente; rclone se encarga solo del transporte WebDAV.

Componente Rol
restic Motor de backup: deduplicación por bloques, cifrado, versionado por snapshots, retención.
rclone Transporte: habla WebDAV contra Nextcloud. restic lo usa como backend, no como sincronizador.
Remote configurado Credenciales de Nextcloud guardadas en la configuración de rclone.
Repositorio restic Carpeta remota donde vive el repositorio cifrado completo, con todos los snapshots.
Archivo de entorno Variables con la ubicación del repositorio y la clave de cifrado.

04Cómo funciona el script, paso a paso

Control de instancia única. Al arrancar, el script verifica si existe un archivo
de lock con el PID de una corrida anterior. Si ese proceso sigue vivo, sale sin hacer nada (evita
corridas superpuestas). Si el PID ya no existe — por ejemplo tras un corte manual — limpia el lock
huérfano y continúa normalmente.

Detección automática de carpetas. En cada corrida, el script escanea el directorio
base y toma todas las subcarpetas de primer nivel que encuentra, salvo las excluidas explícitamente.
Si mañana se crea una carpeta nueva, la próxima corrida la incorpora sola — no hace falta tocar el
script.

Un snapshot por carpeta, mismo repositorio. Cada carpeta se respalda con su propio
comando y su propio tag, pero todas comparten el mismo repositorio restic. Esto permite que la
deduplicación funcione también entre carpetas, y que la retención se aplique de forma
independiente para cada una.

Reintentos ante fallos. Si una carpeta falla, el script reintenta un par de veces
con varios minutos de espera entre intentos. Ese margen es intencional: si un intento anterior fue
cortado a mitad de una escritura, Nextcloud puede quedar con un bloqueo temporal sobre ese objeto del
lado servidor. Reintentar de inmediato choca contra ese lock todavía activo — esperar le da tiempo a
Nextcloud de liberarlo solo.

Por qué se desactivó el timeout en la primera carga completa. En las primeras
pruebas fijamos un límite de tiempo por carpeta. Con carpetas de gran volumen y un enlace de subida
limitado, ese límite resultó matemáticamente insuficiente: transferir varios terabytes puede tomar más
de un día entero, solo de transferencia pura. El timeout cortaba el proceso a la fuerza a mitad de una
subida legítima — y eso, además, dejaba locks huérfanos en Nextcloud que disparaban en cascada errores
de bloqueo (423 Locked) y del backend de sesiones (500 Internal Server Error).

config del script — primera carga completa
# 0 = sin límite de tiempo por carpeta.
# Correcto SOLO para la primera carga completa con volúmenes grandes.
BACKUP_TIMEOUT=0

Este valor es correcto solo para la primera carga completa. Una vez que todas las
carpetas tienen un snapshot base, las corridas siguientes son incrementales y deberían tardar minutos,
no horas — ahí sí conviene volver a fijar un timeout acotado como red de seguridad: si una corrida
incremental tarda eso, algo anómalo está pasando y vale más que el script lo corte y avise.

Límite de conexiones concurrentes. Nextcloud usa Redis para su sistema de bloqueo
transaccional de archivos. Con muchas conexiones simultáneas escribiendo bloques, ese subsistema queda
bajo presión y empieza a rechazar operaciones. Limitar la concurrencia del lado del cliente reduce esa
presión, a costa de una subida algo más lenta — un trade-off razonable para priorizar que el backup
complete sin errores en cascada.

flag aplicado en cada backup
restic backup /srv/****/carpeta-ejemplo \
    --tag carpeta-ejemplo \
    -o rclone.connections=2

05Política de retención — 7 diarios / 4 semanales / 6 mensuales

restic no guarda un snapshot por corrida para siempre — eso haría crecer el espacio usado sin
límite. Aplica en cambio una política de poda que conserva más densidad de puntos de
recuperación cerca del presente, y menos a medida que el historial se aleja en el tiempo.

Regla Qué conserva
--keep-daily 7 Un snapshot por cada uno de los últimos 7 días.
--keep-weekly 4 De las 4 semanas anteriores, un snapshot por semana.
--keep-monthly 6 De los 6 meses anteriores a eso, un snapshot por mes.
--prune Elimina físicamente del repositorio los datos que ya no pertenecen a ningún snapshot conservado.

En la práctica: corriendo todos los días durante un año, no se acumulan 365 snapshots — quedan como
máximo ~17 (7+4+6), distribuidos con grano fino en lo reciente y grano grueso en lo
histórico. La poda se aplica de forma independiente por carpeta, así que una que cambia todos los días
y otra que casi no cambia nunca se podan cada una según su propio historial.

06Cómo restaurar

En los tres casos, primero se cargan las credenciales del repositorio en la sesión de terminal.

Ver los snapshots disponibles.

bash
# Todos los snapshots del repositorio
restic snapshots

# Solo los snapshots de una carpeta puntual
restic snapshots --tag carpeta-ejemplo

La salida incluye el ID corto de cada snapshot, la fecha y el tag — esos tres datos son los que se usan para restaurar.

Restauración completa (el 100% del repositorio). Trae de vuelta el estado completo del último snapshot de todas las carpetas:

bash
mkdir -p /srv/restore-completo
restic restore latest --target /srv/restore-completo

Para restaurar el estado de una fecha específica en vez del más reciente, se reemplaza
latest por el ID del snapshot correspondiente.

Restauración selectiva por carpeta. Como cada carpeta tiene su propio tag, se puede restaurar solo una sin tocar el resto:

bash
mkdir -p /srv/restore-carpeta-ejemplo
restic restore latest --tag carpeta-ejemplo --target /srv/restore-carpeta-ejemplo

Restauración de un único archivo. Dos formas, según convenga reconstruir la ruta completa o solo obtener el archivo suelto:

Opción A — a un directorio, manteniendo la ruta original:

bash
restic restore latest --tag carpeta-ejemplo \
    --target /tmp/restore-archivo \
    --include "/srv/****/carpeta-ejemplo/documento.xlsx"

# Queda en: /tmp/restore-archivo/srv/****/carpeta-ejemplo/documento.xlsx

Opción B — directo a un archivo, sin reconstruir subcarpetas:

bash
restic dump latest --tag carpeta-ejemplo \
    /srv/****/carpeta-ejemplo/documento.xlsx > /tmp/documento.xlsx

La opción B es la más práctica cuando solo hace falta ese archivo puntual, sin todo el árbol de carpetas que lo contiene.

Antes de restaurar sobre datos en producción
Restaurar hacia una ruta nueva (como en todos los ejemplos de arriba) es siempre seguro. Restaurar
directamente encima de la carpeta original sobrescribe el contenido actual — conviene verificar
primero en una ruta temporal antes de reemplazar datos en vivo.

07Operación y referencia rápida

Antes de instalar en cron:

  • Confirmar que la primera carga completa terminó sin carpetas en fallo total.
  • Revisar el resumen final del log: exitosas / fallidas / omitidas.
  • Volver a fijar un timeout acotado, ya que las corridas de cron en adelante van a ser incrementales.
  • Validar al menos una restauración de prueba antes de dar el proceso por confiable.
Sobre la clave de cifrado del repositorio
Sin la contraseña de cifrado del repositorio no hay forma de recuperar el backup — ni Nextcloud ni
nadie puede rescatar los datos si se pierde. Tiene que estar guardada también en un gestor de
contraseñas, no solo en el servidor.
Correr backup manual
bash restic-backup.sh
Ver snapshots
restic snapshots
Ver snapshots de una carpeta
restic snapshots --tag <carpeta>
Restaurar todo
restic restore latest --target <destino>
Restaurar una carpeta
restic restore latest --tag <carpeta> --target <destino>
Restaurar un archivo
restic dump latest --tag <carpeta> <ruta> > <destino>
Verificar integridad
restic check
Ver log del día
tail -f backup-$(date +%Y%m%d).log

08Código fuente completo

Versión completa del script, saneada de nombres de host y rutas reales — lista para adaptar
y reutilizar. Todas las rutas usan valores genéricos de ejemplo; los comentarios explican
cada bloque para que se entienda sin haber seguido el resto del artículo.

restic-backup.sh

restic-backup.sh
#!/bin/bash
# ============================================================
# restic-backup.sh
#
# Backup incremental con deduplicación de un árbol de carpetas
# hacia un backend remoto (Nextcloud u otro WebDAV) usando
# restic + rclone.
#
# QUÉ HACE:
#   - Detecta automáticamente las subcarpetas de primer nivel
#     dentro de BACKUP_SOURCE (no hace falta listarlas a mano).
#   - Genera un snapshot restic por carpeta, todos dentro del
#     mismo repositorio (permite deduplicación también entre
#     carpetas y retención independiente por carpeta).
#   - Reintenta ante fallos transitorios, con espera suficiente
#     para que el backend remoto libere locks huérfanos.
#   - Aplica una política de retención (poda automática) al
#     final de cada corrida completa.
#   - Corre una verificación de integridad semanal (domingos).
#
# REQUISITOS:
#   - restic y rclone instalados.
#   - Un remote de rclone ya configurado (rclone config) apuntando
#     al backend WebDAV/S3/etc. de destino.
#   - Un archivo de entorno con RESTIC_REPOSITORY y RESTIC_PASSWORD
#     (ver samba.env.example en este mismo repositorio de ejemplo).
#
# USO:
#   bash restic-backup.sh
#   (pensado para disparar por cron una vez que la primera carga
#    completa haya terminado sin errores)
# ============================================================

set -o pipefail

# ------------------------------------------------------------
# CONFIGURACIÓN — ajustar estas rutas a tu entorno
# ------------------------------------------------------------
BACKUP_SOURCE="/srv/datos"                       # carpeta base a respaldar
EXCLUDE_DIRS=(".nc-staging" "lost+found")        # subcarpetas a ignorar dentro de BACKUP_SOURCE

RESTIC_ENV="/etc/scripts/backup/repo.env"        # acá van RESTIC_REPOSITORY y RESTIC_PASSWORD

LOG_DIR="/var/log/restic-backup"
LOG_FILE="${LOG_DIR}/backup-$(date +%Y%m%d).log"
LOCK_FILE="/tmp/restic-backup.lock"

MAX_RETRIES=2
RETRY_WAIT=300          # segundos entre reintentos (5 min - le da tiempo al backend remoto de liberar locks huérfanos)
BACKUP_TIMEOUT=0        # segundos por carpeta. 0 = SIN LÍMITE.
                         # Usar 0 en la primera carga completa con carpetas grandes / enlace lento.
                         # Una vez que todas las carpetas tengan snapshot base, fijar un valor
                         # acotado (ej. 10800 = 3hs) como red de seguridad para las corridas
                         # incrementales — si una corrida incremental tarda eso, algo anómalo
                         # está pasando y conviene que el script lo corte y avise.

RETENTION_ARGS="--keep-daily 7 --keep-weekly 4 --keep-monthly 6"

RCLONE_CONNECTIONS=2     # límite de conexiones concurrentes hacia el backend remoto.
                          # Reduce la presión sobre sistemas de locking basados en Redis
                          # (típico en Nextcloud) a costa de una subida algo más lenta.

BACKUP_HOST_TAG="$(hostname)"   # identificador de host que queda grabado en cada snapshot

# ------------------------------------------------------------
# LOG
# ------------------------------------------------------------
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "${LOG_FILE}"
}

log_separator() {
    echo "============================================================" >> "${LOG_FILE}"
}

# ------------------------------------------------------------
# CLEANUP
# ------------------------------------------------------------
cleanup() {
    rm -f "${LOCK_FILE}"
    log "Script finalizado. Lock liberado."
}

# ------------------------------------------------------------
# INSTANCIA ÚNICA
# Evita que dos ejecuciones se solapen si una corrida anterior
# todavía no terminó (por ejemplo si tarda más que el intervalo
# de cron configurado).
# ------------------------------------------------------------
mkdir -p "${LOG_DIR}"

if [ -f "${LOCK_FILE}" ]; then
    PID=$(cat "${LOCK_FILE}")
    if kill -0 "${PID}" 2>/dev/null; then
        log "AVISO: Backup en curso (PID ${PID}). Saliendo sin hacer nada."
        exit 0
    else
        log "Lock huérfano (PID ${PID} ya no existe). Continuando."
        rm -f "${LOCK_FILE}"
    fi
fi

echo $$ > "${LOCK_FILE}"
trap cleanup EXIT

# ------------------------------------------------------------
# DEPENDENCIAS
# ------------------------------------------------------------
for cmd in restic rclone; do
    if ! command -v "$cmd" &>/dev/null; then
        log "ERROR: comando requerido no encontrado: $cmd"
        exit 1
    fi
done

if [ ! -f "${RESTIC_ENV}" ]; then
    log "ERROR: no se encuentra ${RESTIC_ENV} con las credenciales del repo"
    exit 1
fi

# shellcheck disable=SC1090
source "${RESTIC_ENV}"

if [ -z "${RESTIC_REPOSITORY}" ] || [ -z "${RESTIC_PASSWORD}" ]; then
    log "ERROR: RESTIC_REPOSITORY o RESTIC_PASSWORD no están seteados"
    exit 1
fi

export RESTIC_REPOSITORY
export RESTIC_PASSWORD

# ------------------------------------------------------------
# DETECCIÓN AUTOMÁTICA DE CARPETAS
# Toma todas las subcarpetas de primer nivel de BACKUP_SOURCE,
# salvo las listadas en EXCLUDE_DIRS. Si mañana se crea una
# carpeta nueva, la próxima corrida la incorpora sola.
# ------------------------------------------------------------
detect_folders() {
    local folder
    while IFS= read -r -d '' folder; do
        local name
        name=$(basename "${folder}")
        local skip=false
        for excl in "${EXCLUDE_DIRS[@]}"; do
            [ "${name}" = "${excl}" ] && skip=true && break
        done
        [ "${skip}" = true ] && continue
        echo "${name}"
    done < <(find "${BACKUP_SOURCE}" -mindepth 1 -maxdepth 1 -type d -print0 | sort -z)
}

# ------------------------------------------------------------
# BACKUP DE UNA CARPETA CON REINTENTOS
# ------------------------------------------------------------
backup_folder() {
    local folder="$1"
    local local_path="${BACKUP_SOURCE}/${folder}"
    local attempt=0
    local success=false

    if [ ! -d "${local_path}" ]; then
        log "  [SKIP] ${folder}: no existe (${local_path})"
        return 2
    fi

    while [ $attempt -lt $MAX_RETRIES ]; do
        attempt=$((attempt + 1))
        log "  [INTENTO ${attempt}/${MAX_RETRIES}] Backup: ${folder}"

        if [ "${BACKUP_TIMEOUT}" -eq 0 ]; then
            restic backup "${local_path}" \
                --tag "${folder}" \
                --host "${BACKUP_HOST_TAG}" \
                -o rclone.connections=${RCLONE_CONNECTIONS} \
                >> "${LOG_FILE}" 2>&1
        else
            timeout ${BACKUP_TIMEOUT} restic backup "${local_path}" \
                --tag "${folder}" \
                --host "${BACKUP_HOST_TAG}" \
                -o rclone.connections=${RCLONE_CONNECTIONS} \
                >> "${LOG_FILE}" 2>&1
        fi

        local exit_code=$?

        if [ $exit_code -eq 0 ]; then
            log "  [OK] ${folder}: backup completado"
            success=true
            break
        elif [ $exit_code -eq 124 ]; then
            log "  [TIMEOUT] ${folder}: superó ${BACKUP_TIMEOUT}s en intento ${attempt}"
        else
            log "  [ERROR] ${folder}: falló con código ${exit_code} en intento ${attempt}"
        fi

        if [ $attempt -lt $MAX_RETRIES ]; then
            log "  [ESPERA] Reintentando en ${RETRY_WAIT} segundos..."
            sleep ${RETRY_WAIT}
        fi
    done

    if [ "$success" = false ]; then
        log "  [FALLO TOTAL] ${folder}: agotados ${MAX_RETRIES} intentos"
        return 1
    fi

    return 0
}

# ------------------------------------------------------------
# EJECUCIÓN
# ------------------------------------------------------------
log_separator
log "INICIO DE BACKUP"
log "Repo   : ${RESTIC_REPOSITORY}"
log "Base   : ${BACKUP_SOURCE}"
log_separator

mapfile -t FOLDERS < <(detect_folders)

log "Carpetas detectadas: ${#FOLDERS[@]}"
for f in "${FOLDERS[@]}"; do
    log "  - ${f}"
done
log_separator

SUCCESS=0
FAILED=0
SKIPPED=0

for folder in "${FOLDERS[@]}"; do
    log ""
    log ">> Procesando carpeta: ${folder}"
    backup_folder "${folder}"
    result=$?

    case $result in
        0) SUCCESS=$((SUCCESS + 1)) ;;
        1) FAILED=$((FAILED + 1)) ;;
        2) SKIPPED=$((SKIPPED + 1)) ;;
    esac
done

# ------------------------------------------------------------
# RETENCIÓN
# --group-by paths hace que cada carpeta se pode de forma
# independiente, según su propio historial de snapshots.
# ------------------------------------------------------------
log ""
log_separator
log "APLICANDO POLÍTICA DE RETENCIÓN"
log "  ${RETENTION_ARGS}"

restic forget --group-by paths ${RETENTION_ARGS} --prune >> "${LOG_FILE}" 2>&1
if [ $? -eq 0 ]; then
    log "  [OK] Retención aplicada correctamente"
else
    log "  [ERROR] Falló restic forget/prune - revisar log"
fi

# ------------------------------------------------------------
# VERIFICACIÓN LIGERA DEL REPO (chequea estructura, no baja todo)
# Se corre una vez por semana (domingo) para no sumar carga diaria.
# ------------------------------------------------------------
if [ "$(date +%u)" -eq 7 ]; then
    log ""
    log "Verificación semanal del repositorio (check)..."
    restic check >> "${LOG_FILE}" 2>&1
    if [ $? -eq 0 ]; then
        log "  [OK] Repositorio íntegro"
    else
        log "  [ERROR] Problemas de integridad detectados - revisar log"
    fi
fi

# Rotar logs viejos (mantener últimos 30 días)
find "${LOG_DIR}" -name "backup-*.log" -mtime +30 -delete

log ""
log_separator
log "RESUMEN FINAL"
log "  Exitosas : ${SUCCESS}"
log "  Fallidas : ${FAILED}"
log "  Omitidas : ${SKIPPED}"
log_separator

[ $FAILED -eq 0 ] && exit 0 || exit 1

repo.env.example

repo.env.example
# ============================================================
# repo.env.example
#
# Plantilla del archivo de entorno que necesita restic-backup.sh.
# Copiar como "repo.env" (o el nombre que apunte RESTIC_ENV en el
# script), completar con los valores reales, y restringir permisos:
#
#   cp repo.env.example repo.env
#   chmod 600 repo.env
#   chown root:root repo.env
#
# No versionar el archivo real (con los valores completos) en
# ningún repositorio ni compartirlo — RESTIC_PASSWORD es la clave
# de cifrado del backup completo. Sin ella el backup es
# irrecuperable, no hay forma de rescatar los datos.
# ============================================================

# Ubicación del repositorio restic. El prefijo "rclone:" le dice a
# restic que use rclone como transporte; lo que sigue es
# "<nombre-del-remote-en-rclone>:<carpeta-remota>".
# El remote ("NOMBRE_REMOTE" acá) se configura antes con:
#   rclone config
export RESTIC_REPOSITORY="rclone:NOMBRE_REMOTE:CARPETA-REMOTA/restic-backup"

# Clave de cifrado del repositorio. Generar una fuerte, por ejemplo:
#   openssl rand -base64 32
# Guardarla también en un gestor de contraseñas aparte del servidor.
export RESTIC_PASSWORD="REEMPLAZAR-POR-UNA-CLAVE-FUERTE"
Antes de usarlo en otro entorno
Ajustar BACKUP_SOURCE, RESTIC_ENV, LOG_DIR y el nombre del
remote en repo.env a la infraestructura real. Los valores de este listado son
genéricos a propósito.