Sistema de backups incrementales y deduplicados para servidores Linux gestionados con RunCloud, utilizando SFTP remoto y S3 (Wasabi).
Tabla de contenidos
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
- Registrarse en Wasabi
- Crear un bucket (ej:
miempresa-backups) - Seleccionar región (ej:
eu-central-1para Europa) - 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ón | Endpoint |
|---|---|
| US East 1 | s3.wasabisys.com o s3.us-east-1.wasabisys.com |
| US East 2 | s3.us-east-2.wasabisys.com |
| US West 1 | s3.us-west-1.wasabisys.com |
| EU Central 1 | s3.eu-central-1.wasabisys.com |
| EU West 1 | s3.eu-west-1.wasabisys.com |
| EU West 2 | s3.eu-west-2.wasabisys.com |
| AP Northeast 1 | s3.ap-northeast-1.wasabisys.com |
| AP Northeast 2 | s3.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
- 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
- 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/* - Claves SSH dedicadas:
- Usa claves SSH específicas para backups
- Considera restricciones en
authorized_keysdel servidor SFTP
- Credenciales S3:
- Crea un usuario IAM dedicado con permisos mínimos
- Nunca uses credenciales root de AWS/Wasabi
Rendimiento
- Ancho de banda:
# Limitar velocidad de subida (útil para no saturar conexión) restic backup --limit-upload 5000 /home # 5000 KB/s - Exclusiones inteligentes:
- Excluye
node_modules,vendor,cache - Excluye logs rotativos
- Excluye archivos temporales
- Excluye
- Paralelismo:
# Usar más conexiones paralelas para S3 restic backup --option s3.connections=10 /home
Verificación
- Verificación regular:
# Verificar integridad del repositorio (semanalmente) restic check # Verificación completa con lectura de datos (mensualmente) restic check --read-data - 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)