Guía Completa de Backups con Restic para Servidores RunCloud

Sistema de backups incrementales y deduplicados para servidores Linux gestionados con RunCloud, utilizando SFTP remoto y S3 (Wasabi).

Introducción

¿Por qué Restic?

Restic es una herramienta de backup moderna que ofrece:

  • Deduplicación: Solo almacena los cambios reales entre snapshots
  • Cifrado: Todos los datos se cifran con AES-256 antes de salir del servidor
  • Incrementalidad: Después del backup inicial, solo se transfieren los bloques modificados
  • Verificación: Integridad de datos verificable en cualquier momento
  • Múltiples backends: Soporta SFTP, S3, B2, Azure, Google Cloud, y más

Arquitectura Propuesta

┌─────────────────────────────────────────────────────────────┐
│                    SERVIDOR RUNCLOUD                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   /home/    │  │  MySQL/     │  │     PostgreSQL      │  │
│  │  (usuarios) │  │  MariaDB    │  │                     │  │
│  └──────┬──────┘  └──────┬──────┘  └──────────┬──────────┘  │
│         │                │                     │             │
│         └────────────────┼─────────────────────┘             │
│                          │                                   │
│                    ┌─────▼─────┐                             │
│                    │  RESTIC   │                             │
│                    └─────┬─────┘                             │
└──────────────────────────┼───────────────────────────────────┘
                           │
            ┌──────────────┼──────────────┐
            │              │              │
            ▼              ▼              ▼
     ┌──────────┐   ┌──────────┐   ┌──────────┐
     │   SFTP   │   │  WASABI  │   │  LOCAL   │
     │ (Hetzner)│   │   (S3)   │   │ (opcional)│
     └──────────┘   └──────────┘   └──────────┘

Instalación de Restic

Opción 1: Desde repositorios (puede estar desactualizado)

# Ubuntu/Debian
sudo apt update && sudo apt install restic

# Verificar versión
restic version
Lenguaje del código: PHP (php)

Opción 2: Binario oficial (recomendado)

