Páginas

Buscar este blog

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

lunes, 21 de septiembre de 2026

FreeBSD 14.4 Replicación Incremental ZFS Cliente Servidor - Envío 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

# cat ~/bin/zfsresp.sh

#!/bin/sh
### BEGIN INFO
# PROVIDE:
# REQUIRE:
# KEYWORD:
# NOMBRE:  Replicación Incremental ZFS cliente - servidor
# DESC:    Replica zroot/home/carlos (solaris) -> zbackup/solaris/home/carlos (tormenta)
# VERSION: 1.2.0
# FECHA:   2026-10-14
### END INFO

### INICIO DEL GUION

# Detectar fallos en cualquier punto de la tubería (zfs send | ssh).
set -o pipefail

NOMBRE="Replicacion Incremental ZFS cliente - servidor"
VERSION="1.2.0"

snap_prefix=snap
retention=30   # margen amplio por si el cliente pasa días apagado

# Patrón estricto de snapshot diario: snap-YYYYMMDD (8 dígitos).
# Excluye nombres manuales tipo snap-14.4-RELEASE-p8-... que romperían
# el orden lexicográfico y podrían colarse como base del incremental.
snap_pat="${snap_prefix}-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]"

# Rutas absolutas para 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"

# Keepalive SSH: evita "Timeout, server ... not responding".
SSH_OPTS="-o ConnectTimeout=10 -o ServerAliveInterval=30 \
          -o ServerAliveCountMax=10 -o TCPKeepAlive=yes \
          -o BatchMode=yes"

today="$snap_prefix-`$date +%Y%m%d`"
snap_today="$src_0@$today"

log=/home/carlos/cronlog

echo >> $log
echo "$NOMBRE v$VERSION" >> $log
echo "(ejecucion manual)" >> $log
$date >> $log
echo >> $log

# Comprobar conectividad SSH antes de nada.
if ! ssh $SSH_OPTS $host true 2>/dev/null; then
    echo "ERROR: no hay conexion SSH con $host. Abortando." >> $log
    echo "**********" >> $log
    exit 1
fi

# Limpia el estado del destino: desmonta y aborta receives colgados.
# Elimina el error "dataset is busy".
# Nota: se usa sh -c para evitar que csh/tcsh (shell por defecto de root
# en FreeBSD) interprete mal el "2>" como argumento en lugar de redirección.
clean_dst() {
    ssh $SSH_OPTS $host "sh -c 'zfs unmount \"$dst_0\" 2>/dev/null; zfs receive -A \"$dst_0\" 2>/dev/null; true'"
}

# --- Snapshots locales antiguas (para purga) ---
snap_old=`$zfs list -t snapshot -o name \
  | $grep "${src_0}@${snap_pat}" \
  | $sort -r \
  | $sed 1,${retention}d \
  | $sort \
  | $xargs -n 1`

# --- Snapshot base en destino remoto ---
snap_base_name=`ssh $SSH_OPTS $host "zfs list -t snapshot -o name \
  | grep '${dst_0}@${snap_pat}' \
  | sort -r \
  | sed 1q \
  | cut -d@ -f2"`
snap_base="${src_0}@$snap_base_name"

# --- Snapshots remotas antiguas (para purga) ---
snap_old_dst_0=`ssh $SSH_OPTS $host "zfs list -t snapshot -o name \
  | grep '${dst_0}@${snap_pat}' \
  | sort -r \
  | sed 1,${retention}d \
  | sort"`

# --- Snapshot de hoy en origen ---
if $zfs list -H -o name -t snapshot | $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
    # Sin -r: no hay datasets hijos.
    if ! $zfs snapshot "$snap_today" >> $log 2>&1; then
        echo "ERROR: no se pudo crear la instantánea $snap_today." >> $log
        echo "**********" >> $log
        exit 1
    fi
fi

echo >> $log

# --- Comparar la PUNTA de origen y destino ---
# No basta con "existe hoy en destino": puede existir la de hoy pero
# faltar intermedias si hubo un apagado prolongado. Comparamos extremos.
snap_last_src=`$zfs list -t snapshot -o name \
  | $grep "${src_0}@${snap_pat}" \
  | $sort -r \
  | sed 1q`
