Páginas

Mostrando entradas con la etiqueta backup. Mostrar todas las entradas
Mostrando entradas con la etiqueta backup. Mostrar todas las entradas

sábado, 3 de octubre de 2026

Backup Manual y Recuperación

FreeBSD 14.5

ZFS FreeBSD Copia de seguridad del servidor tormenta
ZFS FreeBSD Copia de seguridad del cliente solaris

Sistema: solaris y tormenta (FreeBSD 14.5)
Método: Backup manual a disco USB (Modelo B — USB guardado fuera)
Fecha: 2026-10-03

1. Introducción y filosofía

Este documento describe el sistema de backup manual a disco USB implementado en solaris y tormenta. Está diseñado para:

  • Ejecutarse manualmente cuando se conecta el disco USB.
  • Mantener los USB guardados fuera de las máquinas para protección ante ransomware y desastres locales.
  • Ser recuperable tanto para archivos individuales como para el sistema completo.
Filosofía del Modelo B: los USB no están conectados permanentemente. Se conectan solo cuando se va a hacer el backup, y después se guardan en otro lugar. Esto protege contra:
  • Ransomware que cifraría también el USB conectado.
  • Robo o incendio que afectaría a máquina y backup a la vez.
  • Errores humanos que borrarían el backup por accidente.

Estado del sistema

Elementosolaristormenta
Pool origenzrootzroot
Pool destino (USB)zbackupzbk
Dataset destinozbackup/recovery/solariszbk/tormenta
Etiqueta USBZFSBACKUPbackup1tb
Tamaño del pool origen~220 GB~312 GB
Script/root/bin/zfs-backup.sh/root/bin/zfs-backup.sh
Log/var/log/zfs-backup.log/var/log/zfs-backup.log
Retención origen7 snapshots7 snapshots
Retención destino30 snapshots30 snapshots

2. El script zfs-backup.sh

Script único que funciona en ambas máquinas, detectando automáticamente el hostname. Versión 2.4.

Ubicación

/root/bin/zfs-backup.sh

Contenido completo

#!/bin/sh
# zfs-backup.sh - Backup incremental de zroot a pool USB
# Versión: 2.4 (snapshot por dataset + export en error)
# Uso: sh /root/bin/zfs-backup.sh

set -e

# ========== CONFIGURACIÓN SEGÚN MÁQUINA ==========
HOSTNAME=$(hostname -s)
case "$HOSTNAME" in
    solaris)
        SRC_POOL="zroot"
        DST_POOL="zbackup"
        DST_DATASET="zbackup/recovery/solaris"
        USB_DEV="/dev/gpt/ZFSBACKUP"
        RETENTION_SRC=7
        RETENTION_DST=30
        ;;
    tormenta)
        SRC_POOL="zroot"
        DST_POOL="zbk"
        DST_DATASET="zbk/tormenta"
        USB_DEV="/dev/gpt/backup1tb"
        RETENTION_SRC=7
        RETENTION_DST=30
        ;;
    *)
        echo "Hostname no reconocido: $HOSTNAME" >&2
        exit 1
        ;;
esac

SNAP_PREFIX="snap"
SNAP_TODAY="${SNAP_PREFIX}-$(date +%Y%m%d)"
LOG="/var/log/zfs-backup.log"
LOCKFILE="/var/run/zfs-backup.lock"

# ========== VARIABLES DE ESTADO ==========
IMPORTED_BY_US=0

# ========== FUNCIONES ==========
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG"
}

die() {
    log "ERROR: $*"
    if [ "${IMPORTED_BY_US:-0}" -eq 1 ]; then
        log "Exportando pool $DST_POOL antes de salir..."
        zpool export "$DST_POOL" >> "$LOG" 2>&1 || log "Aviso: no se pudo exportar"
    fi
    rm -f "$LOCKFILE"
    exit 1
}

# ========== BLOQUEO ==========
if [ -f "$LOCKFILE" ]; then
    PID=$(cat "$LOCKFILE" 2>/dev/null)
    if kill -0 "$PID" 2>/dev/null; then
        echo "Ya hay una ejecución en curso (PID $PID)"
        exit 0
    fi
    rm -f "$LOCKFILE"
fi
echo $$ > "$LOCKFILE"

log "=========================================="
log "Iniciando backup en $HOSTNAME"
log "=========================================="

# ========== VERIFICAR POOL USB ==========
if ! zpool list "$DST_POOL" >/dev/null 2>&1; then
    log "Pool $DST_POOL no está importado. Intentando importar..."
    if [ ! -e "$USB_DEV" ]; then
        die "Disco USB $USB_DEV no encontrado. ¿Está conectado?"
    fi
    if ! zpool import "$DST_POOL" 2>>"$LOG"; then
        die "No se pudo importar el pool $DST_POOL"
    fi
    log "Pool $DST_POOL importado correctamente"
    IMPORTED_BY_US=1
else
    log "Pool $DST_POOL ya estaba importado"
    IMPORTED_BY_US=0
fi

# ========== CREAR SNAPSHOT DE HOY (dataset por dataset) ==========
log "Creando snapshot $SNAP_TODAY en todos los datasets (los que falten)..."

CREATED=0
SKIPPED=0
FAILED=0
for ds in $(zfs list -H -o name -r "$SRC_POOL" 2>/dev/null); do
    if zfs list -t snapshot "$ds@$SNAP_TODAY" >/dev/null 2>&1; then
        SKIPPED=$((SKIPPED + 1))
    else
        if zfs snapshot "$ds@$SNAP_TODAY" >> "$LOG" 2>&1; then
            CREATED=$((CREATED + 1))
        else
            log "  → error al crear $ds@$SNAP_TODAY"
            FAILED=$((FAILED + 1))
        fi
    fi
done
log "Snapshots: creados=$CREATED, ya existían=$SKIPPED, fallos=$FAILED"

if [ "$FAILED" -gt 0 ]; then
    log "AVISO: hubo $FAILED fallos al crear snapshots (se continúa)"
fi

# ========== DETECTAR ÚLTIMA SNAPSHOT EN DESTINO ==========
LAST_DST_SNAP=$(zfs list -t snapshot -H -o name -r "$DST_DATASET" 2>/dev/null \
    | grep '@snap-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$' \
    | sort -r \
    | head -1 \
    | sed 's/.*@//')

if [ -z "$LAST_DST_SNAP" ]; then
    log "No hay snapshots previas en destino. Envío COMPLETO."
    zfs send -Rwcv "$SRC_POOL@$SNAP_TODAY" \
        | zfs receive -Fsuv -x mountpoint "$DST_DATASET" >> "$LOG" 2>&1 \
        || die "Error en el envío completo"

elif [ "$LAST_DST_SNAP" = "$SNAP_TODAY" ]; then
    log "Última snapshot en destino: $LAST_DST_SNAP"
    log "La snapshot $SNAP_TODAY ya está replicada. Nada que enviar."

elif ! zfs list -t snapshot "$SRC_POOL@$LAST_DST_SNAP" >/dev/null 2>&1; then
    log "AVISO: $LAST_DST_SNAP no existe en origen. Envío COMPLETO."
    zfs send -Rwcv "$SRC_POOL@$SNAP_TODAY" \
        | zfs receive -Fsuv -x mountpoint "$DST_DATASET" >> "$LOG" 2>&1 \
        || die "Error en el envío completo"

else
    log "Enviando incremental desde $LAST_DST_SNAP hasta $SNAP_TODAY"
    zfs send -Rwcv -i "$SRC_POOL@$LAST_DST_SNAP" "$SRC_POOL@$SNAP_TODAY" \
        | zfs receive -Fsuv -x mountpoint "$DST_DATASET" >> "$LOG" 2>&1 \
        || die "Error en el envío incremental"
fi

log "Envío completado correctamente"

# ========== FUNCIÓN DE RETENCIÓN ==========
apply_retention() {
    local root="$1"
    local label="$2"
    local ret="$3"

    log "Aplicando retención de $ret snapshots en $label..."

    zfs list -t snapshot -H -o name -r "$root" 2>/dev/null \
        | grep '@snap-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$' \
        | sort -t@ -k1,1 -k2,2r \
        | awk -F@ -v r="$ret" '
            {
                ds = $1
                if (ds != prev_ds) { count = 0; prev_ds = ds }
                count++
                if (count > r) print $0
            }' \
        | while read snap; do
            log "Borrando $snap"
            zfs destroy "$snap" >> "$LOG" 2>&1 || log "  → error al borrar"
        done

    log "Retención en $label aplicada"
}

# ========== RETENCIÓN EN DESTINO (30 snapshots) ==========
apply_retention "$DST_DATASET" "destino" "$RETENTION_DST"

# ========== RETENCIÓN EN ORIGEN (7 snapshots) ==========
apply_retention "$SRC_POOL" "origen" "$RETENTION_SRC"

# ========== EXPORTAR POOL ==========
if [ "$IMPORTED_BY_US" -eq 1 ]; then
    log "Exportando pool $DST_POOL"
    zpool export "$DST_POOL" >> "$LOG" 2>&1 || log "Aviso: no se pudo exportar"
    log "Ya puedes desconectar el USB de forma segura"
fi

log "Backup completado con éxito"
log "=========================================="
rm -f "$LOCKFILE"

Características clave

ElementoDescripción
Detección automáticaUsa el hostname para elegir el pool y dataset correctos
Snapshot dataset por datasetEvita el fallo atómico de zfs snapshot -r
Detección de sincronizaciónSi ya está todo replicado, no envía nada
Incremental automáticoDetecta la última snapshot en destino y envía solo diferencias
Fallback a full sendSi no hay base común, hace envío completo automáticamente
Retención independiente7 snapshots en origen, 30 en destino
Export automáticoSi el script importó el USB, lo exporta al terminar
Logging completoTodo se registra en /var/log/zfs-backup.log
LockfileEvita ejecuciones simultáneas

3. Ejecución manual paso a paso

Cuándo ejecutar: semanalmente (recomendado domingo por la tarde), o siempre que haya cambios importantes que se quieran respaldar.

Paso 1 — Conectar el USB

Conectar el disco USB (ZFSBACKUP en solaris, backup1tb en tormenta) a la máquina correspondiente. Esperar unos segundos a que el sistema lo detecte.

ls -la /dev/gpt/ZFSBACKUP    # En solaris
ls -la /dev/gpt/backup1tb    # En tormenta

Debe aparecer el device node. Si no aparece, probar con otro puerto USB o revisar dmesg:

dmesg | tail -20

Paso 2 — Ejecutar el script

sh /root/bin/zfs-backup.sh
Importante: NO ejecutar el script si no se está seguro de que el USB está conectado. Si no lo está, el script fallará limpiamente sin tocar nada.

Paso 3 — Verificar el resultado

tail -30 /var/log/zfs-backup.log

Debe verse al final:

[XX:XX:XX] Backup completado con éxito
[XX:XX:XX] ==========================================

Paso 4 — Desconectar el USB

Si el script importó el pool, lo habrá exportado automáticamente. Se verá en el log:

[XX:XX:XX] Ya se puede desconectar el USB de forma segura

Verificar que el pool está exportado:

zpool list

El pool de backup (zbackup o zbk) no debe aparecer en la lista. Si aparece, exportarlo manualmente antes de desconectar:

zpool export zbackup    # En solaris
zpool export zbk        # En tormenta

sync

Paso 5 — Guardar el USB en lugar seguro

Una vez exportado y sincronizado, se puede retirar el cable USB y guardarlo donde corresponda (fuera de casa, caja fuerte, casa de un familiar, etc.).

¡Backup completado! El proceso completo tarda típicamente entre 1 y 15 minutos dependiendo de cuántos datos hayan cambiado desde el último backup.

4. Interpretación del log

Ejemplo de ejecución exitosa (con cambios)