# Descargar última versión
RESTIC_VERSION=$(curl -s https://api.github.com/repos/restic/restic/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//')
wget https://github.com/restic/restic/releases/download/v${RESTIC_VERSION}/restic_${RESTIC_VERSION}_linux_amd64.bz2

# Descomprimir e instalar
bunzip2 restic_${RESTIC_VERSION}_linux_amd64.bz2
chmod +x restic_${RESTIC_VERSION}_linux_amd64
sudo mv restic_${RESTIC_VERSION}_linux_amd64 /usr/local/bin/restic

# Verificar instalación
restic version
Lenguaje del código: PHP (php)

Actualizar Restic

# Si se instaló con el binario oficial
sudo restic self-update
Lenguaje del código: PHP (php)

Conceptos Clave

Repositorio

El repositorio es donde Restic almacena todos los backups. Está cifrado y contiene:

  • Snapshots: Puntos en el tiempo de tus backups
  • Data blobs: Bloques de datos deduplicados
  • Index: Índice para búsqueda rápida

Deduplicación

Restic divide los archivos en bloques (chunks) de tamaño variable. Si un bloque ya existe en el repositorio (de cualquier snapshot anterior), no se vuelve a subir. Esto significa que:

  • El primer backup de /home/ puede ser grande (ej: 50GB)
  • Los siguientes backups solo suben los cambios reales (ej: 500MB)
  • Múltiples snapshots comparten los mismos bloques de datos

Contraseña del Repositorio

CRÍTICO: La contraseña del repositorio cifra todos tus datos. Si la pierdes, no hay forma de recuperar los backups. Guárdala en un lugar seguro fuera del servidor.


Configuración con SFTP

1. Preparar Autenticación SSH

# Generar clave SSH si no existe
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_backup -N ""

# Copiar clave al servidor SFTP (ejemplo con Hetzner Storage Box)
ssh-copy-id -i ~/.ssh/id_ed25519_backup.pub [email protected]

# Verificar acceso sin contraseña
ssh -i ~/.ssh/id_ed25519_backup [email protected]
Lenguaje del código: PHP (php)

2. Configurar SSH para Restic

Crear o editar ~/.ssh/config:

cat >> ~/.ssh/config << 'EOF'
Host backup-sftp
    HostName u393142-sub4.your-storagebox.de
    User u393142-sub4
    IdentityFile ~/.ssh/id_ed25519_backup
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3
EOF

chmod 600 ~/.ssh/config
Lenguaje del código: JavaScript (javascript)

3. Crear Archivo de Contraseña

# Crear directorio seguro para configuración
sudo mkdir -p /etc/restic
sudo chmod 700 /etc/restic

# Generar contraseña segura (o usa una propia)
openssl rand -base64 32 | sudo tee /etc/restic/password > /dev/null
sudo chmod 600 /etc/restic/password

# IMPORTANTE: Guarda una copia de esta contraseña fuera del servidor
cat /etc/restic/password
Lenguaje del código: PHP (php)

4. Inicializar Repositorio SFTP

# Formato: sftp:user@host:path o sftp://user@host/path
restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       init
Lenguaje del código: PHP (php)

5. Primer Backup de Prueba

restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       backup /home/
Lenguaje del código: JavaScript (javascript)

Configuración con S3 (Wasabi)

1. Crear Cuenta y Bucket en Wasabi

  1. Registrarse en Wasabi
  2. Crear un bucket (ej: miempresa-backups)
  3. Seleccionar región (ej: eu-central-1 para Europa)
  4. Crear Access Key en Access Keys > Create New Access Key

2. Configurar Credenciales

# Crear archivo de entorno
sudo tee /etc/restic/wasabi.env > /dev/null << 'EOF'
# Wasabi S3 Configuration
export AWS_ACCESS_KEY_ID="TU_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="TU_SECRET_KEY"
export RESTIC_REPOSITORY="s3:https://s3.eu-central-1.wasabisys.com/miempresa-backups"
export RESTIC_PASSWORD_FILE="/etc/restic/password"
EOF

sudo chmod 600 /etc/restic/wasabi.env
Lenguaje del código: PHP (php)

Regiones de Wasabi disponibles:

RegiónEndpoint
US East 1s3.wasabisys.com o s3.us-east-1.wasabisys.com
US East 2s3.us-east-2.wasabisys.com
US West 1s3.us-west-1.wasabisys.com
EU Central 1s3.eu-central-1.wasabisys.com
EU West 1s3.eu-west-1.wasabisys.com
EU West 2s3.eu-west-2.wasabisys.com
AP Northeast 1s3.ap-northeast-1.wasabisys.com
AP Northeast 2s3.ap-northeast-2.wasabisys.com

3. Inicializar Repositorio S3

# Cargar variables de entorno
source /etc/restic/wasabi.env

# Inicializar repositorio
restic init

# O especificando todo en línea
restic -r s3:https://s3.eu-central-1.wasabisys.com/miempresa-backups \
       --password-file /etc/restic/password \
       init
Lenguaje del código: PHP (php)

4. Primer Backup de Prueba

source /etc/restic/wasabi.env
restic backup /home/

Backup de Bases de Datos

Las bases de datos requieren un tratamiento especial: no podemos hacer backup de los archivos directamente mientras están en uso. Necesitamos crear volcados (dumps) consistentes.

Estructura de Directorios

# Crear directorio para dumps temporales
sudo mkdir -p /var/backups/databases/{mysql,postgresql}
sudo chmod 700 /var/backups/databases
Lenguaje del código: PHP (php)

MySQL / MariaDB

Crear Usuario de Backup (recomendado)

-- Conectar como root
mysql -u root -p

-- Crear usuario dedicado para backups
CREATE USER 'backup'@'localhost' IDENTIFIED BY 'contraseña_segura';
GRANT SELECT, SHOW VIEW, RELOAD, REPLICATION CLIENT, EVENT, TRIGGER, LOCK TABLES ON *.* TO 'backup'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Lenguaje del código: PHP (php)

Configurar Credenciales

# Crear archivo de credenciales MySQL
sudo tee /etc/restic/mysql.cnf > /dev/null << 'EOF'Lenguaje del código: PHP (php)

[client]

user=backup password=contraseña_segura EOF sudo chmod 600 /etc/restic/mysql.cnf

Script de Dump MySQL

sudo tee /etc/restic/scripts/dump-mysql.sh > /dev/null << 'EOF'
#!/bin/bash
#
# Dump de todas las bases de datos MySQL/MariaDB
#

DUMP_DIR="/var/backups/databases/mysql"
MYSQL_CNF="/etc/restic/mysql.cnf"
DATE=$(date +%Y%m%d_%H%M%S)

# Limpiar dumps anteriores
rm -f ${DUMP_DIR}/*.sql.gz

# Obtener lista de bases de datos (excluyendo las del sistema)
DATABASES=$(mysql --defaults-file=${MYSQL_CNF} -N -e "SHOW DATABASES" | grep -Ev "^(information_schema|performance_schema|mysql|sys)$")

# Dump individual por base de datos
for DB in ${DATABASES}; do
    echo "Dumping database: ${DB}"
    mysqldump --defaults-file=${MYSQL_CNF} \
              --single-transaction \
              --routines \
              --triggers \
              --events \
              --quick \
              --lock-tables=false \
              ${DB} | gzip > "${DUMP_DIR}/${DB}_${DATE}.sql.gz"
done

# Dump global (todas las bases de datos en un archivo)
echo "Creating global dump..."
mysqldump --defaults-file=${MYSQL_CNF} \
          --all-databases \
          --single-transaction \
          --routines \
          --triggers \
          --events \
          --quick \
          --lock-tables=false | gzip > "${DUMP_DIR}/all_databases_${DATE}.sql.gz"

echo "MySQL dump completed: $(ls -lh ${DUMP_DIR}/)"
EOF

sudo chmod +x /etc/restic/scripts/dump-mysql.sh
Lenguaje del código: PHP (php)

PostgreSQL

Configurar Autenticación

En RunCloud, PostgreSQL suele usar autenticación peer. Para backups automatizados:

# Crear archivo .pgpass para el usuario postgres
sudo -u postgres tee ~postgres/.pgpass > /dev/null << 'EOF'
localhost:5432:*:postgres:tu_contraseña
EOF

sudo -u postgres chmod 600 ~postgres/.pgpass
Lenguaje del código: PHP (php)

Script de Dump PostgreSQL

sudo tee /etc/restic/scripts/dump-postgresql.sh > /dev/null << 'EOF'
#!/bin/bash
#
# Dump de todas las bases de datos PostgreSQL
#

DUMP_DIR="/var/backups/databases/postgresql"
DATE=$(date +%Y%m%d_%H%M%S)

# Limpiar dumps anteriores
rm -f ${DUMP_DIR}/*.sql.gz
rm -f ${DUMP_DIR}/*.dump

# Obtener lista de bases de datos (excluyendo templates)
DATABASES=$(sudo -u postgres psql -t -c "SELECT datname FROM pg_database WHERE datistemplate = false AND datname != 'postgres'")

# Dump individual por base de datos (formato custom para restauración flexible)
for DB in ${DATABASES}; do
    DB=$(echo ${DB} | xargs)  # Trim whitespace
    if [ -n "${DB}" ]; then
        echo "Dumping database: ${DB}"
        sudo -u postgres pg_dump -Fc ${DB} > "${DUMP_DIR}/${DB}_${DATE}.dump"
    fi
done

# Dump global en formato SQL (todas las bases de datos)
echo "Creating global dump..."
sudo -u postgres pg_dumpall | gzip > "${DUMP_DIR}/all_databases_${DATE}.sql.gz"

echo "PostgreSQL dump completed: $(ls -lh ${DUMP_DIR}/)"
EOF

sudo chmod +x /etc/restic/scripts/dump-postgresql.sh
Lenguaje del código: PHP (php)

Scripts de Automatización

Estructura de Archivos

/etc/restic/
├── password                    # Contraseña del repositorio
├── wasabi.env                  # Variables de entorno para Wasabi
├── mysql.cnf                   # Credenciales MySQL
├── exclude.txt                 # Patrones de exclusión
└── scripts/
    ├── dump-mysql.sh           # Script dump MySQL
    ├── dump-postgresql.sh      # Script dump PostgreSQL
    ├── backup-sftp.sh          # Backup a SFTP
    ├── backup-wasabi.sh        # Backup a Wasabi
    └── backup-full.sh          # Script principal
Lenguaje del código: PHP (php)

Archivo de Exclusiones

sudo tee /etc/restic/exclude.txt > /dev/null << 'EOF'
# Cache y archivos temporales
**/cache/**
**/.cache/**
**/tmp/**
**/*.tmp
**/*.temp
**/*.log

# Node.js
**/node_modules/**

# Python
**/__pycache__/**
**/*.pyc
**/.venv/**
**/venv/**

# Composer
**/vendor/**

# Git (opcional, descomentar si no quieres incluir repos)
# **/.git/**

# Archivos de sesión PHP
**/sessions/**

# Archivos de socket
**/*.sock
**/*.socket

# RunCloud específico
**/logs/**
**/.well-known/acme-challenge/**

# Backups locales antiguos (evitar duplicados)
**/*.sql.gz.old
**/*.tar.gz.old
EOF

sudo chmod 644 /etc/restic/exclude.txt
Lenguaje del código: PHP (php)

Script Principal de Backup

sudo mkdir -p /etc/restic/scripts

sudo tee /etc/restic/scripts/backup-full.sh > /dev/null << 'EOF'
#!/bin/bash
#
# Script principal de backup con Restic
# Uso: ./backup-full.sh [sftp|wasabi|both]
#
set -e

# Configuración
SCRIPTS_DIR="/etc/restic/scripts"
LOG_DIR="/var/log/restic"
LOG_FILE="${LOG_DIR}/backup_$(date +%Y%m%d).log"
LOCK_FILE="/tmp/restic-backup.lock"
EXCLUDE_FILE="/etc/restic/exclude.txt"
PASSWORD_FILE="/etc/restic/password"

# Directorios a respaldar
BACKUP_PATHS=(
    "/home"
    "/var/backups/databases"
)

# Configuración de repositorios
SFTP_REPO="sftp:backup-sftp:/nshosting43"
WASABI_ENV="/etc/restic/wasabi.env"

# Colores para output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Funciones de logging
log() {
    echo -e "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "${LOG_FILE}"
}

log_success() {
    echo -e "$(date '+%Y-%m-%d %H:%M:%S') - ${GREEN}✓ $1${NC}" | tee -a "${LOG_FILE}"
}

log_error() {
    echo -e "$(date '+%Y-%m-%d %H:%M:%S') - ${RED}✗ $1${NC}" | tee -a "${LOG_FILE}"
}

log_warning() {
    echo -e "$(date '+%Y-%m-%d %H:%M:%S') - ${YELLOW}⚠ $1${NC}" | tee -a "${LOG_FILE}"
}

# Crear directorio de logs
mkdir -p "${LOG_DIR}"

# Verificar que no hay otro backup en ejecución
if [ -f "${LOCK_FILE}" ]; then
    PID=$(cat "${LOCK_FILE}")
    if ps -p ${PID} > /dev/null 2>&1; then
        log_error "Backup already running (PID: ${PID})"
        exit 1
    else
        log_warning "Stale lock file found, removing..."
        rm -f "${LOCK_FILE}"
    fi
fi

# Crear lock file
echo $$ > "${LOCK_FILE}"
trap "rm -f ${LOCK_FILE}" EXIT

# Determinar destino(s) del backup
BACKUP_TARGET="${1:-both}"

log "=========================================="
log "Starting backup process (target: ${BACKUP_TARGET})"
log "=========================================="

# Paso 1: Dump de bases de datos
log "Step 1: Dumping databases..."

if [ -x "${SCRIPTS_DIR}/dump-mysql.sh" ]; then
    log "  Dumping MySQL databases..."
    ${SCRIPTS_DIR}/dump-mysql.sh >> "${LOG_FILE}" 2>&1 && \
        log_success "MySQL dump completed" || \
        log_error "MySQL dump failed"
fi

if [ -x "${SCRIPTS_DIR}/dump-postgresql.sh" ]; then
    log "  Dumping PostgreSQL databases..."
    ${SCRIPTS_DIR}/dump-postgresql.sh >> "${LOG_FILE}" 2>&1 && \
        log_success "PostgreSQL dump completed" || \
        log_error "PostgreSQL dump failed"
fi

# Paso 2: Backup a SFTP
backup_sftp() {
    log "Step 2a: Backing up to SFTP..."
    
    restic -r "${SFTP_REPO}" \
           --password-file "${PASSWORD_FILE}" \
           --exclude-file "${EXCLUDE_FILE}" \
           --verbose \
           backup ${BACKUP_PATHS[@]} >> "${LOG_FILE}" 2>&1
    
    if [ $? -eq 0 ]; then
        log_success "SFTP backup completed"
        
        # Aplicar política de retención
        log "  Applying retention policy to SFTP..."
        restic -r "${SFTP_REPO}" \
               --password-file "${PASSWORD_FILE}" \
               forget \
               --keep-hourly 24 \
               --keep-daily 7 \
               --keep-weekly 4 \
               --keep-monthly 12 \
               --keep-yearly 2 \
               --prune >> "${LOG_FILE}" 2>&1
        log_success "SFTP retention policy applied"
    else
        log_error "SFTP backup failed"
        return 1
    fi
}

# Paso 3: Backup a Wasabi S3
backup_wasabi() {
    log "Step 2b: Backing up to Wasabi S3..."
    
    if [ -f "${WASABI_ENV}" ]; then
        source "${WASABI_ENV}"
        
        restic --exclude-file "${EXCLUDE_FILE}" \
               --verbose \
               backup ${BACKUP_PATHS[@]} >> "${LOG_FILE}" 2>&1
        
        if [ $? -eq 0 ]; then
            log_success "Wasabi backup completed"
            
            # Aplicar política de retención
            log "  Applying retention policy to Wasabi..."
            restic forget \
                   --keep-hourly 24 \
                   --keep-daily 7 \
                   --keep-weekly 4 \
                   --keep-monthly 12 \
                   --keep-yearly 2 \
                   --prune >> "${LOG_FILE}" 2>&1
            log_success "Wasabi retention policy applied"
        else
            log_error "Wasabi backup failed"
            return 1
        fi
    else
        log_warning "Wasabi environment file not found, skipping..."
    fi
}

# Ejecutar según el target especificado
case "${BACKUP_TARGET}" in
    sftp)
        backup_sftp
        ;;
    wasabi)
        backup_wasabi
        ;;
    both)
        backup_sftp
        backup_wasabi
        ;;
    *)
        log_error "Invalid target: ${BACKUP_TARGET}. Use: sftp, wasabi, or both"
        exit 1
        ;;
esac

# Paso 4: Verificar integridad (opcional, ejecutar periódicamente)
if [ "$(date +%u)" -eq 7 ]; then  # Solo los domingos
    log "Step 3: Running integrity check (weekly)..."
    
    if [ "${BACKUP_TARGET}" = "sftp" ] || [ "${BACKUP_TARGET}" = "both" ]; then
        restic -r "${SFTP_REPO}" \
               --password-file "${PASSWORD_FILE}" \
               check >> "${LOG_FILE}" 2>&1 && \
            log_success "SFTP repository integrity verified" || \
            log_error "SFTP repository integrity check failed"
    fi
    
    if [ "${BACKUP_TARGET}" = "wasabi" ] || [ "${BACKUP_TARGET}" = "both" ]; then
        source "${WASABI_ENV}"
        restic check >> "${LOG_FILE}" 2>&1 && \
            log_success "Wasabi repository integrity verified" || \
            log_error "Wasabi repository integrity check failed"
    fi
fi

log "=========================================="
log "Backup process completed"
log "=========================================="

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

exit 0
EOF

sudo chmod +x /etc/restic/scripts/backup-full.sh
Lenguaje del código: PHP (php)

Configurar Cron

# Editar crontab de root
sudo crontab -e
Lenguaje del código: PHP (php)

Añadir las siguientes líneas:

# Restic Backup Schedule
# ─────────────────────────────────────────────────────────

# Backup completo diario a las 3:00 AM (a ambos destinos)
0 3 * * * /etc/restic/scripts/backup-full.sh both >> /var/log/restic/cron.log 2>&1

# Backup incremental cada 6 horas solo a SFTP (más rápido)
0 */6 * * * /etc/restic/scripts/backup-full.sh sftp >> /var/log/restic/cron.log 2>&1

# Verificación de integridad mensual (primer domingo del mes a las 5:00 AM)
0 5 1-7 * 0 /usr/local/bin/restic -r sftp:backup-sftp:/nshosting43 --password-file /etc/restic/password check >> /var/log/restic/check.log 2>&1
Lenguaje del código: PHP (php)

Políticas de Retención

Restic usa el comando forget para eliminar snapshots antiguos según políticas definidas.

Política Recomendada

restic forget \
    --keep-hourly 24 \      # Últimas 24 horas (1 por hora)
    --keep-daily 7 \        # Últimos 7 días
    --keep-weekly 4 \       # Últimas 4 semanas
    --keep-monthly 12 \     # Últimos 12 meses
    --keep-yearly 2 \       # Últimos 2 años
    --prune                 # Eliminar datos huérfanos
Lenguaje del código: PHP (php)

Explicación Visual

Tiempo ←───────────────────────────────────────────────────── Ahora
         │
         ├─── Últimas 24 horas: 1 snapshot por hora (máx 24)
         │    ████████████████████████
         │
         ├─── Última semana: 1 snapshot por día (máx 7)
         │    ███████
         │
         ├─── Último mes: 1 snapshot por semana (máx 4)
         │    ████
         │
         ├─── Último año: 1 snapshot por mes (máx 12)
         │    ████████████
         │
         └─── Histórico: 1 snapshot por año (máx 2)
              ██

Ver Snapshots Actuales

# SFTP
restic -r sftp:backup-sftp:/nshosting43 --password-file /etc/restic/password snapshots

# Wasabi
source /etc/restic/wasabi.env
restic snapshots
Lenguaje del código: PHP (php)

Simular Retención (dry-run)

restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       forget \
       --keep-daily 7 \
       --keep-weekly 4 \
       --dry-run
Lenguaje del código: JavaScript (javascript)

Restauración de Backups

Listar Snapshots

# Ver todos los snapshots
restic -r sftp:backup-sftp:/nshosting43 --password-file /etc/restic/password snapshots

# Filtrar por ruta
restic -r sftp:backup-sftp:/nshosting43 --password-file /etc/restic/password snapshots --path /home

# Ver archivos dentro de un snapshot específico
restic -r sftp:backup-sftp:/nshosting43 --password-file /etc/restic/password ls <snapshot-id>
Lenguaje del código: PHP (php)

Restaurar Archivos

Restaurar Todo un Snapshot

# Restaurar a ubicación original
restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       restore <snapshot-id> \
       --target /

# Restaurar a ubicación alternativa
restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       restore <snapshot-id> \
       --target /tmp/restore
Lenguaje del código: PHP (php)

Restaurar Archivos Específicos

# Restaurar solo un directorio específico
restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       restore <snapshot-id> \
       --target /tmp/restore \
       --include /home/runcloud/webapps/miapp

# Restaurar un archivo específico
restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       restore <snapshot-id> \
       --target /tmp/restore \
       --include /home/runcloud/webapps/miapp/wp-config.php
Lenguaje del código: PHP (php)

Usar latest para el Snapshot Más Reciente

restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       restore latest \
       --target /tmp/restore \
       --include /home/runcloud
Lenguaje del código: JavaScript (javascript)

Restaurar Base de Datos

MySQL

# 1. Restaurar el archivo de dump
restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       restore latest \
       --target /tmp/restore \
       --include /var/backups/databases/mysql

# 2. Descomprimir
gunzip /tmp/restore/var/backups/databases/mysql/mibase_*.sql.gz

# 3. Restaurar la base de datos
mysql -u root -p mibase < /tmp/restore/var/backups/databases/mysql/mibase_*.sql
Lenguaje del código: PHP (php)

PostgreSQL

# 1. Restaurar el archivo de dump
restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       restore latest \
       --target /tmp/restore \
       --include /var/backups/databases/postgresql

# 2. Restaurar desde formato custom
sudo -u postgres pg_restore -d mibase /tmp/restore/var/backups/databases/postgresql/mibase_*.dump

# O restaurar desde SQL
gunzip /tmp/restore/var/backups/databases/postgresql/all_databases_*.sql.gz
sudo -u postgres psql < /tmp/restore/var/backups/databases/postgresql/all_databases_*.sql
Lenguaje del código: PHP (php)

Montar Snapshot como Sistema de Archivos

Muy útil para explorar backups sin restaurar completamente:

# Crear punto de montaje
mkdir -p /mnt/restic

# Montar
restic -r sftp:backup-sftp:/nshosting43 \
       --password-file /etc/restic/password \
       mount /mnt/restic

# En otra terminal, explorar:
ls /mnt/restic/snapshots/
ls /mnt/restic/snapshots/latest/home/

# Copiar archivos directamente
cp /mnt/restic/snapshots/latest/home/runcloud/webapps/miapp/wp-config.php /tmp/

# Desmontar cuando termines (Ctrl+C en la terminal del mount)
Lenguaje del código: PHP (php)

Monitorización y Alertas

Script de Notificación

sudo tee /etc/restic/scripts/notify.sh > /dev/null << 'EOF'
#!/bin/bash
#
# Script de notificación para backups
#

WEBHOOK_URL="https://hooks.slack.com/services/xxx/yyy/zzz"  # Opcional: Slack
EMAIL="[email protected]"

STATUS="$1"
MESSAGE="$2"
HOSTNAME=$(hostname)

# Enviar notificación por email (requiere mailutils configurado)
send_email() {
    echo "${MESSAGE}" | mail -s "[Backup ${STATUS}] ${HOSTNAME}" ${EMAIL}
}

# Enviar a Slack (opcional)
send_slack() {
    if [ -n "${WEBHOOK_URL}" ] && [ "${WEBHOOK_URL}" != "https://hooks.slack.com/services/xxx/yyy/zzz" ]; then
        COLOR="good"
        [ "${STATUS}" = "ERROR" ] && COLOR="danger"
        [ "${STATUS}" = "WARNING" ] && COLOR="warning"
        
        curl -s -X POST -H 'Content-type: application/json' \
            --data "{
                \"attachments\": [{
                    \"color\": \"${COLOR}\",
                    \"title\": \"Backup ${STATUS} - ${HOSTNAME}\",
                    \"text\": \"${MESSAGE}\",
                    \"footer\": \"Restic Backup System\",
                    \"ts\": $(date +%s)
                }]
            }" \
            "${WEBHOOK_URL}"
    fi
}