snap_last_dst=`ssh $SSH_OPTS $host "zfs list -t snapshot -o name \
  | grep '${dst_0}@${snap_pat}' \
  | sort -r \
  | sed 1q"`

src_suffix=`echo "$snap_last_src" | $cut -d@ -f2`
dst_suffix=`echo "$snap_last_dst" | $cut -d@ -f2`

if [ -n "$src_suffix" ] && [ "$src_suffix" = "$dst_suffix" ]; then
    echo "Origen y destino ya sincronizados en @$src_suffix. Nada que enviar." >> $log
else
    echo "Destino desfasado (@${dst_suffix:-vacio} vs @${src_suffix:-vacio}). Replicando..." >> $log

    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 (-I)..." >> $log
        clean_dst
        # Sin -R (no hay hijos). -I envía TODOS los snapshots intermedios
        # entre base y hoy, rellenando huecos de días apagado.
        # Sin -F: no queremos que ZFS destruya snapshots del destino
        # silenciosamente si algo se ha desincronizado. Mejor fallar y
        # enterarnos por el log.
        if $zfs send -I "$snap_base" "$snap_today" \
           | ssh $SSH_OPTS $host "zfs receive -vu -x mountpoint '$dst_0'"
        then
            echo "Replicacion completada." >> $log
        else
            echo "ERROR: fallo la replicacion incremental." >> $log
            echo "**********" >> $log
            exit 1
        fi
    else
        echo "No se encontró snapshot común entre origen y destino." >> $log
        echo "Realizando envío completo (puede tardar)..." >> $log
        clean_dst
        # Sin -R. Sin -F (mismo razonamiento que arriba).
        if $zfs send "$snap_today" \
           | ssh $SSH_OPTS $host "zfs receive -vu -x mountpoint '$dst_0'"
        then
            echo "Envío completo finalizado." >> $log
        else
            echo "ERROR: fallo el envio completo." >> $log
            echo "**********" >> $log
            exit 1
        fi
    fi
fi

echo >> $log

# --- Purga local ---
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_pat}" \
      | $sort -r \
      | $sed 1,${retention}d \
      | $sort \
      | $xargs -n 1 $zfs destroy >> $log 2>&1
else
    echo "No se pudo encontrar ninguna instantánea local para destruir." >> $log
fi

echo >> $log

# --- Purga remota ---
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 $SSH_OPTS $host "zfs list -t snapshot -o name \
      | grep '${dst_0}@${snap_pat}' \
      | sort -r \
      | sed 1,${retention}d \
      | sort \
      | xargs -n 1 zfs destroy" >> $log 2>&1
else
    echo "No se pudo encontrar ninguna instantánea remota para destruir." >> $log
fi

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

exit 0

Lanzar el script

cd /home/carlos/bin
./zfsrep.sh 
receiving incremental stream of zroot/home/carlos@snap-20260921 into zbackup/solaris/home/carlos@snap-20260921
received 186M stream in 101.31 seconds (1.84M/sec)

Verificar que todo llegó bien

En tormenta, comprobar que están todas las snapshots intermedias:

ssh root@192.168.88.160 "zfs list -t snapshot -o name -r zbackup/solaris/home/carlos"
NAME
...
zbackup/solaris/home/carlos@snap-20260811
zbackup/solaris/home/carlos@snap-20260812
zbackup/solaris/home/carlos@snap-20260813
zbackup/solaris/home/carlos@snap-20260814
zbackup/solaris/home/carlos@snap-20260815
zbackup/solaris/home/carlos@snap-20260916
zbackup/solaris/home/carlos@snap-20260918
zbackup/solaris/home/carlos@snap-20260919
zbackup/solaris/home/carlos@snap-20260920
zbackup/solaris/home/carlos@snap-20260921

Si se lanza el script zfsrep.sh seguido. Debe decir:

Origen y destino ya sincronizados en @snap-20260920. Nada que enviar.

Si dice eso, el script es idempotente

Revisar archivo /home/carlos/cronlog

cat ~/cronlog

**********

zfsrep.sh (ejecucion manual)
Mon Sep 21 07:16:13 CEST 2026

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

Destino desfasado (@snap-20260920 vs @snap-20260921). Replicando...
Usando 'zroot/home/carlos@snap-20260920' como base del incremental (-I)...
Replicacion completada.