[2026-10-03 18:30:00] ==========================================
[2026-10-03 18:30:00] Iniciando backup en solaris
[2026-10-03 18:30:00] ==========================================
[2026-10-03 18:30:00] Pool zbackup no está importado. Intentando importar...
[2026-10-03 18:30:02] Pool zbackup importado correctamente
[2026-10-03 18:30:02] Creando snapshot snap-20261003 en todos los datasets (los que falten)...
[2026-10-03 18:30:02] Snapshots: creados=18, ya existían=0, fallos=0
[2026-10-03 18:30:03] Última snapshot en destino: snap-20261002
[2026-10-03 18:30:03] Enviando incremental desde snap-20261002 hasta snap-20261003
[2026-10-03 18:32:15] Envío completado correctamente
[2026-10-03 18:32:15] Aplicando retención de 30 snapshots en destino...
[2026-10-03 18:32:15] Retención en destino aplicada
[2026-10-03 18:32:15] Aplicando retención de 7 snapshots en origen...
[2026-10-03 18:32:15] Retención en origen aplicada
[2026-10-03 18:32:15] Exportando pool zbackup
[2026-10-03 18:32:16] Ya puedes desconectar el USB de forma segura
[2026-10-03 18:32:16] Backup completado con éxito
[2026-10-03 18:32:16] ==========================================

Ejemplo de ejecución sin cambios

[2026-10-03 18:30:00] ==========================================
[2026-10-03 18:30:00] Iniciando backup en solaris
[2026-10-03 18:30:00] ==========================================
[2026-10-03 18:30:00] Pool zbackup ya estaba importado
[2026-10-03 18:30:00] Creando snapshot snap-20261003 en todos los datasets (los que falten)...
[2026-10-03 18:30:00] Snapshots: creados=18, ya existían=0, fallos=0
[2026-10-03 18:30:00] Última snapshot en destino: snap-20261003
[2026-10-03 18:30:00] La snapshot snap-20261003 ya está replicada. Nada que enviar.
[2026-10-03 18:30:00] Envío completado correctamente
[2026-10-03 18:30:00] Aplicando retención de 30 snapshots en destino...
[2026-10-03 18:30:00] Retención en destino aplicada
[2026-10-03 18:30:00] Aplicando retención de 7 snapshots en origen...
[2026-10-03 18:30:00] Retención en origen aplicada
[2026-10-03 18:30:00] Backup completado con éxito
[2026-10-03 18:30:00] ==========================================

Ejemplo de error (USB no conectado)

[2026-10-03 18:30:00] ==========================================
[2026-10-03 18:30:00] Iniciando backup en solaris
[2026-10-03 18:30:00] ==========================================
[2026-10-03 18:30:00] Pool zbackup no está importado. Intentando importar...
[2026-10-03 18:30:00] ERROR: Disco USB /dev/gpt/ZFSBACKUP no encontrado. ¿Está conectado?
Este error es normal si se ejecuta el script sin el USB conectado. No corrompe nada. Simplemente conectar el USB y volver a ejecutar.

5. Política de retención

El script aplica automáticamente dos políticas distintas:

UbicaciónSnapshots conservadosRazón
Origen (zroot)7Liberar espacio en el NVMe. Solo se necesitan los últimos días para incrementales.
Destino (USB)30Histórico más largo. El USB tiene más espacio y es el "archivo".

Ajustar la retención

Si se quieren cambiar los valores, editar el script:

ee /root/bin/zfs-backup.sh

Buscar las líneas:

RETENTION_SRC=7
RETENTION_DST=30

Y modifícalas a los valores se preferencia. Por ejemplo, para guardar más histórico en el USB:

RETENTION_SRC=7
RETENTION_DST=60
Espacio: cada snapshot diaria ocupa entre 50 MB y varios GB según lo que cambie. Con 30 snapshots en destino, asegurarse de que el USB tiene espacio suficiente (consultar con zpool list).

6. Recuperación de archivos individuales

Escenario: se ha borrado un archivo por error o se quiere recuperar una versión anterior de algo.

Paso 1 — Conectar el USB y localizar el snapshot

# Conectar el USB de backup
zpool import zbackup        # En solaris
zpool import zbk            # En tormenta

# Ver snapshots disponibles
zfs list -t snapshot -r zbackup/recovery/solaris | grep 'snap-2026'

Paso 2 — Montar el snapshot en un punto temporal

mkdir -p /mnt/snap
mount -t zfs zbackup/recovery/solaris/home/carlos@snap-20261002 /mnt/snap

Ahora /mnt/snap contiene el contenido de /home/carlos tal como estaba en esa fecha.

Paso 3 — Buscar y copiar el archivo

# Ver qué hay
ls -la /mnt/snap/

# Copiar el archivo deseado
cp /mnt/snap/documento.txt /home/carlos/documento_recuperado.txt

Paso 4 — Desmontar y exportar

umount /mnt/snap
rmdir /mnt/snap

zpool export zbackup        # En solaris
zpool export zbk            # En tormenta

sync
Ventaja sobre el método .gz: con el backup ZFS solo montas el snapshot y accedes a los archivos directamente. No hay que descomprimir 300 GB para sacar un documento.

7. Recuperación completa del sistema

Escenario: el NVMe del sistema ha fallado o necesitas reinstalar FreeBSD desde cero.

ATENCIÓN: Este procedimiento borra todo el contenido del disco actual. Asegurarse de tener el backup antes de empezar.

Paso 1 — Arrancar desde USB de FreeBSD

Insertar el USB de instalación, arrancar la máquina desde él, y elegir "Shell" en lugar de instalar.

Paso 2 — Identificar discos

geom disk list
camcontrol devlist

Paso 3 — Preparar el disco destino

gpart destroy -F nda0
gpart create -s gpt nda0
gpart add -t efi -a 1m -s 260m -l EFI nda0
gpart add -t freebsd-boot -a 1m -s 512k -l BOOT nda0
gpart add -t freebsd-swap -a 1m -s 16g -l SWAP nda0
gpart add -t freebsd-zfs -a 1m -l ZFS nda0
newfs_msdos -F 32 -c 1 /dev/nda0p1

Paso 4 — Crear el pool zroot

zpool create -o ashift=12 \
             -O compression=lz4 \
             -O atime=off \
             -O mountpoint=none \
             -R /mnt \
             zroot /dev/gpt/ZFS

Paso 5 — Importar el USB de backup

zpool import zbackup        # En solaris
zpool import zbk            # En tormenta

Paso 6 — Restaurar el sistema

zfs send -Rwcv zbackup/recovery/solaris@snap-20261003 \
    | zfs receive -Fduv zroot

Paso 7 — Configurar el bootfs

zpool set bootfs=zroot/ROOT/default zroot
zpool get bootfs zroot

Paso 8 — Copiar el cargador UEFI

mkdir -p /mnt2
mount_msdosfs /dev/nda0p1 /mnt2
mkdir -p /mnt2/efi/boot
cp /mnt/zroot/ROOT/default/boot/loader.efi /mnt2/efi/boot/bootx64.efi
umount /mnt2
rmdir /mnt2

Paso 9 — Ajustar propiedades de montaje

zfs set mountpoint=/ zroot/ROOT/default
zfs set mountpoint=/home zroot/home
zfs set mountpoint=/usr zroot/usr
zfs set mountpoint=/var zroot/var
zfs set mountpoint=/tmp zroot/tmp

zpool set altroot= zroot
zpool set cachefile=/boot/zfs/zpool.cache zroot

Paso 10 — Exportar y reiniciar

zpool export zbackup
zpool export zbk

reboot

Paso 11 — Verificar

freebsd-version
zpool status zroot
zfs list -r zroot | head -20

8. Recuperación del dataset cifrado

El dataset zroot/nfsv4/secifrado en tormenta está cifrado con ZFS native encryption.

Guardar la clave en al menos 3 sitios distintos:
  • Gestor de contraseñas (Bitwarden, KeePass, etc.).
  • Papel en caja fuerte.
  • USB de emergencia separado del USB de backup.

Ver el contenido de la clave

base64 /etc/zfs/secifrado.key

Restaurar la clave

echo 'TU_CLAVE_EN_BASE64' | base64 -d > /etc/zfs/secifrado.key
chmod 400 /etc/zfs/secifrado.key
chown root:wheel /etc/zfs/secifrado.key
ls -la /etc/zfs/secifrado.key

Montar el dataset cifrado

zfs mount zroot/nfsv4/secifrado
ls -la /nfsv4/secifrado/
Recomendación: una vez al año, haz una prueba real de recuperación del dataset cifrado en una máquina de prueba.

9. Checklist y rutinas

Rutina semanal (domingo)

  • [ ] Conectar USB ZFSBACKUP a solaris
  • [ ] Ejecutar sh /root/bin/zfs-backup.sh
  • [ ] Verificar log: tail -20 /var/log/zfs-backup.log
  • [ ] Confirmar "Backup completado con éxito"
  • [ ] Desconectar USB y guardarlo en lugar seguro
  • [ ] Repetir con USB backup1tb en tormenta

Rutina mensual

  • [ ] Verificar snapshots recientes en los USB
  • [ ] Comprobar espacio disponible: zpool list
  • [ ] Revisar el log en busca de errores: grep "ERROR" /var/log/zfs-backup.log

Rutina semestral

  • [ ] Prueba de restauración de un archivo aleatorio.
  • [ ] Verificar integridad del pool USB: zpool scrub zbackup
  • [ ] Comprobar que las claves de cifrado siguen accesibles.

Apéndice A — Comandos útiles

Ver estado de los pools

zpool list
zpool status
zpool status -v

Ver snapshots

# Todos los snapshots del pool
zfs list -t snapshot -r zroot

# Solo los snapshots diarios (formato snap-YYYYMMDD)
zfs list -t snapshot -r zroot | grep 'snap-2026'

# Contar snapshots por dataset
zfs list -t snapshot -r zroot | awk -F@ '{print $1}' | sort | uniq -c

Comprobar integridad

zpool scrub zroot
zpool scrub zbackup
zpool status

Ver procesos de backup en curso

ps aux | grep -E "zfs (send|receive)" | grep -v grep
tmux ls

Reconectar a una sesión tmux

tmux attach -t backup_usb

Apéndice B — Solución de problemas

Error: "Pool zbackup no está importado"

ls -la /dev/gpt/ZFSBACKUP
zpool import zbackup
sh /root/bin/zfs-backup.sh

Error: "cannot create snapshot ... dataset already exists"

Causa: alguno de los datasets ya tiene la snapshot de hoy. La versión 2.4 del script ya maneja esto dataset por dataset.

Error: "cannot send ... encrypted dataset"

Causa: falta el flag -w en zfs send.

Solución: usar -Rwcv en lugar de -Rcv.

El USB no se puede exportar (pool is busy)

lsof | grep zbackup
fuser -m /zbackup

zfs unmount -a
zpool export -f zbackup

Backup incompleto o corrupto

zpool scrub zbackup
zpool status zbackup

Apéndice C — Glosario

TérminoSignificado
PoolConjunto de discos gestionados por ZFS como una unidad
DatasetSistema de archivos dentro de un pool
SnapshotImagen inmutable de un dataset en un momento concreto
Full sendEnvío completo de un dataset a otro pool
Incremental sendEnvío solo de los cambios desde una snapshot base
RetenciónNúmero de snapshots que se conservan antes de borrar las más antiguas
ashiftTamaño de sector usado por ZFS (12 = 4096 bytes)
-w (raw)Envío de bloques cifrados sin descifrar
BEBoot Environment — entorno de arranque de FreeBSD
tmux

Multiplexor de terminal (permite sesiones persistentes)

 

FreeBSD es genial!.

Realizar copia de seguridad del cliente solaris en disco USB

ZFS FreeBSD Copia de seguridad del servidor tormenta
Backup Manual y Recuperación

FreeBSD14.5

1. Identificar discos

geom disk list
camcontrol devlist
gpart show

En solaris, el disco USB suele aparecer como da0 o da1. Verificar con geom disk list cuál es el correcto (etiqueta ZFSBACKUP).

2. Comprobaciones previas (antes de destruir nada)

¿Tiene particiones?

gpart show da0

¿Tiene restos de ZFS?

zpool import | grep -B2 -A5 da0

¿Está montado en algún sitio?

mount | grep da0

3. Plan resumido

ComandoQué hace
gpart destroy -F da0Borra particiones
zpool labelclear -f /dev/da0Borra restos ZFS
gpart create -s gpt da0Tabla GPT nueva
gpart add -t freebsd-zfs -l ZFSBACKUP da0Partición ZFS
zpool create -o ashift=12 ... zbackup /dev/gpt/ZFSBACKUPPool zbackup
zfs create -o mountpoint=none zbackup/recovery/solarisDataset destino
zfs snapshot -r zroot@snap-$(date +%Y%m%d)Snapshot del sistema
zfs send -Rwcv ... | zfs receive -Fsuv ...Envío local (en tmux)
zpool export zbackupCerrar limpiamente