# Enviar notificaciones
send_email
send_slack
EOF

sudo chmod +x /etc/restic/scripts/notify.sh
Lenguaje del código: PHP (php)

Healthchecks.io (Recomendado)

Servicio gratuito para monitorizar cron jobs:

# Añadir al final del script de backup
HEALTHCHECK_URL="https://hc-ping.com/tu-uuid-aquí"

# Al inicio del backup
curl -fsS -m 10 --retry 5 "${HEALTHCHECK_URL}/start"

# Al final exitoso
curl -fsS -m 10 --retry 5 "${HEALTHCHECK_URL}"

# En caso de error
curl -fsS -m 10 --retry 5 "${HEALTHCHECK_URL}/fail"
Lenguaje del código: PHP (php)

Verificar Estado de Backups

sudo tee /etc/restic/scripts/check-status.sh > /dev/null << 'EOF'
#!/bin/bash
#
# Verificar estado de los backups
#

PASSWORD_FILE="/etc/restic/password"
SFTP_REPO="sftp:backup-sftp:/nshosting43"
WASABI_ENV="/etc/restic/wasabi.env"
MAX_AGE_HOURS=25  # Alertar si el último backup tiene más de 25 horas

check_repo() {
    local REPO_NAME="$1"
    local REPO="$2"
    
    echo "=== ${REPO_NAME} ==="
    
    # Obtener último snapshot
    LAST_SNAPSHOT=$(restic -r "${REPO}" --password-file "${PASSWORD_FILE}" snapshots --json --latest 1 2>/dev/null)
    
    if [ -z "${LAST_SNAPSHOT}" ] || [ "${LAST_SNAPSHOT}" = "[]" ]; then
        echo "ERROR: No snapshots found!"
        return 1
    fi
    
    LAST_TIME=$(echo "${LAST_SNAPSHOT}" | jq -r '.[0].time')
    LAST_EPOCH=$(date -d "${LAST_TIME}" +%s)
    NOW_EPOCH=$(date +%s)
    AGE_HOURS=$(( (NOW_EPOCH - LAST_EPOCH) / 3600 ))
    
    echo "Last backup: ${LAST_TIME}"
    echo "Age: ${AGE_HOURS} hours"
    
    if [ ${AGE_HOURS} -gt ${MAX_AGE_HOURS} ]; then
        echo "WARNING: Backup is older than ${MAX_AGE_HOURS} hours!"
        return 1
    fi
    
    # Verificar estadísticas del repo
    echo ""
    restic -r "${REPO}" --password-file "${PASSWORD_FILE}" stats --mode raw-data
    echo ""
    
    return 0
}