Intentando destruir instantáneas antiguas zroot/home/carlos...
No se pudo encontrar ninguna instantánea local para destruir.

Intentando destruir instantáneas antiguas zbackup/solaris/home/carlos...
No se pudo encontrar ninguna instantánea remota para destruir.
**********

Comprobar que no queda un receive pendiente

ssh root@192.168.88.160 "zfs get receive_resume_token zbackup/solaris/home/carlos"
NAME                         PROPERTY              VALUE      SOURCE
zbackup/solaris/home/carlos  receive_resume_token  -          -

Debe devolver - (sin receive pendiente).

Resumen del estado

Aspecto				   Estado
Conexión SSH con tormenta        - Correcta
Detección de desfase 		 - Correcta
Replicación incremental con -I	 - Completada
Relleno de huecos por apagados	 - Funciona
dataset is busy	                 - Resuelto por clean_dst()
Timeout SSH	                 - Resuelto por SSH_OPTS
Mensaje "Replicacion completada" - Ahora es fiable
Idempotencia (2ª ejecución)	 - Funciona
La comparación de puntas funciona (detecta que ya está al día). El script es idempotente: ejecutarlo varias veces no rompe nada ni duplica envíos. No hay receives colgados ni estados "busy" residuales. "Replicacion completada" ahora es un mensaje fiable, no un falso positivo.

Estado final del script

Escenario		     Comportamiento
Cliente apagado varios días -I rellena los snapshots intermedios
Destino ya al día 	    - Salta sin enviar nada
Destino con receive colgado - clean_dst() lo aborta antes de recibir
SSH lento o pausado         - ServerAliveInterval evita el timeout
Fallo en zfs send o ssh	    - pipefail + if lo detectan y abortan con error
Sin hijos en el dataset	    -R eliminado; operaciones simples
Retención	            - 30 snapshots, purga automática en ambos lados

Recomendaciones

Si algún vez el cliente pasa más de 30 días apagado, la base común desaparecerá y el script hará un envío completo aunque tardará más. Si se quiere evitar, debe aumentarse el valor de retention, o crear una snapshot "ancla" con zfs hold que nunca se borre.

Guardar una copia del script corregido en un sitio seguro (por ejemplo, dentro del propio pool ZFS que se replica, o en git).

Revisar el log cada cierto tiempo:

tail -50 /home/carlos/cronlog
Si algún día salta "ERROR: fallo la replicacion...", sabrás que es real y no un falso "completada".

Se puede considerar automatizarlo con cron si se quiere. El script ya tiene rutas absolutas y BatchMode=yes, así que está preparado. Solo se necesita una clave SSH sin passphrase entre carlos@solaris y root@tormenta (la tiene, porque funciona manualmente).

Servidor tormenta - zpools

carlos@tormenta:~ % zpool list
NAME      SIZE  ALLOC   FREE  CKPOINT  EXPANDSZ   FRAG    CAP  DEDUP    HEALTH  ALTROOT
zbackup  1.81T   958G   898G        -         -     0%    51%  1.00x    ONLINE  -
zroot     448G   206G   242G        -         -     8%    45%  1.00x    ONLINE  -
 
carlos@tormenta:~ % zfs list -r zbackup
NAME                          USED  AVAIL  REFER  MOUNTPOINT
zbackup                      1008G   790G  92.5M  none
zbackup/dellhome             56.6G   790G  56.6G  none
zbackup/nfsv4                36.6G   790G    96K  none
zbackup/nfsv4/docs           7.80G   790G  7.80G  none
zbackup/nfsv4/secrets        28.8G   790G  28.8G  none
zbackup/recovery              755G   790G   104K  none
zbackup/recovery/solaris      282G   790G   282G  none
zbackup/recovery/tormenta     473G   790G   473G  none
zbackup/reserved               50G   840G    96K  none
zbackup/solaris              81.4G   790G    96K  none
zbackup/solaris/home         81.4G   790G    96K  none
zbackup/solaris/home/carlos  81.4G   790G  74.3G  none
zbackup/usr                   147M   790G   104K  none
zbackup/usr/home              147M   790G   140M  none
FreeBSD es genial!.

viernes, 3 de abril de 2026