4. Preparar el disco

gpart destroy -F da0
# → da0 destroyed

Borrar cualquier resto de ZFS (aunque no haya, por asegurar)

zpool labelclear -f /dev/da0 2>/dev/null

Crear tabla GPT nueva

gpart create -s gpt da0
# → da0 created

Crear partición ZFS ocupando todo el disco

gpart add -t freebsd-zfs -l ZFSBACKUP da0
# → da0p1 added

Verificar

gpart show da0
# →       40  3907029088  da0  GPT  (2T)
# →       40  3907029088    1  freebsd-zfs  (2T)

5. Crear el pool zbackup

Los discos modernos suelen tener sectores de 4K, pero muchos discos USB reportan 512B. Es crítico crear el pool con ashift=12 (4096 bytes) para evitar problemas de rendimiento.

Ver ashift que ZFS detectaría por defecto

diskinfo -v da0 | grep -i "sector size"

Si dice 512, hay que forzar ashift=12 al crear el pool.

zpool create -o ashift=12 \
             -O compression=lz4 \
             -O atime=off \
             -O xattr=sa \
             -O relatime=on \
             -O mountpoint=none \
             zbackup /dev/gpt/ZFSBACKUP

Banderas

  • ashift=12 → sectores 4K (crítico para rendimiento en discos modernos).
  • compression=lz4 → compresión ligera y rápida.
  • atime=off → no actualizar tiempo de acceso (más rápido).
  • xattr=sa → atributos extendidos en el inodo.
  • relatime=on → atime solo cuando sea útil.
  • mountpoint=none → no montar automáticamente.

Verificar

zpool status zbackup
zpool list zbackup
zfs list

Crear dataset destino

zfs create -o mountpoint=none zbackup/recovery
zfs create -o mountpoint=none zbackup/recovery/solaris
zfs list -r zbackup

Salida esperada:

zbackup                     XXXG   XXXG    96K  none
zbackup/recovery            XXXG   XXXG    96K  none
zbackup/recovery/solaris    XXXG   XXXG    96K  none

Pool zbackup creado correctamente:

DatoValor
Poolzbackup
Tamaño~2 TB
Dataset destinozbackup/recovery/solaris (listo)
zroot a respaldar~220 G

6. Crear snapshot del sistema

zfs snapshot -r zroot@snap-$(date +%Y%m%d)

Verificar

zfs list -t snapshot -r zroot | grep snap-$(date +%Y%m%d) | wc -l

El resultado depende del número de datasets del sistema. En solaris actual: ~30.

Nota para el futuro: si un script creó el snapshot en un solo dataset sin usar -r, o si otro proceso lo creó antes para otro propósito, puede haber conflictos. Cuando se monte el cron para backups automáticos, usar date +%Y%m%d-%H%M%S para evitar colisiones si se ejecuta dos veces el mismo día.
zfs snapshot -r zroot@snap-$(date +%Y%m%d-%H%M%S)

7. Lanzar el envío en tmux

Entrar en tmux

tmux new -s backup_usb

Comando de envío (dentro de tmux)

zfs send -Rwcv zroot@snap-$(date +%Y%m%d) \
  | zfs receive -Fsuv -x mountpoint zbackup/recovery/solaris

Banderas explicadas

FlagSignificado
-RRecursivo: envía zroot y todos sus hijos
-wRaw: envía los bloques cifrados tal cual (necesario si hay datasets cifrados)
-cCompresión nativa ZFS (lz4)
-vVerbose: muestra progreso por dataset
-FFuerza rollback en destino si hay divergencias
-sGuarda token de resume en /var/tmp/ (permite reanudar si se corta)
-uNo monta los datasets recibidos
-x mountpointNo replica los mountpoints del origen
Importante sobre -w: aunque solaris no tiene datasets cifrados actualmente, el -w es inofensivo si no hay cifrado. Mantener el mismo script/comando que en tormenta para consistencia y por si en el futuro se añade cifrado.

8. Salir de tmux sin matar el proceso

Ctrl+b  d

9. Monitorizar (en otra terminal)

Ver datasets llegando en tiempo real

watch -n 10 'zfs list -r zbackup/recovery/solaris | head -30'

Ver velocidad de escritura al USB

zpool iostat zbackup 5

Ver espacio consumido

watch -n 30 'zfs list zbackup'

10. Verificar tras el envío

Volver a la sesión tmux

tmux attach -t backup_usb

Si ya terminó, se verá el real Xm Ys del comando. Entonces:

Contar snapshots origen vs destino

zfs list -t snapshot -r zroot | wc -l
zfs list -t snapshot -r zbackup/recovery/solaris | wc -l

Deben coincidir.

Verificar datasets replicados

zfs list -r zbackup/recovery/solaris | head -30

Espacio usado

zfs list zbackup
zpool list zbackup

11. Si algo falla durante el envío

¿Sigue algún proceso zfs vivo?

ps aux | grep -E "zfs (send|receive)" | grep -v grep

Si aparece algo, sigue corriendo. Esperar y dejarlo terminar.

¿En qué estado está el destino?

zfs list zbackup
zfs list -r zbackup/recovery/solaris | head -20

Se verá si quedó un dataset a medias (zbackup/recovery/solaris con datos parciales).

¿Hay un token de resume guardado?

ls -la /var/tmp/ | grep -i zfs
ls -la / | grep -i zfs
El token de resume solo existe si se usó -s en el zfs receive. Si no se usó, no hay reanudación posible: hay que empezar de cero.

¿El proceso murió? Limpiar el destino parcial

zfs destroy -r zbackup/recovery/solaris
zfs create -o mountpoint=none zbackup/recovery/solaris
zfs list -r zbackup/recovery

Relanzar con -s para permitir resume

tmux new -s backup_usb

Dentro de tmux:

zfs send -Rwcv zroot@snap-20261002 \
  | zfs receive -Fsuv -x mountpoint zbackup/recovery/solaris
El flag -s en zfs receive guarda un token de reanudación en /var/tmp/. Si se vuelve a cortar, se podrá reanudar con:
zfs send -t $(ls /var/tmp/zfs_*.resume 2>/dev/null | head -1)

Salir de tmux con Ctrl+b d antes de cerrar SSH. No cerrar la sesión SSH hasta que tmux esté desconectado.

12. Exportar el pool y desconectar el USB

Verificar que no hay nada escribiendo

zpool status zbackup
lsof | grep zbackup 2>/dev/null

Exportar limpiamente

zpool export zbackup

Y por último

sync

Ya se puede retirar el cable USB.

13. Tiempo estimado

Velocidad USB~220 GB
80 MB/s~45 min
120 MB/s~30 min
200 MB/s~18 min
400 MB/s~9 min

Con el chip ASMT USB 3.0 + disco SATA: ~15-30 min si es SSD, ~40-60 min si es HDD.

Recordatorio: para poder cerrar la sesión SSH sin que se corte el envío, primero entrar en tmux con tmux new -s backup_usb, lanzar el envío, y salir con Ctrl+b d.
Recomendación adicional: poner una etiqueta al disco físico con rotulador o adhesivo:
  • Backup Solaris
  • snap-20261002
  • zbackup pool

14. Restaurar archivos de configuración

No requiere reiniciar. Solo copia los archivos desde el snapshot.

Paso 1: Montar el snapshot

mkdir -p /mnt/snap
mount -t zfs zroot/ROOT/default@snap-14.4-RELEASE-p8-casa.lan-2026-28-sept-2026 /mnt/snap

Paso 2: Comparar qué cambió

diff /etc/pf.conf       /mnt/snap/etc/pf.conf
diff /etc/rc.conf       /mnt/snap/etc/rc.conf
diff /etc/sysctl.conf   /mnt/snap/etc/sysctl.conf
diff /boot/loader.conf  /mnt/snap/boot/loader.conf

Paso 3: Backup de los actuales

cp /etc/pf.conf       /etc/pf.conf.broken-$(date +%Y%m%d)
cp /etc/rc.conf       /etc/rc.conf.broken-$(date +%Y%m%d)
cp /etc/sysctl.conf   /etc/sysctl.conf.broken-$(date +%Y%m%d)
cp /boot/loader.conf  /boot/loader.conf.broken-$(date +%Y%m%d)

Paso 4: Restaurar desde el snapshot

cp /mnt/snap/etc/pf.conf       /etc/pf.conf
cp /mnt/snap/etc/rc.conf       /etc/rc.conf
cp /mnt/snap/etc/sysctl.conf   /etc/sysctl.conf
cp /mnt/snap/boot/loader.conf  /boot/loader.conf

Paso 5: Desmontar y probar

umount /mnt/snap
rmdir /mnt/snap

# Recargar pf (sin reiniciar)
pfctl -f /etc/pf.conf

# Ver rutas
netstat -rn

# Probar red
ping -c 3 192.168.88.1
ping -c 3 8.8.8.8
ping -c 3 google.com

Paso 6: Si funciona, reiniciar para aplicar loader.conf y sysctl.conf

shutdown -r now

15. Rollback completo

Requiere arrancar en modo monousuario. No se puede hacer con el dataset montado.

Paso 1: Crear snapshot de seguridad del estado actual

Antes de tocar nada, para poder volver si algo falla:

zfs snapshot -r zroot@before-rollback-$(date +%Y%m%d-%H%M)

Paso 2: Reiniciar en modo monousuario

shutdown -r now

En el menú de arranque de FreeBSD, pulsar 2 (Boot Single User).

Paso 3: Preparar el sistema

zfs set readonly=off zroot
zfs mount -a

Paso 4: Rollback

Esto destruye los snapshots posteriores a este (incluido el de before-rollback).
zfs rollback -R zroot/ROOT/default@snap-14.4-RELEASE-p8-casa.lan-2026-28-sept-2026

Paso 5: Salir del modo monousuario y reiniciar

exit

O directamente:

reboot

Qué se pierde con el rollback

ElementoEstado
pf.confVuelve al estado del snapshot
loader.confVuelve al estado del snapshot
sysctl.confVuelve al estado del snapshot
/etc/rc.confEstado del snapshot
Archivos en /home/carlosNO se pierden (dataset separado)
Archivos en /varNO se pierden (dataset separado, si no se hace rollback ahí)
Solo afecta a zroot/ROOT/default (el sistema base). Los datos personales en zroot/home/carlos no se tocan.

16. Uso del script automatizado

Solaris tiene instalado el script /root/bin/zfs-backup.sh que automatiza todo el proceso.

Ejecución manual (modelo B: USB guardado fuera)

# 1. Conectar el USB ZFSBACKUP a solaris
# 2. Ejecutar:
sh /root/bin/zfs-backup.sh

# 3. Ver el resultado
tail -20 /var/log/zfs-backup.log

# 4. Cuando termine, el script exporta el pool automáticamente
# 5. Desconectar el USB y guardarlo en lugar seguro

Qué hace el script automáticamente

  • Detecta que está en solaris y usa zbackup/zbackup/recovery/solaris/ZFSBACKUP.
  • Importa el pool zbackup si no está importado.
  • Crea el snapshot zroot@snap-YYYYMMDD (dataset por dataset, sin fallos atómicos).
  • Detecta la última snapshot en destino y envía solo el incremental.
  • Aplica retención: 7 snapshots en origen, 30 en destino.
  • Exporta el pool al terminar (para poder desconectar el USB).
  • Registra todo en /var/log/zfs-backup.log.

Frecuencia recomendada

Semanal (domingo por la tarde). Total: 5-15 minutos por backup.

17. Errores comunes a evitar

  • Usar zfs send -Rcv sin -w → falla si hay datasets cifrados.
  • Olvidar -s en zfs receive → no hay reanudación posible si se corta.
  • Cerrar la sesión SSH sin tmux → el envío muere al desconectar.
  • No guardar la clave de cifrado fuera del backup → backup inútil si se pierde.
  • Usar zfs destroy sin -n primero → riesgo de borrar snapshots con dependencias.
  • Ejecutar el script de retención sin verificar → puede borrar snapshots indiscriminadamente.
  • Importar el mismo pool en dos máquinas simultáneamente → corrompe metadatos.
  • Desconectar el USB sin zpool export → riesgo de corrupción.