echo "Backup Status Report - $(date)"
echo "=================================="
echo ""

# Verificar SFTP
check_repo "SFTP Repository" "${SFTP_REPO}"

echo ""

# Verificar Wasabi
if [ -f "${WASABI_ENV}" ]; then
    source "${WASABI_ENV}"
    check_repo "Wasabi S3 Repository" "${RESTIC_REPOSITORY}"
fi
EOF

sudo chmod +x /etc/restic/scripts/check-status.sh
Lenguaje del código: PHP (php)

Mejores Prácticas

Seguridad

  1. Contraseña del repositorio:
    • Usa una contraseña fuerte (mínimo 20 caracteres)
    • Guarda una copia fuera del servidor (gestor de contraseñas, caja fuerte)
    • Si pierdes la contraseña, pierdes los backups
  2. Permisos de archivos: sudo chmod 700 /etc/restic sudo chmod 600 /etc/restic/* sudo chmod 700 /etc/restic/scripts sudo chmod 700 /etc/restic/scripts/*
  3. Claves SSH dedicadas:
    • Usa claves SSH específicas para backups
    • Considera restricciones en authorized_keys del servidor SFTP
  4. Credenciales S3:
    • Crea un usuario IAM dedicado con permisos mínimos
    • Nunca uses credenciales root de AWS/Wasabi

Rendimiento

  1. Ancho de banda: # Limitar velocidad de subida (útil para no saturar conexión) restic backup --limit-upload 5000 /home # 5000 KB/s
  2. Exclusiones inteligentes:
    • Excluye node_modules, vendor, cache
    • Excluye logs rotativos
    • Excluye archivos temporales
  3. Paralelismo: # Usar más conexiones paralelas para S3 restic backup --option s3.connections=10 /home

Verificación

  1. Verificación regular: # Verificar integridad del repositorio (semanalmente) restic check # Verificación completa con lectura de datos (mensualmente) restic check --read-data
  2. Pruebas de restauración:
    • Programa pruebas de restauración periódicas
    • Documenta el procedimiento de restauración
    • Verifica que las bases de datos restauradas funcionan

Múltiples Destinos (3-2-1 Rule)

La regla 3-2-1:

  • 3 copias de tus datos
  • 2 tipos de almacenamiento diferentes
  • 1 copia offsite
┌─────────────────┐
│ Datos Origen    │
│ (Servidor)      │
└────────┬────────┘
         │
    ┌────┴────┐
    │         │
    ▼         ▼
┌───────┐ ┌───────┐
│ SFTP  │ │Wasabi │  ← 2 medios diferentes
│Hetzner│ │  S3   │  ← 1 (o ambos) offsite
└───────┘ └───────┘

Comandos de Referencia Rápida

# ═══════════════════════════════════════════════════════════
# COMANDOS RESTIC - REFERENCIA RÁPIDA
# ═══════════════════════════════════════════════════════════

# --- Configuración común ---
export RESTIC_REPOSITORY="sftp:backup-sftp:/nshosting43"
export RESTIC_PASSWORD_FILE="/etc/restic/password"

# --- Operaciones básicas ---
restic init                          # Inicializar repositorio
restic backup /home                  # Hacer backup
restic snapshots                     # Listar snapshots
restic ls latest                     # Ver contenido del último snapshot
restic stats                         # Estadísticas del repositorio

# --- Restauración ---
restic restore latest --target /tmp/restore              # Restaurar todo
restic restore latest --target /tmp --include /home/user # Restaurar parcial
restic mount /mnt/restic                                 # Montar como FS

# --- Mantenimiento ---
restic forget --keep-daily 7 --prune    # Aplicar retención
restic check                             # Verificar integridad
restic check --read-data                 # Verificación completa

# --- Información ---
restic diff <snap1> <snap2>             # Diferencias entre snapshots
restic find "*.php"                     # Buscar archivos
restic cat snapshot <id>                # Ver metadatos de snapshot

# --- Utilidades ---
restic unlock                           # Desbloquear repo (si hay locks)
restic cache --cleanup                  # Limpiar cache local
restic self-update                      # Actualizar restic
Lenguaje del código: PHP (php)

Troubleshooting

Error: «repository is already locked»

# Ver locks activos
restic -r sftp:backup-sftp:/nshosting43 --password-file /etc/restic/password list locks

# Eliminar locks (solo si estás seguro de que no hay backup en curso)
restic -r sftp:backup-sftp:/nshosting43 --password-file /etc/restic/password unlock
Lenguaje del código: PHP (php)

Error de conexión SFTP

# Verificar conectividad SSH
ssh -vvv backup-sftp

# Verificar que el directorio existe
ssh backup-sftp "ls -la /nshosting43"

# Probar con ruta alternativa
restic -r sftp:[email protected]:/nshosting43 ...
Lenguaje del código: PHP (php)

Backup muy lento

# Verificar velocidad de red
iperf3 -c servidor-destino

# Usar más conexiones paralelas
restic backup --option s3.connections=20 /home

# Verificar exclusiones
restic backup --dry-run -vv /home 2>&1 | grep -E "(added|skipped)"
Lenguaje del código: PHP (php)

Cache corrupto

# Limpiar cache de restic
restic cache --cleanup

# Ubicación del cache
ls -la ~/.cache/restic/
Lenguaje del código: PHP (php)

Suscríbete al boletín SysAdmin

Este es tu recurso para las últimas noticias y consejos sobre administración de sistemas, Linux, Windows, cloud computing, seguridad de la nube, etc. Lo enviamos 2 días a la semana.

¡Apúntate a nuestro newsletter!


– patrocinadores –

Noticias destacadas

– patrocinadores –

¡SUSCRÍBETE AL BOLETÍN
DE LOS SYSADMINS!

Scroll al inicio
×