Crear Rotación de logs FileBrowser FreeBSD 14.4

https://gnulinuxcodigo.blogspot.com/2026/03/filebrowser-con-tailscale-en-freebsd.html


Rotación de logs

Para que los logs no crezcan indefinidamente, se puede configurar newsyslog en FreeBSD

Crear /etc/newsyslog.conf.d/filebrowser.conf

sudo tee /etc/newsyslog.conf.d/filebrowser.conf > /dev/null << EOF
# FileBrowser log rotation - rotates weekly or when > 10MB
/home/carlos/.config/filebrowser/filebrowser.log  carlos:carlos  640  7 10000 *  Z
EOF

Reiniciar servicio

sudo service newsyslog restart
FreeBSD es genial!.

viernes, 6 de febrero de 2026

Alternativas cuando el Router no Permite el Reenvío de Puertos

Bibliografía:

Tailscale

En la página de Tailscale "Regístrese con su proveedor de identidad", inicie sesión con una cuenta de proveedor de identidad de inicio de sesión único (SSO).

como habilitar acceso remoto a un servidor local (FreeBSD 14.3), que está detrás de un NAT (una red doméstica donde el ISP no permite abrir puertos), sin necesidad de un dominio publico.

Tailscale, un servicio P2P basado en wireguard que implementa NAT traversal para crear una red VPN segura y privada. Permite conectar dispositivos de forma directa (P2P) cuando es posible, o a través de relays (DEP servers) si el NAT es estricto. No expone puertos públicos ni requiere configuraciones complejas en routers. Tailscale es gratuito para uso personal (hasta 3 usuarios y 100 dispositivos).

Tailscale no necesita dominio público porque el acceso se realiza a través de IPs privadas de Tailscale (en el rango 100.64.0.0/10 conocidas como CGNAT) o nombre de host automáticos en tu "tailnet" (red privada de Tailscale). 

Instalación, configuración, NAT traversal y acceso a servicios locales. Asumido que tiene acceso root en el servidor FreeBSD 14.3 y que el sistema está actualizado (pkg update && pkg upgrade).

Wireguard es el protocolo subyacente, un VPN moderno, ligero y seguro que usa criptografía de curva elíptica para túneles cifrados.

Nat Travesal. Tailscale utiliza técnicas como STUN (para descubrir IPs públicas y puertos), ICE (para negociar conexiones P2P) y relays DERP (servidores de Tailscale que actúan como intermediariso si el P2P falla debido a NAT simétrico o firewalls estrictos).

Tailnet. Su red privada virtual. Todos los dispositivos autenticados en su cuenta forman parte de ella.

Servicios locales. Para acceder a un servicio en el servidor (por ejemplo web en 80), el cliente se conecta a la IP de Tailscale + puerto, como si estuvieran en la misma LAN.

Dispositivo cliente (PC, teléfono móvil, etc.) desde donde se accederá instalando Tailscale (disponible para Windows, macOS, FreeBSD, iOS, Android, Linux).

Firewall en FreeBSD. Si usa PF (Packet Filter), asegurarse de que permita tráfico saliente (por defecto lo hace). No necesita reglas entrantes especiales para Tailscale, ya que utiliza la interfaz tailscale0.

Instalar Tailscale en el servidor local (FreeBSD 14.3)

pkg install tailscale

Esto instala tailscaled (el demonio) y tailscale (la CLI). Dependencias como Wireguard se instalan automáticamente.

Habilitar el servicio para que inicie al inicio

sysrc tailscaled_enable="YES"

Iniciar el servicio

service tailscaled start

Comprobar el estado

service tailscaled status

Autenticar el servidor en tu tailnet

tailscale up