18. Checklist final

  • [ ] Disco USB identificado y preparado
  • [ ] Pool zbackup creado con ashift=12
  • [ ] Dataset zbackup/recovery/solaris creado
  • [ ] Snapshot del sistema creado (zfs snapshot -r)
  • [ ] Envío lanzado dentro de tmux
  • [ ] Proceso monitorizado hasta el final
  • [ ] Verificado: snapshots origen vs destino coinciden
  • [ ] Pool exportado limpiamente (zpool export zbackup)
  • [ ] USB desconectado y guardado en lugar seguro
  • [ ] Clave de cifrado guardada en al menos 2 sitios distintos (si aplica)

19. Dónde guardar el USB

UbicaciónProtección
❌ Junto a la máquinaNinguna (robo/incendio afecta a ambos)
⚠️ Otra habitación de la casaProtección baja
✅ Otra planta / garaje / trasteroProtección media
✅✅ Casa de un familiar / amigoProtección alta
✅✅✅ Caja de seguridad del bancoProtección máxima

Recomendación: caja fuerte pequeña en casa + copia secundaria en casa de un familiar.

20. Verificaciones periódicas (cada 3-6 meses)

# 1. Conectar el USB ZFSBACKUP a solaris
# 2. Importar el pool
zpool import zbackup

# 3. Verificar el pool
zpool status zbackup

# 4. Contar snapshots en destino
zfs list -t snapshot -r zbackup/recovery/solaris | grep 'snap-2026' | wc -l

# 5. Ver las más recientes
zfs list -t snapshot -r zbackup/recovery/solaris | grep 'snap-2026' | sort | tail -5

# 6. Exportar
zpool export zbackup

Si todo está en orden, los datos son recuperables.

FreeBSD es genial!.

viernes, 2 de octubre de 2026

Realizar copia de seguridad del servidor tormenta en disco USB

ZFS FreeBSD Copia de seguridad del cliente solaris
Backup Manual y Recuperación

FreeBSD 14.5

1. Identificar discos

geom disk list
camcontrol devlist
gpart show

2. Comprobaciones previas (antes de destruir nada)

¿Tiene particiones?

gpart show da1

¿Tiene restos de ZFS?

zpool import | grep -B2 -A5 da1

¿Está montado en algún sitio?

mount | grep da1

3. Plan resumido

ComandoQué hace
gpart destroy -F da1Borra particiones
zpool labelclear -f /dev/da1Borra restos ZFS
gpart create -s gpt da1Tabla GPT nueva
gpart add -t freebsd-zfs -l backup1tb da1Partición ZFS
zpool create -o ashift=12 ... zbk /dev/gpt/backup1tbPool zbk
zfs create -o mountpoint=none zbk/tormentaDataset destino
zfs snapshot -r zroot@snap-$(date +%Y%m%d)Snapshot del sistema
zfs send -Rwcv ... | zfs receive -Fsuv ...Envío local (en tmux)
zpool export zbkCerrar limpiamente

4. Preparar el disco

gpart destroy -F da1
# → da1 destroyed

Borrar cualquier resto de ZFS (aunque no haya, por asegurar)

zpool labelclear -f /dev/da1 2>/dev/null

Crear tabla GPT nueva

gpart create -s gpt da1
# → da1 created

Crear partición ZFS ocupando todo el disco

gpart add -t freebsd-zfs -l backup1tb da1
# → da1p1 added

Verificar

gpart show da1
# →       40  1953525088  da1  GPT  (932G)
# →       40  1953525088    1  freebsd-zfs  (932G)

5. Crear el pool zbk

Los discos modernos suelen tener sectores de 4K, pero muchos discos USB reportan 512B. Es crítico crear el pool con ashift=12 (4096 bytes) para evitar problemas de rendimiento.

Ver ashift que ZFS detectaría por defecto

diskinfo -v da1 | grep -i "sector size"

Si dice 512, hay que forzar ashift=12 al crear el pool.

zpool create -o ashift=12 \
             -O compression=lz4 \
             -O atime=off \
             -O xattr=sa \
             -O relatime=on \
             -O mountpoint=none \
             zbk /dev/gpt/backup1tb

Banderas

  • ashift=12 → sectores 4K (crítico para rendimiento en discos modernos).
  • compression=lz4 → compresión ligera y rápida.
  • atime=off → no actualizar tiempo de acceso (más rápido).
  • xattr=sa → atributos extendidos en el inodo.
  • relatime=on → atime solo cuando sea útil.
  • mountpoint=none → no montar automáticamente.

Verificar

zpool status zbk
zpool list zbk
zfs list

Crear dataset destino

zfs create -o mountpoint=none zbk/tormenta
zfs list -r zbk

Salida esperada:

zbk           XXXG   XXXG    96K  none
zbk/tormenta   96K   XXXG    96K  none

Pool zbk creado correctamente:

DatoValor
Poolzbk
Tamaño928 G
Disponible899 G
Dataset destinozbk/tormenta (listo)
zroot a respaldar312 G

6. Crear snapshot del sistema

zfs snapshot -r zroot@snap-$(date +%Y%m%d)

Verificar

zfs list -t snapshot -r zroot | grep snap-$(date +%Y%m%d) | wc -l

El resultado depende del número de datasets del sistema. En tormenta actual: ~48.

Nota para el futuro: si un script creó el snapshot en un solo dataset sin usar -r, o si otro proceso lo creó antes para otro propósito, puede haber conflictos. Cuando se monte el cron para backups automáticos, usar date +%Y%m%d-%H%M%S para evitar colisiones si se ejecuta dos veces el mismo día.
zfs snapshot -r zroot@snap-$(date +%Y%m%d-%H%M%S)

7. Lanzar el envío en tmux

Entrar en tmux

tmux new -s backup_usb

Comando de envío (dentro de tmux)

zfs send -Rwcv zroot@snap-$(date +%Y%m%d) \
  | zfs receive -Fsuv -x mountpoint zbk/tormenta

Banderas explicadas

FlagSignificado
-RRecursivo: envía zroot y todos sus hijos
-wRaw: envía los bloques cifrados tal cual (necesario si hay datasets cifrados como zroot/nfsv4/secifrado)
-cCompresión nativa ZFS (lz4)
-vVerbose: muestra progreso por dataset
-FFuerza rollback en destino si hay divergencias
-sGuarda token de resume en /var/tmp/ (permite reanudar si se corta)
-uNo monta los datasets recibidos
-x mountpointNo replica los mountpoints del origen
Importante sobre -w: el dataset zroot/nfsv4/secifrado está cifrado con ZFS native encryption. Con zfs send -Rcv (sin -w) el envío falla. Siempre usar -Rwcv.

8. Cifrado: consideraciones

El flag -w envía el dataset cifrado tal cual (raw). Ventaja: funciona, conserva el cifrado, es rápido. Inconveniente: en destino el dataset también estará cifrado y necesitarás la clave para montarlo.

¿Qué se necesita para recuperarlo?

La passphrase o keyfile utilizada para cifrar zroot/nfsv4/secifrado. Sin ella, el backup es inútil.

GUARDAR LA PASSPHRASE/KEYFILE
Si se pierde la clave, se pierden los datos y el backup. Guardarla en:
  • Un gestor de contraseñas (Bitwarden, KeePass, etc.)
  • Un archivo en un USB que no sea el mismo del backup
  • Papel impreso en un lugar seguro

Comprobar qué método de cifrado se utilizó

zfs get encryption,keyformat,keylocation zroot/nfsv4/secifrado

# NAME                       PROPERTY     VALUE                        SOURCE
# zroot/nfsv4/secifrado      encryption   aes-256-gcm                  -
# zroot/nfsv4/secifrado      keyformat    raw                          -
# zroot/nfsv4/secifrado      keylocation  file:///etc/zfs/secifrado.key

Si dice keyformat=passphrase, se necesita esa passphrase.
Si dice keyformat=raw con keylocation=file:///..., es necesario ese archivo.

Ver el contenido (para copiarlo en un gestor de contraseñas)

Convertir a base64 para poder copiarlo como texto:

base64 /etc/zfs/secifrado.key

9. Salir de tmux sin matar el proceso

Ctrl+b  d

10. Monitorizar (en otra terminal)

Ver datasets llegando en tiempo real

watch -n 10 'zfs list -r zbk/tormenta | head -30'

Ver velocidad de escritura al USB

zpool iostat zbk 5

Ver espacio consumido

watch -n 30 'zfs list zbk'

11. Verificar tras el envío

Volver a la sesión tmux

tmux attach -t backup_usb

Si ya terminó, se verá el real Xm Ys del comando. Entonces:

Contar snapshots origen vs destino

zfs list -t snapshot -r zroot | wc -l
zfs list -t snapshot -r zbk/tormenta | wc -l

Deben coincidir (probablemente ~300+).

Verificar datasets replicados

zfs list -r zbk/tormenta | head -30

Espacio usado

zfs list zbk
zpool list zbk

12. Si algo falla durante el envío

¿Sigue algún proceso zfs vivo?

ps aux | grep -E "zfs (send|receive)" | grep -v grep

Si aparece algo, sigue corriendo. Esperar y dejarlo terminar.

¿En qué estado está el destino?

zfs list zbk
zfs list -r zbk/tormenta | head -20

Se verá si quedó un dataset a medias (zbk/tormenta con datos parciales).

¿Hay un token de resume guardado?

ls -la /var/tmp/ | grep -i zfs
ls -la / | grep -i zfs
El token de resume solo existe si se usó -s en el zfs receive. Si no se usó, no hay reanudación posible: hay que empezar de cero.

¿El proceso murió? Limpiar el destino parcial

zfs destroy -r zbk/tormenta
zfs create -o mountpoint=none zbk/tormenta
zfs list -r zbk

Relanzar con -s para permitir resume

tmux new -s backup_usb

Dentro de tmux:

zfs send -Rwcv zroot@snap-20260930 \
  | zfs receive -Fsuv -x mountpoint zbk/tormenta
El flag -s en zfs receive guarda un token de reanudación en /var/tmp/. Si se vuelve a cortar, se podrá reanudar con:
zfs send -t $(ls /var/tmp/zfs_*.resume 2>/dev/null | head -1)

Salir de tmux con Ctrl+b d antes de cerrar SSH. No cerrar la sesión SSH hasta que tmux esté desconectado.

13. Exportar el pool y desconectar el USB

Verificar que no hay nada escribiendo

zpool status zbk
lsof | grep zbk 2>/dev/null

Exportar limpiamente

zpool export zbk

Y por último

sync

Ya se puede retirar el cable USB.

14. Tiempo estimado

Velocidad USB312 GB
80 MB/s~1.1 h
120 MB/s~43 min
200 MB/s~26 min
400 MB/s~13 min

Con el chip ASMT USB 3.0 + disco SATA: ~20–45 min si es SSD, ~50–80 min si es HDD.

Recordatorio: para poder cerrar la sesión SSH sin que se corte el envío, primero entrar en tmux con tmux new -s backup_usb, lanzar el envío, y salir con Ctrl+b d.
Recomendación adicional: poner una etiqueta al disco físico con rotulador o adhesivo:
  • Backup Tormenta
  • snap-20260930
  • zbk pool

15. Restaurar archivos de configuración

No requiere reiniciar. Solo copia los archivos desde el snapshot.

Paso 1: Montar el snapshot

mkdir -p /mnt/snap
mount -t zfs zroot/ROOT/default@snap-14.4-RELEASE-p8-casa.lan-2026-28-sept-2026 /mnt/snap

Paso 2: Comparar qué cambió

diff /etc/pf.conf       /mnt/snap/etc/pf.conf
diff /etc/rc.conf       /mnt/snap/etc/rc.conf
diff /etc/sysctl.conf   /mnt/snap/etc/sysctl.conf
diff /boot/loader.conf  /mnt/snap/boot/loader.conf

Paso 3: Backup de los actuales

cp /etc/pf.conf       /etc/pf.conf.broken-$(date +%Y%m%d)
cp /etc/rc.conf       /etc/rc.conf.broken-$(date +%Y%m%d)
cp /etc/sysctl.conf   /etc/sysctl.conf.broken-$(date +%Y%m%d)
cp /boot/loader.conf  /boot/loader.conf.broken-$(date +%Y%m%d)

Paso 4: Restaurar desde el snapshot

cp /mnt/snap/etc/pf.conf       /etc/pf.conf
cp /mnt/snap/etc/rc.conf       /etc/rc.conf
cp /mnt/snap/etc/sysctl.conf   /etc/sysctl.conf
cp /mnt/snap/boot/loader.conf  /boot/loader.conf

Paso 5: Desmontar y probar

umount /mnt/snap
rmdir /mnt/snap

# Recargar pf (sin reiniciar)
pfctl -f /etc/pf.conf

# Ver rutas
netstat -rn

# Probar red
ping -c 3 192.168.88.1
ping -c 3 8.8.8.8
ping -c 3 google.com

Paso 6: Si funciona, reiniciar para aplicar loader.conf y sysctl.conf

shutdown -r now

16. Rollback completo

Requiere arrancar en modo monousuario. No se puede hacer con el dataset montado.

Paso 1: Crear snapshot de seguridad del estado actual

Antes de tocar nada, para poder volver si algo falla:

zfs snapshot -r zroot@before-rollback-$(date +%Y%m%d-%H%M)

Paso 2: Reiniciar en modo monousuario

shutdown -r now

En el menú de arranque de FreeBSD, pulsar 2 (Boot Single User).

Paso 3: Preparar el sistema

zfs set readonly=off zroot
zfs mount -a

Paso 4: Rollback

Esto destruye los snapshots posteriores a este (incluido el de before-rollback).
zfs rollback -R zroot/ROOT/default@snap-14.4-RELEASE-p8-casa.lan-2026-28-sept-2026

Paso 5: Salir del modo monousuario y reiniciar

exit

O directamente:

reboot

Qué se pierde con el rollback

ElementoEstado
pf.confext_if="wlan0" (vuelve atrás)
loader.confSin hw.em.eee_setting=1
sysctl.confSin dev.em.0.eee_control=0
/etc/rc.confEstado del snapshot
Archivos en /home/carlosNO se pierden (dataset separado)
Archivos en /varNO se pierden (dataset separado, si no se hace rollback ahí)
Solo afecta a zroot/ROOT/default (el sistema base). Los datos personales en zroot/home/carlos no se tocan.

17. Errores comunes a evitar

  • Usar zfs send -Rcv sin -w → falla si hay datasets cifrados.
  • Olvidar -s en zfs receive → no hay reanudación posible si se corta.
  • Cerrar la sesión SSH sin tmux → el envío muere al desconectar.
  • No guardar la clave de cifrado fuera del backup → backup inútil si se pierde.
  • Usar zfs destroy sin -n primero → riesgo de borrar snapshots con dependencias.
  • Ejecutar el script de retención sin verificar → puede borrar snapshots indiscriminadamente.

18. Checklist final

  • [ ] Disco USB identificado y preparado
  • [ ] Pool zbk creado con ashift=12
  • [ ] Dataset zbk/tormenta creado
  • [ ] Snapshot del sistema creado (zfs snapshot -r)
  • [ ] Envío lanzado dentro de tmux
  • [ ] Proceso monitorizado hasta el final
  • [ ] Verificado: snapshots origen vs destino coinciden
  • [ ] Pool exportado limpiamente (zpool export zbk)
  • [ ] USB desconectado y guardado en lugar seguro
  • [ ] Clave de cifrado guardada en al menos 2 sitios distintos
FreeBSD es genial!.

martes, 1 de septiembre de 2026

Script Copia de Seguridad de un Conjunto de Datos ZFS Hacia un Dispositivo Remoto

1. Replicación de datos ZFS FreeBSD

2. Script Copia de Seguridad de un Conjunto de Datos ZFS Envío Remoto Versión 1.0.0

3. Script Replicación Incremental ZFS Cliente Servidor - Envío Remoto Versión 1.1.0

Script de Replicación ZFS (zfsrep.sh)

shell zsh

Este script automatiza la copia de seguridad y replicación incremental de snapshots de ZFS desde el sistema local (solaris.local.com FreeBSD 14.4 ZFS) hacia un servidor remoto (tormenta.local.com FreeBSD 14.4 ZFS). Gestiona la creación de la snapshot diaria, el envío incremental a través de SSH y la purga automática de snapshots antiguas tanto en origen como en destino, respetando una retención de, (el valor de retention), 15 snapshots.

Descripción del funcionamiento

El script utiliza zfs send y zfs receive para transferir los datos. Detecta la última snapshot disponible en el destino para realizar un envío incremental (ahorrando ancho de banda), o realiza un envío completo si no encuentra una base común. Todo el proceso queda registrado en un archivo de log ubicado en /home/carlos/cronlog.

Contenido del script

#!/bin/sh
### BEGIN INFO
# PROVIDE:
# REQUIRE:
# KEYWORD:
### END INFO

### INICIO DEL GUION
# Estas variables se nombran primero porque están anidadas en otras variables.
snap_prefix=snap
retention=15

# Se necesitan rutas completas a estas utilidades al ejecutar el script desde cron.
date=/bin/date
cut=/usr/bin/cut
grep=/usr/bin/grep
sed=/usr/bin/sed
sort=/usr/bin/sort
xargs=/usr/bin/xargs
zfs=/sbin/zfs

src_0="zroot/home/carlos"
dst_0="zbackup/solaris/home/carlos"
host="root@192.168.88.160"
today="$snap_prefix-`$date +%Y%m%d`"
snap_today="$src_0@$today"

# Patrón grep con sufijo numérico para evitar falsos positivos.
snap_old=`$zfs list -t snapshot -o name \
  | $grep "${src_0}@${snap_prefix}-[0-9]" \
  | $sort -r \
  | $sed 1,${retention}d \
  | $sort \
  | $xargs -n 1`

# Detecta la snapshot más reciente del destino remoto para usarla como base.
# Todo el pipeline corre en tormenta vía SSH.
snap_base_name=`ssh $host "$zfs list -t snapshot -o name \
  | grep '${dst_0}@${snap_prefix}-[0-9]' \
  | sort -r \
  | sed 1q \
  | cut -d@ -f2"`
snap_base="${src_0}@${snap_base_name}"

# snap_old_dst_0 lista las snapshots > retention en el destino remoto.
snap_old_dst_0=`ssh $host "$zfs list -t snapshot -o name \
  | grep '${dst_0}@${snap_prefix}-[0-9]' \
  | sort -r \
  | sed 1,${retention}d \
  | sort"`

log=/home/carlos/cronlog

echo >> $log
echo "zfsrep.sh" >> $log
$date >> $log
echo >> $log

# Busca la instantánea de hoy en origen y, si no la encuentras, créala.
if $zfs list -H -o name -t snapshot \
  | $sort \
  | $grep "${snap_today}$" > /dev/null
then
	echo "La instantánea de hoy '$snap_today' ya existe." >> $log
else
	echo "Tomando la instantánea de hoy: $snap_today" >> $log
	$zfs snapshot -r $snap_today >> $log 2>&1
fi

echo >> $log

# Comprueba si la instantánea de hoy ya existe en el destino remoto.
# Si existe, no hay nada que enviar; saltar directamente a la purga.
if ssh $host "$zfs list -H -o name -t snapshot \
  | grep '${dst_0}@${today}$'" > /dev/null 2>&1
then
	echo "La instantánea de hoy ya existe en destino remoto. Nada que enviar." >> $log
else
	# Detecta la base del incremental y replica.
	# Permite recuperarse de huecos de sincronización sin intervención manual.
	if [ -n "$snap_base_name" ] && $zfs list -H -o name -t snapshot \
	  | $grep "${snap_base}$" > /dev/null
	then
		echo "Usando '$snap_base' como base del incremental..." >> $log
		$zfs send -R -i $snap_base $snap_today \
		  | ssh $host $zfs receive -vuF -x mountpoint $dst_0 >> $log 2>&1
		echo >> $log
		echo "Replicacion completada." >> $log
	else
		echo "No se encontró snapshot común entre origen y destino." >> $log
		echo "Realizando envío completo (puede tardar)..." >> $log
		$zfs send -R $snap_today \
		  | ssh $host $zfs receive -vuF -x mountpoint $dst_0 >> $log 2>&1
		echo >> $log
		echo "Envío completo finalizado." >> $log
	fi
fi

echo >> $log

# Eliminar instantáneas locales más antiguas que el valor asignado a $retention.
echo "Intentando destruir instantáneas antiguas $src_0..." >> $log

if [ -n "$snap_old" ]
then
	echo "Destruyendo las siguientes instantáneas antiguas:" >> $log
	echo "$snap_old" >> $log
	$zfs list -t snapshot -o name \
	  | $grep "${src_0}@${snap_prefix}-[0-9]" \
	  | $sort -r \
	  | $sed 1,${retention}d \
	  | $sort \
	  | $xargs -n 1 $zfs destroy -r >> $log 2>&1
else
	echo "No se pudo encontrar ninguna instantánea local para destruir." >> $log
fi

echo >> $log

# Eliminar instantáneas remotas más antiguas que el valor asignado a $retention.
# Todo el pipeline, incluido zfs destroy, corre en tormenta vía SSH.
echo "Intentando destruir instantáneas antiguas $dst_0..." >> $log

if [ -n "$snap_old_dst_0" ]
then
	echo "Destruyendo las siguientes instantáneas antiguas en $host:" >> $log
	echo "$snap_old_dst_0" >> $log
	ssh $host "$zfs list -t snapshot -o name \
	  | grep '${dst_0}@${snap_prefix}-[0-9]' \
	  | sort -r \
	  | sed 1,${retention}d \
	  | sort \
	  | xargs -n 1 $zfs destroy -r" >> $log 2>&1
else
	echo "No se pudo encontrar ninguna instantánea remota para destruir." >> $log
fi

echo "**********" >> $log

### FIN DEL SCRIPT

Estructura de zbackup (servidor)

carlos@tormenta:~ % zfs list -r zbackup
NAME                          USED  AVAIL  REFER  MOUNTPOINT
zbackup                      1008G   791G  92.5M  none
zbackup/dellhome             56.6G   791G  56.6G  none
zbackup/nfsv4                36.6G   791G    96K  none
zbackup/nfsv4/docs           7.80G   791G  7.80G  none
zbackup/nfsv4/secrets        28.8G   791G  28.8G  none
zbackup/recovery              755G   791G   104K  none
zbackup/recovery/solaris      282G   791G   282G  none
zbackup/recovery/tormenta     473G   791G   473G  none
zbackup/reserved               50G   841G    96K  none
zbackup/solaris              80.9G   791G    96K  none
zbackup/solaris/home         80.9G   791G    96K  none
zbackup/solaris/home/carlos  80.9G   791G  73.2G  none
zbackup/usr                   147M   791G   104K  none
zbackup/usr/home              147M   791G   140M  none

zfsrep.sh - Funcionamiento

Asigna rutas absolutas a los binarios (zfs, date, grep, etc.) para evitar problemas de $PATH al ejecutarse desde cron

date=/bin/date
cut=/usr/bin/cut
grep=/usr/bin/grep
sed=/usr/bin/sed
sort=/usr/bin/sort
xargs=/usr/bin/xargs
zfs=/sbin/zfs

Contexto

El script corre en solaris (cliente) y replica el dataset zroot/home/carlos hacia zbackup/solaris/home/carlos en tormenta (servidor), usando SSH como canal de transporte.

1 - Definición de variables

src_0="zroot/home/carlos"
dst_0="zbackup/solaris/home/carlos"
host="root@192.168.88.160"
today="snap-20260901"
snap_today="zroot/home/carlos@snap-fecha_de_hoy"

Se calculan los nombres de las snapshots y rutas antes de hacer nada. today se genera en este momento con $date, así que si el script corre a las 2:00 AM o a las 23:00 del mismo día, el nombre es idéntico - eso es lo que permite la comprobación de duplicado más adelante.

2 - Inventario de snapshots antiguas a purgar (local)

snap_old=`$zfs list -t snapshot -o name
  | $grep "${src_0}@${snap_prefix}-[0-9]"
  | $sort -r
  | $sed 1,${retention}d
  | $sort
  | $xargs -n 1`

Se calcula en este momento, antes de crear la snapshot de hoy. El pipeline:

Lista todas las snapshots del sistema Filtra las de zroot/home/carlos@snap- seguido de dígito Las ordena de más reciente a más antigua (sort -r) Elimina las primeras 15 líneas (sed 1,15d) - las 15 más recientes se conservan Lo que queda son las candidatas a destruir

Este cálculo es previo a la snapshot de hoy para que retention=15 sea exacto.

3 - Inventario remoto: base del incremental y purga

snap_base_name=`ssh $host "...pipeline en tormenta..."`
snap_old_dst_0=`ssh $host "...pipeline en tormenta..."`

Se abren dos conexiones SSH a tormenta para:

snap_base_name: la snapshot más reciente que tiene zbackup/solaris/home/carlos. Será el punto de partida del incremental. Si el destino tiene snap-20260831, el incremental enviará solo los cambios entre ese día y hoy. snap_old_dst_0: lista de snapshots en destino que superan retention=15, candidatas a purga remota.

Todo el pipeline de grep/sort/sed corre dentro del SSH, en tormenta, para que el resultado que llega a solaris sea solo texto limpio.

4 - Snapshot de hoy en origen

if zfs list ... | grep "${snap_today}$"
then
    "ya existe"
else
    zfs snapshot -r $snap_today
fi

Comprueba si zroot/home/carlos@snap-20260901 ya existe. El flag -r crea la snapshot de forma recursiva - si hubiera datasets hijo bajo zroot/home/carlos, también se snapshotearían. Si ya existe (segunda ejecución del día), simplemente lo anota en el log y continúa.

5 - Comprobación de duplicado en destino

if ssh $host "zfs list ... | grep '${dst_0}@${today}$'"
then
    "Nada que enviar"
else
    # continuar con el send
fi

Pregunta a tormenta: ¿ya tienes zbackup/solaris/home/carlos@snap-20260901? Si la respuesta es sí, el script salta directamente a la purga - no hay nada que transferir. Esto evita el reenvío innecesario si cron lanza el script más de una vez al día.

6 - Replicación incremental (o completa)

Caso normal - base común encontrada:

zfs send -R -i $snap_base $snap_today \
  | ssh $host zfs receive -vuF -x mountpoint $dst_0

zfs send genera un stream binario con los bloques que cambiaron entre snap_base y snap_today. Ese stream se pasa directamente por el pipe a SSH, que lo entrega a zfs receive en tormenta. No se escribe nada en disco en solaris - es un flujo directo en memoria.

Los flags:

-R: incluye snapshots hijas recursivamente
-i: incremental entre dos snapshots
-v: verbose en el log
-u: no monta el dataset recibido
-F: destruye en destino lo que no existe en el stream (mantiene coherencia)
-x mountpoint: ignora la propiedad mountpoint del stream — el destino mantiene mountpoint=none

Caso de hueco - sin base común:

zfs send -R $snap_today | ssh $host zfs receive -vuF -x mountpoint $dst_0

Si no hay ninguna snapshot común entre origen y destino, se hace un envío completo. Más lento, pero garantiza que el destino queda sincronizado desde cero.

7 - Purga local

zfs list -t snapshot -o name
  | grep "${src_0}@${snap_prefix}-[0-9]"
  | sort -r | sed 1,15d | sort
  | xargs -n 1 zfs destroy -r

Mismo pipeline que en el paso 2, pero esta vez ejecutando zfs destroy -r sobre cada snapshot antigua. El -r destruye también las snapshots hijas si las hubiera. Las 15 más recientes quedan intactas.

8 - Purga remota

ssh $host "zfs list ... | grep ... | sort -r | sed 1,15d | sort | xargs -n 1 zfs destroy -r"

Ídem pero en tormenta. Todo el pipeline - incluido el zfs destroy - corre dentro del SSH. Esto es crítico: si el destroy corriera en local, intentaría destruir zbackup/... en solaris, donde ese dataset no existe.

Entrada en cron para ejecución diaria a las 2:00 AM

 crontab -e
# Ejecución diaria de la copia de seguridad ZFS a las 2:00 AM
0 2 * * * /home/carlos/bin/zfsrep.sh
Guardar y salir

Que sea ejcutable

chmod +x /home/carlos/bin/zfsrep.sh

Comprobar que el script funciona manualmente antes de programarlo

sh -x /home/carlos/bin/zfsrep.sh

Ejemplo de crontab completo (para root)

SHELL=/bin/sh
PATH=/bin:/usr/bin:/sbin:/usr/sbin
MAILTO=root

# Ejecución diaria de la copia de seguridad ZFS a las 2:00 AM
0 2 * * * /home/carlos/bin/zfsrep.sh

Notas Finales

Robustez: La lógica de incremental/completo permite que el script se recupere automáticamente si se borra una instantánea intermedia.

Eficiencia: La mejora que evita reenvíos el mismo día ahorra ancho de banda y CPU.

Mantenimiento: La limpieza automática en origen y destino evita que el espacio se llene con instantáneas antiguas.

Portabilidad: Al usar rutas absolutas, el script funciona correctamente desde cron.

Con este sistema, tus datos están respaldados diariamente en un dispositivo remoto, con una política de retención clara y la capacidad de recuperación ante fallos de sincronización.

FreeBSD es genial!.

martes, 15 de abril de 2025

Script sh Replicar Instantáneas de Conjunto de Datos ZFS desde Servidor FreeBSD Local a otra Máquina FreeBSD Externa

El autor de este sencillo e ingenioso guión es iceflatline. Lamentablemente su sitio web está caido. Contiene algunas modificaciones para adaptarlo a este laboratorio

Script mejorado

https://gnulinuxcodigo.blogspot.com/2025/03/crear-particiones-en-disco-duro-usb-con.html

Crear el pool de respaldo zbackup

 zpool create -o ashift=12 \
-O compression=lz4 \
-O atime=off \
-O mountpoint=none \
backup /dev/gpt/zbackup

Conjuntos de datos zbackup

 sudo zfs create -o mountpoint=none zbackup/solaris
 sudo zfs create -o mountpoint=none zbackup/solaris/home
 sudo zfs create -o mountpoint=none zbackup/solaris/home/carlos
 sudo zfs create -o mountpoint=none zbackup/tormenta-confiles
 sudo zfs create -o mountpoint=none zbackup/usr/
 sudo zfs create -o mountpoint=none zbackup/usr/backup
 sudo zfs create -o mountpoint=none zbackup/usr/backup/docs
 sudo zfs create -o mountpoint=none zbackup/usr/home

Flujo de trabajo recomendado para un disco de respaldo mountpoint=none

Seguridad contra modificaciones accidentales

Evita conflictos de rutas

Comportamiento en el arranque — Con mountpoint=none ZFS no intenta montar nada del pool zbackup al arrancar, lo que acelera el boot y evita errores si el disco USB no está conectado en ese momento.

Acceso bajo demanda — Cuando se necesita recuperar algo, se monta puntualmente el dataset de interés, se hace lo que se necesita, y luego se desmonta.

Los scripts no se ven afectados — zfs receive escribe directamente al dataset sin necesidad de que esté montado. mountpoint=none no interfiere en absoluto con zfs send/receive.

carlos@tormenta:~ % zfs list -r zbackup
zbackup                            48.9G  1.71T  4.86M  none
zbackup/solaris                    40.8G  1.71T    96K  none
zbackup/solaris/home               40.8G  1.71T    96K  none
zbackup/solaris/home/carlos        40.8G  1.71T  39.9G  none
zbackup/tormenta-confiles           800K  1.71T   800K  none
zbackup/usr                        8.16G  1.71T   104K  none
zbackup/usr/backup                 7.80G  1.71T   104K  none
zbackup/usr/backup/docs            7.80G  1.71T  7.80G  none
zbackup/usr/home                    368M  1.71T   132M  none
#!/bin/sh
### BEGIN INFO
# PROVIDE:
# REQUIRE:
# KEYWORD:

### INICIO DEL GUION
# Estas variables se nombran primero porque están anidadas en otras variables.
snap_prefix=snap
retention=15

# Se necesitan rutas completas a estas utilidades al ejecutar el script desde \
cron.
date=/bin/date
grep=/usr/bin/grep
sed=/usr/bin/sed
sort=/usr/bin/sort
xargs=/usr/bin/xargs
zfs=/sbin/zfs

src_0="zroot/home/carlos"
dst_0="zbackup/solaris/home/carlos"
host="root@192.168.88.160"

# Usar $date en lugar de date para consistencia con rutas absolutas.
today="$snap_prefix-`$date +%Y%m%d`"
yesterday="$snap_prefix-`$date -v -1d +%Y%m%d`"
snap_today="$src_0@$today"
snap_yesterday="$src_0@$yesterday"

# Patrón grep con sufijo numérico para evitar falsos positivos.
snap_old=`$zfs list -t snapshot -o name \
  | $grep "${src_0}@${snap_prefix}-[0-9]" \
  | $sort -r \
  | $sed 1,${retention}d \
  | $sort \
  | $xargs -n 1`

log=/home/carlos/cronlog

# Todo el pipeline de listado remoto corre en tormenta vía SSH.
snap_old_dst_0=`ssh $host "$zfs list -t snapshot -o name \
  | grep '${dst_0}@${snap_prefix}-[0-9]' \
  | sort -r \
  | sed 1,${retention}d \
  | sort"`

# Crea una línea en blanco entre la entrada de registro anterior y esta.
echo >> $log

# Imprimir el nombre del script.
echo "zfsrep.sh" >> $log
# Imprime la fecha de hoy.
$date >> $log
# Crea una línea en blanco.
echo >> $log

# Buscar instantánea de hoy y, si no la encuentra, créala.
if $zfs list -H -o name -t snapshot \
  | $sort \
  | $grep "${snap_today}$" > /dev/null
then
	echo "La instantánea de hoy '$snap_today' ya existe." >> $log
else
	echo "Tomando la instantánea de hoy: $snap_today" >> $log
	$zfs snapshot -r $snap_today >> $log 2>&1
fi

echo >> $log

# Replicar el snapshot diario (ayer) o mostrar error si falta.
if $zfs list -H -o name -t snapshot \
  | $sort \
  | $grep "${snap_yesterday}$" > /dev/null
then
	echo "La instantánea de ayer '$snap_yesterday' existe. Continuando \
    con la replicacion..." >> $log
	$zfs send -R -i $snap_yesterday $snap_today \
	  | ssh $host $zfs receive -vuF -x mountpoint $dst_0 >> $log 2>&1
	echo >> $log
	echo "Replicacion completada." >> $log
else
	echo "Error: Replicacion no completada. Falta la instantánea de \
    ayer." >> $log
fi

echo >> $log

# Eliminar instantáneas locales más antiguas que el valor asignado a retention.
echo "Intentando destruir instantáneas antiguas $src_0..." >> $log

if [ -n "$snap_old" ]
then
	echo "Destruyendo las siguientes instantáneas antiguas:" >> $log
	echo "$snap_old" >> $log
	$zfs list -t snapshot -o name \
	  | $grep "${src_0}@${snap_prefix}-[0-9]" \
	  | $sort -r \
	  | $sed 1,${retention}d \
	  | $sort \
	  | $xargs -n 1 $zfs destroy -r >> $log 2>&1
else
	echo "No se pudo encontrar ninguna instantánea local para \
    destruir." >> $log
fi

echo >> $log

# Purgar snaps remotos > retention
# Todo el pipeline, incluido zfs destroy, corre en tormenta vía SSH.
echo "Intentando destruir instantáneas antiguas $dst_0..." >> $log

if [ -n "$snap_old_dst_0" ]
then
	echo "Destruyendo las siguientes instantáneas antiguas en $host:" >> $log
	echo "$snap_old_dst_0" >> $log
	ssh $host "$zfs list -t snapshot -o name \
	  | grep '${dst_0}@${snap_prefix}-[0-9]' \
	  | sort -r \
	  | sed 1,${retention}d \
	  | sort \
	  | xargs -n 1 $zfs destroy -r" >> $log 2>&1
else
	echo "No se pudo encontrar ninguna instantánea remota para \
    destruir." >> $log
fi

# Marcar el fin del script con un delimitador.
echo "**********" >> $log

### FIN DEL SCRIPT

Luego, agregue una tarea a la tabla cron bajo la cuenta del usuario. El script se ejecuta todos los días a las 22:00 (hora local):