Esto genera un URL de autenticación (ej. https://login.tailscale.com/a/65831eb3ddff0). Copiar y pegar en un navegador en cualquier dispositivo. Iniciar sesión con su cuenta de Tailscale para aprobar el dispositivo. Una vez aprobado, el comando completará y el servidor se unirá a su tailnet.

Recomendación de mantenimiento


Nunca vuelva a ejecutar tailscale up sin --hostname o sin haber hecho tailscale logout antes. Uso correcto:

tailscale logout
tailscale up --hostname=tornado
tailscale status
100.115.10.68   tormenta  nombreusuario@  freebsd

Se muestra el servidor con una IP de Tailscale y estado online

ifconfig tailscale0 para la interfaz VPN.

ifconfig tailscale0

tailscale0: flags=1008043< <UP,BROADCAST,RUNNING,MULTICAST,LOWER_UP> metric 0 mtu 1280
	options=4080000< <LINKSTATE,MEXTPG>
	inet 100.115.10.68 netmask 0xffffffff broadcast 100.115.10.68
	inet6 fd7a:115c:a1e0::cd01:687 prefixlen 48
	groups: tun
	nd6 options=101 <PERFORMNUD,NO_DAD>
	Opened by PID 27732
You are about to connect the device tormenta to the nombreususario@gmail.com tailnet.

Public key 		nodekey:af18fa705771d20c28856057767cafc...

Hostname    		tormenta

Operating system	freebsd (14.3-RELEASE-p7)

Tailscale version	1.94.1

tormenta		100.115.10.68

No es necesario configurar nada extra en su router o firewall.

Exponer Servicios Locales

Por defecto, Tailscale permite acceso a cualquer puerto en la IP de Tailscale del servidor, siempre que el servicio escuche en 0.0.0.0 o la interfaz tailscale0

Ejemplo para un servidor web (por ejemplo, Nginx en el puerto 80) se configura para escuchar en 0.0.0.0:80

Archivo de configuración /usr/local/etc/nginx/nginx.conf

---
server {
        listen       8000; #poudriere
        server_name  localhost;
        root /usr/local/share/poudriere/html;

        location /data {
            alias  /usr/local/poudriere/data/logs/bulk;
            autoindex on;
        }

        location /packages {
                root /usr/local/poudriere/data;
                autoindex on;
        }

        location / {
                index index.html index.htm;
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/local/www/nginx-dist;
        }
}

# Servidor web principal en el puerto 80
server {
        listen 0.0.0.0:80; #nginx
        server_name localhost;

        root /usr/local/www/nginx;
        index index.html index.htm;

        # Paginas de error para el servidor principal
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/local/www/nginx-dist;
        }
    }

}
---

Si se quiere acceder a toda la subred local (otros dispositivos en la red 192.168.88.0/24), se tiene que habilitar subnet routes:

tailscale up --advertise-routes=192.168.88.0/24. Posteriormenta se aprueba en el 
admin console.

Seguridad adicional

Usar ACLs (Access Control Lists) en el admin console para restringir accesos (por ejemplo, solo ciertos usuarios pueden acceder al servidor).

Habilitar MagicDNS. En el admin console, ir a DNS > Enable MagicDNS. Para usar nombre como fsb143 en lugar de IPs.

Para headless (sin interacción): Usar pre-auth keys

Acceso desde Internet (lado cliente)

Instalar Tailscale en el dispositivo
Ejecutar tailscale up y autenticar con el mismo URL/login utilizado para el servidor 
(unirse al mismo tailnet)

Una vez conectado acceder al servidor

Web:
abrir http://100.115.10.68:80 (reemplazar con la IP de Tailscale del servidor 
obtenida de tailscale status). Bienvenido al servidor FreeBSD nginx! Si ve esta pagina, el servidor web nginx se instalo correctamente y funciona
correctamente. Se requiere configuracion adicional. Para obtener documentacion y soporte en linea, consulte nginx.org. Gracias por usar nginx.
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!.

viernes, 11 de abril de 2025

Configuración de Zerotier Acceso a Servidor LAN vía SSH FreeBSD 14.2

Configuración para conectar clientes a un servidor FreeBSD via Zerotier y SSH

Instalación y configuración de Zerotier en el servidor FreeBSD

Instalación y puesta en marcha de Zerotier

 pkg install net/zerotier
 sysrc zerotier_enable=YES
 service zerotier start

Unirse a la Red Zerotier (192.168.192.0-24):

 zerotier-cli join <Network ID>

Reemplaza <Network ID> con el ID de tu red Zerotier (puedes verlo en Zerotier central).

Autorizar el servidor en Zerotier, aparecerá en la lista de miembros con su ID

Asigna una IP fija al servidor en la red Zerotier (por ej: 192.168.192.133).

Configuración del cortafuegos PF en el servidor FreeBSD