# Ejecuta script backup cada día a las 22:00
0 22 * * * /home/carlos/bin/zfsrep.sh

Las entradas del archivo /home/carlos/cronlog se verán así:

**********

zfsrep.sh
Tue Apr 15 09:45:19 CEST 2025

Tomando la instantánea de hoy: zroot/home@snap-20250415

La instantánea de ayer 'zroot/home@snap-20250414' existe. \
Continuando con la replicacion...
receiving incremental stream of zroot/home@snap-20250415 into \
zbackup/solaris/home/carlos@snap-20250415
received 312B stream in 0.91 seconds (341B/sec)
receiving incremental stream of zroot/home/carlos@snap-20250415 \
into zbackup/solaris/home/carlos@snap-20250415
received 16.3M stream in 7.07 seconds (2.30M/sec)

Replicacion completada.

Intentando destruir instantáneas antiguas zroot/home...
Destruyendo las siguientes instantáneas antiguas:
zroot/home@snap-20250327

Intentando destruir instantáneas antiguas zbackup/solaris...
No se pudo encontrar ninguna instantánea para destruir.
FreeBSD es genial!.

sábado, 14 de diciembre de 2024

Replicación de datos ZFS FreeBSD

Bibliografía:
klarasystems.com
Klara Inc. Allan Jude y Jim Salter
Illumos ZFS Administracion

1. Replicación de datos ZFS FreeBSD

2. Script Copia de Seguridad de un Conjunto de Datos ZFS Envío Remoto Versión 1.0.0

3. Script Replicación Incremental ZFS Cliente Servidor - Envío Remoto Versión 1.1.0

Diagrama Dia

Replication ZFS en FreeBSD

Lo que hace la replicación ZFS es poder serializar un sistema de archivos. Cuando serializa un objeto en ciencia de datos o programación de computadoras se trata de hacerlo en formato que pueda transportarse con facilidad de tal manera que podamos recrear ese sistema de archivos en el otro lado, de forma que nos permite cruzar sistemas operativos, no importa si la fuente es FreeBSD y el destino es Linux, Illumos, Mac o Windows, etc.

Recrea el sistema de archivo exactamente igual sin depender que sea byte por byte. Eso también proporciona compatibilidad hacia delante y hacia atrás, puede hacer una transmisión ZFS 10 con la última versión de ZFS y aún recibirlo con una versión de ZFS de hace 10 años o viceversa.

Otra cosa importante sobre la forma en que esto funciona en la replicación ZFS es que es unidireccional, por lo que el remitente controla todo lo que empaqueta el sistema de archivos en el formato serializado y lo enviará, no requiere ninguna interacción con el lado que lo va a recibir, lo que significa que no tiene que recibirlo ahora mismo.

Lo interesante sobre la forma en que esto funciona es que no requiere un manejo especial para cosas como permisos, atributos extentidos de ACLS y cualquier otra cosa que pueden estar adjuntos al sistema de archivos, todos son sólo datos que se serializan de la misma manera que el contenido de los archivos, por lo que ZFS no tiene que realizar comprobaciones adicionales ni nigún trabajo extra, simplemente envía los datos y el resto, el lado es responsable de interpretarlo, por lo que si usa algunas de las herramientas de depuración de ZFS podemos ver lo que hay dentro de una secuencia de envío, pero básicamente es un comando de objeto que dice que necesitamos crear un nuevo objeto.

Como toda copia de seguridad todavía debe probar y asegurarse de que puede restaurar el sistema de archivos desde su copia de seguridad.

Sumas de Verificación de un Extremo a Otro

ZFS utiliza sumas de verificación de extremo a extremo por lo que sabemos que ZFS almacena una suma de verificación para cada bloque en el que escribe incluido sus propios metadatos internos. Antes de enviar ZFS se asegura de que los datos que está a punto de enviar son correctos comprobando la suma de verificación, luego los envía con la suma de verificación para que cuando llegue al otro lado verificar que lo que recibe sigue siendo los datos correctos. Después se escriben en disco y al volver a leerlos desde la copia de seguridad verifica de nuevo que sigue siendo la misma suma de verificación y que los datos sobrevivieron intactos. Si los datos están cifrados, también hay un código de verificación allí, asegurándose de que no solo los datos estén intactos sino también que no han sido modificados maliciosamente porque verifica que se descibra correctamente.

¿Que ocurre si por algún motivo se interrumpe la replicación?.

En el lado receptor aparece una propiedad llamada token de reanudación y contiene en qué instantánea estábamos, en que objeto en esa instantánea, que tan lejos en ese objeto y permite reanudar esa replicación. Si ejecutamos

  zfs get -H receive_resume_token zroot/backup
 zstream token 1-ca6584b3f-c0-bb2...
 object: 128
 offset: 1507590144
 bytes: 1511357704
 toguid: 2687048938072742180
 toname: 'zroot/usr/home@snap-20241214'
 

Nos dará una cadena, vemos que estamos enviando el objeto 128, este desplazamiento de 1.5 GB y que tenemos otros 1.5 GB para enviar, la cuadrícula de la instantánea y el nombre de la instantánea.

Como toda copia de seguridad todavía debe probar y asegurarse de que puede restaurar el sistema de archivos desde su backup

En el lado de envío (origen) se utiliza:

zfs send -t receive_resume_token 1-ca6584b3f-c0-bb2...

El token contiene el resto de la información sobre exactamente desde dónde continuar y dejaremos que este envio continue.

Conectar al servidor tormenta vía SSH desde el cliente solaris

El archivo de hosts contiene información sobre los hosts conocidos en la red.

Archivo /etc/hosts

cat /etc/hosts| grep tormenta
192.168.88.160   tormenta

Archivo ~/.ssh/config

$ cat .ssh/config
Host tormenta
	HostName 192.168.88.160
	User carlos
	Port 30000
	AddressFamily inet
	BindInterface em0
	CheckHostIP yes
...
$ ssh tormenta
Last login: Sat Dec 14 06:50:20 2024 from solaris
...

Compruebe que el ssh basado en clave funciona

Copiar la clave publica id_rsa.pub del cliente en el archivo authorized_keys de su cuenta en el servidor

 ssh-keygen

 cat ~/.ssh/id_rsa.pub | ssh remote_host "cat >> .ssh/authorized_keys"

 ssh tormenta
 
 carlos@tormenta:~ %

Particionar el Disco Para las Copias de Seguridad

El disco debe estar desmontado, para averiguar el punto de montaje se utiliza mount y unmount para desmontarlo.

Es importante asegurarse del disco sobre el que va a trabajar. Para ello dispone de múltiples opciones.

 gpart
 gpart show

 lsblk
 lsblk -d
 
 Mostrar discos físicos
 sysctl kern.disks

Y por último usando dmesg para identificar discos conectados, por ejemplo, si conecta un dispositivo de almacenamiento vía USB aparece al final del archivo dmesg

 /sbin/dmesg | tail
 

Preparar el dispositivo de almacenamiento

El comando gpart destroy DESTRUYE EL ESQUEMA DE PARTICIONAMIENTO implementado por GEOM. Es un proceso irreversible. Destruye todos los datos. -F Fuerza la destrucción de la tabla de particiones incluso si no está vacía.

Ya conoce el disco a particionar?

En esta laboratorio es (da1) /dev/da1 de 2 TB con una capacidad real de 1862 GB (2x0.9313 = 1.862 TB = 1862 GB)

gpart destroy -F /dev/da1

gpart create -s gpt /dev/da1
gpart add -t freebsd-zfs -a 1m -l backups da1
da1p1 created

zpool create -o ashift=12 \
	-O compression=lz4 \
	-O atime=off \
	-O mountpoint=none \
	backups gpt/backups

zpool set autoexpand=on backups
zpool set listsnapshots=on backups

Consultar propiedades

zfs get compression,atime,dedup,sync,copies,mountpoint zbackup
NAME     PROPERTY     VALUE           SOURCE
zbackup  compression  on              default
zbackup  atime        off             local
zbackup  dedup        off             default
zbackup  sync         standard        default
zbackup  copies       1               default
zbackup  mountpoint   none            local

Crear Conjunto de Datos en pool backups

zfs create -p backups/usr/backup/docs
zfs create backps/usr/home
zfs create -p backups/solarishome/home
zfs create -p backups/recovery/solaris
zfs create backups/recovery/tormenta
zfs create backups/reserved

Listar recursivamente backups

zfs list -r backups
NAME                             USED  AVAIL     REFER  MOUNTPOINT
backups                          480G  1.29T      192K  none
backups/recovery                 330G  1.29T       96K  none
backups/recovery/solaris        67.9G  1.29T     67.9G  none
backups/recovery/tormenta        262G  1.29T      262G  none
backups/reserved                  96K  1.34T       96K  none
backups/solarishome             62.8G  1.29T       96K  none
backups/solarishome/home        62.8G  1.29T     40.4G  none
backups/usr                     7.78G  1.29T       96K  none
backups/usr/backup              7.78G  1.29T       96K  none
backups/usr/backup/docs         7.78G  1.29T     7.78G  none
backups/usr/home                6.14M  1.29T     5.97M  none

Establecer un nombre descriptivo si la versión de OpenZFS lo admite

zpool version
zfs-2.2.9-FreeBSD_g079ba86d7
zfs-kmod-2.2.9-FreeBSD_g079ba86d7

zpool get all backups | grep -E 'comment|compatibility'
backups  comment           -          default
backups  compatibility     off        default

 zpool set comment='Copia Seguridad USB' backups

 zfs get all backups | grep -i comment
 zpool set comment='Copia Seguridad USB' backups

zfs get all backups | grep -i comment
zpool get all backups | grep -E 'comment|compatibility'
backups  comment        Copia Seguridad USB      local
backups  compatibility       off                 default

zpool get comment backups
NAME     PROPERTY  VALUE                SOURCE
backups  comment   Copia Seguridad USB  local

Listar recursivamente pool zroot servidor tormenta

zfs list -r zroot 
NAME                                   USED  AVAIL   REFER  MOUNTPOINT
zroot                                  170G   264G    112K  /zroot
zroot/ROOT                            40.8G   264G     96K  none
zroot/ROOT/default                    40.8G   264G   36.0G  /
zroot/confsolaris                     1.16M   264G   1.16M  /zroot/confsolaris
zroot/jails                           1.25G   264G     96K  /jails
zroot/jails/loghost                   1.25G   264G   1.25G  /jails/loghost
zroot/jails/media                       96K   264G     96K  /jails/media
zroot/reserved                          96K   289G     96K  /zroot/reserved
zroot/tmp                             2.87G   264G   2.87G  /tmp
zroot/usr                             90.7G   264G     96K  /usr
zroot/usr/backup                      88.5G   264G    128K  /usr/backup
zroot/usr/backup/confiles              284M   264G     96K  /usr/backup/confiles
zroot/usr/backup/confiles/tormenta    4.71M   264G    860K  /usr/backup/confiles/tormenta
zroot/usr/backup/docs                 7.78G   264G   7.78G  /usr/backup/docs
zroot/usr/home                        6.57M   264G   4.82M  /usr/home
zroot/usr/ports                        924M   264G    924M  /usr/ports
zroot/usr/src                         1.26G   264G   1.26G  /usr/src
zroot/var                             1.27G   264G     96K  /var
zroot/var/audit                         96K   264G     96K  /var/audit
zroot/var/crash                         96K   264G     96K  /var/crash
zroot/var/db                            96K   264G     96K  /var/db
zroot/var/jail                        1.26G   264G     96K  /var/jail
zroot/var/jail/www                    1.26G   264G   1.26G  /var/jail/www
zroot/var/log                         3.86M   264G   2.64M  /var/log
zroot/var/mail                         144K   264G    144K  /var/mail
zroot/var/tmp                          152K   264G     96K  /var/tmp

Exportar e Importar backups

Esto asegura que el pool se cierre correctamente y se liberen todos los recursos asociados. Luego, el disco puede ser desconectado de forma segura.

zpool export backups

Grupos disponibles para importar

z
zpool import
  pool: backups
     id: 10639314260213971789
  state: ONLINE
 action: The pool can be imported using its name or numeric identifier.
 config:

	backups     ONLINE
	  da0p1     ONLINE
zpool import backups	  

Listar Información Sobre los Grupos de Almacenamiento ZFS

zpool list 
NAME      SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH
backups  1.81T   430G  1.39T        -         -     0%    23%  1.00x    ONLINE
zroot     448G   142G   306G        -         -     4%    31%  1.00x    ONLINE

Disponible para importar

zpool import
no pools available to import

Crear las instantáneas

Para crear instantáneas y todos sus sistemas de archivos descendientes se utiliza -r Crear las instantáneas de los conjunto de datos zroot/usr/backup/docs y zroot/usr/home

zfs snapshot -r zroot/usr/home@snap-`date +%Y%m%d`
zfs snapshot -r zroot/usr/backup/docs@snap-`date +%Y%m%d`

Listar instantáneas creadas

zfs list -t snapshot zroot/usr/home@snap-20241213
NAME                           USED  AVAIL     REFER  MOUNTPOINT
zroot/usr/home@snap-20241213   172K      -     5.97M  -

zfs list -t snapshot zroot/usr/backup/docs@snap-20241213
NAME                                  USED  AVAIL     REFER  MOUNTPOINT
zroot/usr/backup/docs@snap-20241213   216K      -     7.78G  -

Enviar la primera instantánea completa

-R con el comando zfs send. Esta opción preservará las propiedades ZFS de cualquier conjunto de datos, instantáneas y clones descendientes en la secuencia.
-d Utilice la ruta completa de la instantánea enviada sin el primer elemento (sin el nombre del grupo) para determinar el nombre de la nueva instantánea
-u No montar el flujo recibido
-v Imprime información detallada sobre la secuencia y el tiempo necesario para realizar la operación de recepción

En el primer envío se utiliza la ruta de destino completa (backups/usr/backup/docs)
En el flujo incremental sólo se utiliza el nombre del pool (backups)

zfs send -R -v zroot/usr/home@snap-20241213 | zfs receive -vd backups/usr/home
zfs send -R -v zroot/usr/backup/docs@snap-20241213 | zfs receive -vdF \
backups/usr/backup/docs

Envío incremental

Tenga en cuenta que el envío de un flujo incremental sólo tendrá éxito si ya existe una instantánea completa inicial en el lado receptor.

Envía todos los bloques que tienen una hora de creacion entre zroot@snap-20241213 y zroot@snap-20241214, se serializan y envían al otro lado que ya tiene zroot@snap-20241213 por lo que puede aplicar los cambios y tener eso también sin tener que mirar lo que hay en zroot@snap-20241213, al contrario que rsync que tiene que revisar archivo por archivo.

Si recibe un error como este al realizar una replicación incremental: "No se puede recibir el flujo incremental las copias de seguridad de destino se han modificado desde la snapshot más reciente", es decir, que las instantáneas de origen y destino ya no son idénticas ZFS aborta la replicación. Use receive -F para forzar al sistema receptor a retroceder al estado de la última instantánea recibida para que los sistemas estén nuevamente sincronizados.

Tomar las instantáneas

zfs snapshot zroot/usr/home@snap-`date +%Y%m%d`
zfs snapshot zroot/usr/backup/docs@snap-`date +%Y%m%d`

Listar instantáneas creadas

zfs list -t snapshot zroot/usr/home@snap-20241213
NAME                           USED  AVAIL   REFER  MOUNTPOINT
zroot/usr/home@snap-20241214   172K      -   5.97M  -

zfs list -t snapshot zroot/usr/backup/docs@snap-20241213
NAME                                  USED  AVAIL   REFER  MOUNTPOINT
zroot/usr/backup/docs@snap-20241214   216K      -   7.78G  -

Enviar el flujo de datos

-F Fuerce una reversión del sistema de archivos a la instantánea más reciente antes de realizar la operación de recepción. Si recibe un flujo de replicación incremental (por ejemplo, uno generado por zfs send -R [-i|-I]), destruya las instantáneas y los sistemas de archivos que no existan en el lado de envío.

zfs send -R -i zroot/usr/home@snap-20241213 zroot/usr/home@snap-20241214 | zfs \
>receive -vdF backups
zfs send -R -i  zroot/usr/backup/docs@snap-20241213 \
zroot/usr/backup/docs@snap-20241214 | zfs receive -vdF backups

Replicación remota a través de SSH

Origen FreeBSD solaris, destino backups servidor tormenta

Tomar la instatánea del conjunto de datos zroot/home

solaris~ # zfs snapshot zroot/home@snap-`date +%Y%m%d`
 

Listar instantánea

solaris~ # zfs list -t snapshot zroot/home@snap-20241213
NAME                       USED  AVAIL  REFER  MOUNTPOINT
zroot/home@snap-20241213  25.4M      -  39.2G  -
solaris~ # zfs send -Rv zroot/home@snap-20241213 | ssh root@tormenta "zfs \
receive -vdF backups/solarishome/home"
 

Replicación remota incremental

Tomar la instantánea

solaris~ # zfs snapshot zroot/home@snap-`date +%Y%m%d`

Listar las intantáneas

solaris~ # zfs list -t snapshot | grep "zroot/home@snap-*"   
zroot/home@snap-20241213           185M      -  39.2G  -
zroot/home@snap-20241214            53.4M    -  39.2G  -

Replicar el flujo de datos

 solaris~ # zfs send -R -v zroot/home@snap-20241213 zroot/home@snap-20241214 | \
 ssh root@tormenta "zfs receive -vdF backups"

Exportar e importar pool backups

 zfs export backups
 zfs import backups
 

Donde están las instantáneas?

Conectarse al servidor tormenta vía SSH

solaris~ $ ssh tormenta
carlos@tormenta~ $ % ls -l /export/solarishome/home/.zfs/snapshot
total 1
drwxr-xr-x  5 root  wheel  5 May 30  2024 snap-20241213
drwxr-xr-x  5 root  wheel  5 May 30  2024 snap-20241214

carlos@tormenta~ $ % ls -l /export/usr/home/.zfs/snapshot
total 1
drwxr-xr-x  4 root  wheel  4 Jul 24 17:31 snap-20241213
drwxr-xr-x  4 root  wheel  4 Jul 24 17:31 snap-20241214

carlos@tormenta:~ % ls -l /export/usr/backup/docs/.zfs/snapshot
total 289
drwxr-xr-x  30 carlos  wheel  923 Dec 12 08:19 snap-20241212
drwxr-xr-x  30 carlos  wheel  926 Dec 13 07:34 snap-20241214

carlos@tormenta:~ % zfs list -t snapshot -o name | grep "zroot/usr/backup/docs@snap-"
zroot/usr/backup/docs@snap-20241213    248K    -   7.78G  -
zroot/usr/backup/docs@snap-20241214    216K    -   7.78G  -

carlos@tormenta:~ % zfs list -t snapshot -o name | grep "zroot/usr/home@snap-"
zroot/usr/home@snap-20241213    172K    -   5.97M  -
zroot/usr/home@snap-20241214    172K    -   5.97M  -

Listar todas las instantáneas en backups

carlos@tormenta:~ % zfs list -t snapshot -r backups
NAME                                     USED  AVAIL   REFER  MOUNTPOINT
backups/solarishome/home@snap-20241213  22.5G      -   62.8G  -
backups/solarishome/home@snap-20241214     0B      -   40.4G  -
backups/usr/backup/docs@snap-20241213    276K      -   7.78G  -
backups/usr/backup/docs@snap-20241214      0B      -   7.78G  -
backups/usr/home@snap-20241213           172K      -   5.97M  -
backups/usr/home@snap-20241214             0B      -   5.97M  -

Tenga en cuenta las siguientes consideraciones al restaurar una instantánea:

. Se restaura la instantánea y el sistema de archivos.
. El sistema de archivos y todos los sistemas de archivos descendientes se desmontan.
. Los sistemas de archivos son inaccesibles mientras se restauran.
. El sistema de archivos original que e va a restaurar no debe exister mientras está siendo restaurado.
. Si existe un nombre de sistema de archivos en conflicto, puede utilizarse zfs rename para cambiar el nombre del sistema de archivos.

Restaurar instantánea de forma remota

-d Utilice la ruta completa de la instantánea enviada sin el primer elemento (sin el nombre del grupo) para determinar el nombre de la nueva instantánea.

 
ssh root@tormenta "zfs send -Rv backups/filesolaris/confsolaris@snap-20241215" \
| zfs receive -vFd zroot

full send of toshiba/filesolaris/confsolaris@snap-20241215 estimated size is 1.20M
total estimated size is 1.20M
receiving full stream of toshiba/filesolaris/confsolaris@snap-20241215 into \
zroot/filesolaris/confsolaris@snap-20241215
received 1.20M stream in 0.06 seconds (19.0M/sec)

Recuperar el flujo de datos desde tormenta (pool backups) a solaris

solaris~ # ssh root@tormenta "zfs send -Rv backups/usr/backup/docs@snap-20241216" \
zfs receive zroot/documentos

full send of toshiba/usr/backup/docs@snap-20241216 estimated size is 7.88G
send from @snap-20241216 to toshiba/usr/backup/docs@snap-20241217 estimated size is 105K
total estimated size is 7.88G
TIME   SENT   SNAPSHOT toshiba/usr/backup/docs@snap-20241216
...

 zfs list | grep "zroot/documentos"
zroot/documentos    96K   330G   96K  /zroot/documentos

Enviar el flujo de datos a un archivo

zfs send zroot/usr/home@-20241222-snap1 > usr.home.20241222.servidor

Podemos utilizar el comando receive y proporcionar el grupo en el que desea importarlo y podría ser un grupo completamente diferente, y es probablemente la principal razon por la que usarias esto para transferir un conjunto de datos a otro sistema, especifica el grupo y el conjunto de datos al que desea importarlo, si no proporciona uno simplemente creará un conjunto de datos con el nombre que se usó desde el momento en que se creo la instantánea. Ahora, usando cat nombre del archivo y canalizarlo a través de una tuberia a zfs receive, se creará un nuevo conjunto de datos llamado zroot/homeservidor

 cat usr.home.20241222.tormenta | zfs receive zroot/home.20241222.servidor

Listar el conjunto de datos
 zfs list | grep "zroot/homeservidor"
zroot/homeservidor  4.82M  264G  4.82M  /zroot/home.20241222.servidor


Otro ejemplo de envío y recepción de datos ZFS:

El comando zfs send crea una representación de flujo de una instantánea que está escrita, a la salida estándar. Puede redirigir la salida a un archivo o a un sistema diferente. El comando zfs receive crea una instantánea cuyo contenido se especifica en la secuencia que se proporciona en la entrada estándar. Si se recibe una transmisión completa, un nuevo sistema de archivos también se crea.

Creacion de los conjuntos de datos

 zfs -p create zroot/usuarios/user1
 zfs create zroot/usuarios/user2
 zfs create zroot/usuarios/user3

 zfs list -r zroot/usuarios
 NAME                   USED  AVAIL  REFER  MOUNTPOINT
 zroot/usuarios        63.5M   351G  63.2M  /zroot/usuarios
 zroot/usuarios/user1    96K   351G    96K  /zroot/usuarios/user1
 zroot/usuarios/user2    96K   351G    96K  /zroot/usuarios/user2
 zroot/usuarios/user3    96K   351G    96K  /zroot/usuarios/user3

Tomar las instantáneas

 zfs snapshot -r zroot/usuarios@hoy

Se crea una secuencia de replicación para todas las instantáneas de usuarios

 zfs send -R zroot/usuarios@hoy > /snaps/usuarios

A continuación, los sistemas de archivos originales y las instantáneas se destruyen

 zfs destroy -r zroot/usuarios
 zfs list -r zroot/usuarios
 cannot open 'zroot/usuarios': dataset does not exist

Y luego se recuperan

 zfs receive -Fd zroot < /snaps/usuarios
 zfs list -r zroot/usuarios
 NAME                   USED  AVAIL  REFER  MOUNTPOINT
 zroot/usuarios        63.6M   351G   112K  /zroot/usuarios
 zroot/usuarios/user1    96K   351G    96K  /zroot/usuarios/user1
 zroot/usuarios/user2    96K   351G    96K  /zroot/usuarios/user2
 zroot/usuarios/user3    96K   351G    96K  /zroot/usuarios/user3
FreeBSD es genial!.