Habilitar IP Forwarding (Reenvío de paquetes):

echo "net.inet.ip.forwarding=1" >> /etc/sysctl.conf

Aplicar los cambios:

sysctl net.inet.ip.forwarding=1
net.inet.ip.forwarding: 1

Reglas PF (/etc/pf.conf)

# Macros
wan_if = "re0"               # Interfaz conectada al MikroTik (LAN)
lan_net = "192.168.88.0/24"  # Red local
zt_if = "zt1ocu1pr8e2sac"    # Interfaz ZeroTier
zt_net = "192.168.192.0/24"  # Red ZeroTier

# Opciones generales
set block-policy drop
set skip on { lo0, $zt_if }  # ZeroTier no necesita filtrado estricto
scrub in all fragment reassemble

# Reglas predeterminadas
block all
pass out quick keep state

# Permitir tráfico entre redes LAN y ZeroTier
pass quick from $lan_net to $zt_net
pass quick from $zt_net to $lan_net

# Permitir servicios desde la LAN (Samba, NFSv4, SSH)
pass in on $wan_if proto { tcp, udp } from $lan_net to any port { 139, \
445, 111, 2049, 22 }

# Permitir SSH desde ZeroTier (clientes externos)
pass in on $zt_if proto tcp from $zt_net to ($wan_if) port 22

# Reglas para ZeroTier (puerto UDP 9993 y comunicación entre peers)
pass in on $zt_if proto udp from any to any port 9993

# Permitir ICMP (pings y mensajes de error)
pass in inet proto icmp all icmp-type { echoreq, unreach }

Configuración de clientes Zerotier:

Instalar Zerotier en los dispositivos cliente

Unirlos a la red Zerotier

zerotier-cli join <Network ID> o mediante la app

Autoriza los clientes en Zerotier y asígnales IPs fijas si es necesario

Acceso SSH al servidor

Conectarse desde un cliente usando la IP de Zerotier del servidor

 ssh usuario@192.168.192.133

Ruta estática entre la red local (192.168.88.0/24) y Zerotier (192.168.192.0/24)

¿Por qué es necesaria?

Los clientes en Zerotier (192.168.192.0/24) no saben como llegar a la red local del servidor (192.168.88.0/24) por defecto.

La ruta estática indica a Zerotier que el servidor actúa como puerta de enlace para la red local.

Configuración en Zerotier Central:

Ir a su red en Zerotier
En Advanced > Managed Routes,
añadir:

 - Destination: 192.168.88.0/24
 - Via: 192.168.192.133 (IP Zerotier del servidor)

Explicación Técnica

Zerotier como VPN Layer 2:

Crea una red virtual donde todos los dispositivos aparecen como si estuvieran en la misma LAN, evitando la necesidad de port forwarding

Ruta estática:

Dirige el tráfico desde Zerotier hacia la red local a través del servidor, que actúa como router.

Regla de Cortafuegos en Mikrotik

Agregar una regla que permita el tráfico SSH desde la red ZeroTier hacia el servidor FreeBSD

# En Mikrotik (RouterOS CLI)

/ip firewall filter add chaind=filter action=accept protocol=tcp \
dst-port=22 src-address=192.168.192.0/24 comment="Permitir SSH desde ZeroTier"

El servidor usa la IP de ZeroTier para SSH, asegúrese de que SSH está configurado para escuchar en esa IP

# En /etc/ssh/sshd_config
ListenAddress 192.168.192.133

Reiniciar SSH después

service sshd restart

Verificaión:

Desde un cliente Zerotier, por ej: desactivo la conexión wifi de mi teléfono móvil y activo los datos para conectarme desde una red externa:

 ssh carlos@192.168.192.133


Captura de tráfico

 tcpdump -i zt1ocu1pr8e2sac

Optimizar ZeroTier:

- En /usr/local/etc/zerotier-one/zerotier-one.conf, configura allowManaged y allowGlobal para controlar el acceso a rutas:

   {
     "settings": {
       "allowManaged": true,
       "allowGlobal": false
     }
   }

Diagrama de Flujo

Esta configuración garantiza que solo los servicios necesarios estén expuestos, y la comunicación entre redes se realice de manera segura y controlada.

FreeBSD es genial!.