Páginas

viernes, 25 de septiembre de 2026

Migrar un dominio local a casa.lan en FreeBSD usando Unbound

Montar o expandir un laboratorio en casa y quieres estructurar servicios bajo un dominio local limpio y profesional (como casa.lan), migrar desde un dominio heredado o genérico (local.com) es un proceso rápido usando FreeBSD y Unbound como servidor DNS recursivo y autoritativo.

Actualizar la configuración del sistema operativo, estructurar las zonas directas e inversas en Unbound para todos los dispositivos y asegurar la compatibilidad con servicios como NFSv4.

Requisitos previos

- Un servidor FreeBSD con IP fija (en nuestro ejemplo, 192.168.88.160). - Unbound instalado y configurado como servidor DNS principal de la red.

Actualizar /etc/rc.conf

Modificar los parámetros relacionados con el hostname, dominio y servicios que dependen de él (nfsuserd)

hostname="tormenta.casa.lan"
domain="casa.lan"
nfsuserd_flags="-domain casa.lan"

Actualizar la configuración de Unbound.

Editar el archivo /usr/local/etc/unbound/local-zone.conf

Registrar el nuevo dominio y la máquina

server:
  local-zone: "casa.lan." static
  local-data: "casa.lan. IN SOA tormenta.casa.lan. root.casa.lan. 2026092301 3600 900 1209600 3600"
  local-data: "casa.lan. IN NS tormenta.casa.lan."
  local-data: "tormenta.casa.lan. IN A 192.168.88.160"
  local-data: "tormenta. IN A 192.168.88.160"
 

Verificar la sintaxis de Unbound

  unbound-checkconf /usr/local/etc/unbound/unbound.conf
  

Aplicar los cambios y reiniciar servicios

Cambiar el hostname en caliente

  hostname tormenta.casa.lan
  
service unbound restart
service nfsuserd restart

Comprobar que la resolución DNS interna responde correctamente al nuevo dominio. Ejecutar

dig @127.0.0.1 tormenta.casa.lan  
; <<>> DiG 9.20.26 <<>> @127.0.0.1 tormenta.casa.lan
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2667
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;tormenta.casa.lan.		IN	A

;; ANSWER SECTION:
tormenta.casa.lan.	3600	IN	A	192.168.88.160

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Fri Sep 25 06:36:50 CEST 2026
;; MSG SIZE  rcvd: 62

Actualizar /etc/hosts

El sistema consulta este archivo antes que el DNS. Reemplazar cualquier mención a local.com por casa.lan

192.168.88.160    tormenta.casa.lan   tormenta

Actualizar /etc/resolv.conf

Como la IP está configurada de forma estática en rc.conf se deben actualizar manualmente los sufijos de búsqueda DNS de la propia máquina.

search casa.lan
nameserver 127.0.0.1

Revisar exportaciones NFS

Si el acceso a los recursos compartidos nombre de dominio en lugar de IP

En /etc/exports comprobar si hay alguna línea que permita el acceso explícito a *.local.com

En el cliente solaris, archivo /etc/resolv.conf

Añadir la línea casa.lan para que el sistema sepa que debe buscar automáticamente en el dominio local al escribir nombre cortos, como hacer ping a tormenta en lugar de tormenta.casa.lan

search casa.lan
nameserver 192.168.88.160

Comprobar resolución desde el cliente

ping -c 3 tormenta

Configurar resolución DNS inversa (PTR) para la red 192.168.88.0/24.

Añadir zona inversa en Unbound

El archivo /usr/local/etc/unbound/local-zone.com necesita una zona de búsqueda inversa (in-addr.arpa) para traducir direcciones IP a nombres.

server:
  # Zona directa existente
  local-zone: "casa.lan." static
  local-data: "casa.lan. IN SOA tormenta.casa.lan. root.casa.lan. 2026092301 3600 900 1209600 3600"
  local-data: "casa.lan. IN NS tormenta.casa.lan."
  local-data: "tormenta.casa.lan. IN A 192.168.88.160"
  local-data: "tormenta. IN A 192.168.88.160"

  # Zona inversa para la red 192.168.88.x (el orden de la IP va invertido en arpa)
  local-zone: "88.168.192.in-addr.arpa." static
  local-data: "88.168.192.in-addr.arpa. IN SOA tormenta.casa.lan. root.casa.lan. 2026092301 3600 900 1209600 3600"
  local-data: "88.168.192.in-addr.arpa. IN NS tormenta.casa.lan."
  local-data: "160.88.168.192.in-addr.arpa. IN PTR tormenta.casa.lan."

Validar y reiniciar Unbound

unbound-checkconf /usr/local/etc/unbound/unbound.conf
service unbound restart

Probar la resolución inversa

Utilizar el comando dig con la opción -x para consultar la IP de tu servidor desde cualquier equipo de la red (o desde el propio servidor):

dig -x 192.168.88.160 @192.168.88.160                             carlos@solaris

; <<>> DiG 9.20.26 <<>> -x 192.168.88.160 @192.168.88.160
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 21003
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;160.88.168.192.in-addr.arpa.	IN	PTR

;; ANSWER SECTION:
160.88.168.192.in-addr.arpa. 3600 IN	PTR	tormenta.casa.lan.

;; Query time: 208 msec
;; SERVER: 192.168.88.160#53(192.168.88.160) (UDP)
;; WHEN: Fri Sep 25 07:00:58 CEST 2026
;; MSG SIZE  rcvd: 87

Para añadir equipos de la red local, (clientes, impresoras, NAS, etc.), se utiliza el archivo /usr/local/etc/unbound/local-zone.conf

Por cada equipo registrado: El registro A (asocia el nombre a la IP) y el registro PTR en la zona inversa (asocia la IP al nombre), para que funcionen herramientas de red, SSH o NFS sin retrasos.

solaris.casa.lan

server:
  # --- ZONA DIRECTA (casa.lan) ---
  local-zone: "casa.lan." static
  local-data: "casa.lan. IN SOA tormenta.casa.lan. root.casa.lan. 2026092301 3600 900 1209600 3600"
  local-data: "casa.lan. IN NS tormenta.casa.lan."
  
  # Servidor principal
  local-data: "tormenta.casa.lan. IN A 192.168.88.160"
  local-data: "tormenta. IN A 192.168.88.160"

  # Nuevos equipos (Ejemplo: solaris)
  local-data: "solaris.casa.lan. IN A 192.168.88.50"
  local-data: "solaris. IN A 192.168.88.50"


  # --- ZONA INVERSA (88.168.192.in-addr.arpa) ---
  local-zone: "88.168.192.in-addr.arpa." static
  local-data: "88.168.192.in-addr.arpa. IN SOA tormenta.casa.lan. root.casa.lan. 2026092301 3600 900 1209600 3600"
  local-data: "88.168.192.in-addr.arpa. IN NS tormenta.casa.lan."
  
  # PTR para tormenta
  local-data: "160.88.168.192.in-addr.arpa. IN PTR tormenta.casa.lan."

  # PTR para solaris (el último octeto de la IP va primero en arpa)
  local-data: "50.88.168.192.in-addr.arpa. IN PTR solaris.casa.lan."
IMPORTANTE: No olvidar actualizar el número de serie del SOA (en el ejemplo 2026092301, que suele seguir el formato AAAAMMDDnn) cada vez que se modifique el archivo de zona, para que los cambios se refresquen adecuadamente. Cambiar 2026092301 por 2026092302 en la zona directa y zona inversa.

Verificar que la sintaxis sea correcta y reiniciar

unbound-checkconf /usr/local/etc/unbound/unbound.conf
service unbound restart

Comprobar que los registros (directos e inversos) funcionan y son leidos correctamente por Unbound con el nuevo número de serie

Verificar el registro A

dig @127.0.0.1 solaris.casa.lan

; <<>> DiG 9.20.26 <<>> @127.0.0.1 solaris.casa.lan
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 33984
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;solaris.casa.lan.		IN	A

;; ANSWER SECTION:
solaris.casa.lan.	3600	IN	A	192.168.88.51

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Fri Sep 25 07:17:43 CEST 2026
;; MSG SIZE  rcvd: 61

Verificar el registro PTR (IP responde a su nombre)

dig -x 192.168.88.50 @127.0.0.1

; <<>> DiG 9.20.26 <<>> -x 192.168.88.50 @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 43540
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;50.88.168.192.in-addr.arpa.	IN	PTR

;; AUTHORITY SECTION:
88.168.192.in-addr.arpa. 3600	IN	SOA	tormenta.casa.lan. root.casa.lan. 2026092401 3600 900 1209600 3600

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Fri Sep 25 07:19:26 CEST 2026
;; MSG SIZE  rcvd: 113

Guardar y reiniciar

unbound-checkconf /usr/local/etc/unbound/unbound.conf
service unbound restart

Comprobar que NFSv4 está alineado con casa.lan

Archivo /etc/rc.conf

nfsuserd_flags="-domain casa.lan"

Reiniciar servicio

service nfsuserd restart

En los clientes NFSv4 en FreeBSD archivo /etc/rc.conf

nfsuserd_enable="YES"
nfsuserd_flags="-domain casa.lan"

Reiniciar servicio

service nfsuserd restart

Montar los recursos compartidos en el cliente usando el nombre de dominio nuevo para referirte al servidor.

mount -t nfs tormenta.casa.lan:/tu/exportacion /punto/de/montaje

Agregar nuevos clientes (synology, tornado, macbook, kamikaze y raspberrypi incluidas las IPs de loopback IPv4/IPv6

server:
  # ==========================================
  # ZONA DIRECTA: casa.lan
  # ==========================================
  local-zone: "casa.lan." static
  local-data: "casa.lan. IN SOA tormenta.casa.lan. root.casa.lan. 2026092401 3600 900 1209600 3600"
  local-data: "casa.lan. IN NS tormenta.casa.lan."

  # Resoluciones locales (localhost y servidores)
  local-data: "localhost. IN A 127.0.0.1"
  local-data: "localhost. IN AAAA ::1"

  # Tus equipos
  local-data: "tormenta.casa.lan. IN A 192.168.88.160"
  local-data: "tormenta. IN A 192.168.88.160"

  local-data: "synology.casa.lan. IN A 192.168.88.247"
  local-data: "synology. IN A 192.168.88.247"

  local-data: "solaris.casa.lan. IN A 192.168.88.51"
  local-data: "solaris. IN A 192.168.88.51"

  local-data: "tornado.casa.lan. IN A 192.168.88.183"
  local-data: "tornado. IN A 192.168.88.183"

  local-data: "macbook.casa.lan. IN A 192.168.88.43"
  local-data: "macbook. IN A 192.168.88.43"

  local-data: "kamikaze.casa.lan. IN A 192.168.88.210"
  local-data: "kamikaze. IN A 192.168.88.210"

  local-data: "raspberrypi.casa.lan. IN A 192.168.88.200"
  local-data: "raspberrypi. IN A 192.168.88.200"


  # ==========================================
  # ZONA INVERSA: 88.168.192.in-addr.arpa
  # ==========================================
  local-zone: "88.168.192.in-addr.arpa." static
  local-data: "88.168.192.in-addr.arpa. IN SOA tormenta.casa.lan. root.casa.lan. 2026092401 3600 900 1209600 3600"
  local-data: "88.168.192.in-addr.arpa. IN NS tormenta.casa.lan."

  # Registros PTR (Inversos)
  local-data: "160.88.168.192.in-addr.arpa. IN PTR tormenta.casa.lan."
  local-data: "247.88.168.192.in-addr.arpa. IN PTR synology.casa.lan."
  local-data: "51.88.168.192.in-addr.arpa. IN PTR solaris.casa.lan."
  local-data: "183.88.168.192.in-addr.arpa. IN PTR tornado.casa.lan."
  local-data: "43.88.168.192.in-addr.arpa. IN PTR macbook.casa.lan."
  local-data: "210.88.168.192.in-addr.arpa. IN PTR kamikaze.casa.lan."
  local-data: "200.88.168.192.in-addr.arpa. IN PTR raspberrypi.casa.lan."
Actualizado el número de serie SOA a 2026092401 que coincide con la fecha de hoy (24)

Comprobar errores de sintaxis y reiniciar

unbound-checkconf /usr/local/etc/unbound/unbound.conf
service unbound restart

Verificar que los equipos nuevos responden en la resolución directa e inversa

Registros A

for equipo in tormenta synology solaris tornado macbook kamikaze raspberrypi; do
  echo "--- Probando $equipo.casa.lan ---"
  dig +short @127.0.0.1 $equipo.casa.lan
done

Registros PTR IPs a nombres

for ip in 160 247 51 183 43 210 200; do
  echo "--- Probando 192.168.88.$ip ---"
  dig +short -x 192.168.88.$ip @127.0.0.1
done

Qué debe ir realmente en el /etc/hosts de cada máquina?

El archivo /etc/hosts del sistema operativo solo se utiliza para la resolución local estática previa al DNS y para que la propia máquina se reconozca a sí misma (loopback y su propia IP). Solo se necesitan las líneas básicas de bucle local y la IP propia del host:

Archivo /etc/hosts del servidor tormenta

::1             localhost localhost.my.domain
127.0.0.1       localhost localhost.my.domain
192.168.88.160  tormenta.casa.lan tormenta
FreeBSD es genial!.

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.1.0
# FECHA:   2026-09-21
### END INFO

### INICIO DEL GUION

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

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

# 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".
clean_dst() {
    ssh $SSH_OPTS $host "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_prefix}-[0-9]" \
  | $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_prefix}-[0-9]' \
  | 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_prefix}-[0-9]' \
  | 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_prefix}-[0-9]" \
  | $sort -r \
  | sed 1q`
snap_last_dst=`ssh $SSH_OPTS $host "zfs list -t snapshot -o name \
  | grep '${dst_0}@${snap_prefix}-[0-9]' \
  | 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.
        if $zfs send -I "$snap_base" "$snap_today" \
           | ssh $SSH_OPTS $host "zfs receive -vuF -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.
        if $zfs send "$snap_today" \
           | ssh $SSH_OPTS $host "zfs receive -vuF -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_prefix}-[0-9]" \
      | $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_prefix}-[0-9]' \
      | 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
############ FINAL DEL SCRIPT ############

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!.

sábado, 5 de septiembre de 2026

Configuración de WiFi con IP fija en FreeBSD 14.4

Incluye script de resume (opcional) para recuperar conexión tras hibernación

Configuración básica en /etc/rc.conf

Para usar una IP fija en wlan0, añadir o modificar estas líneas:

wlans_iwm0="wlan0"
ifconfig_wlan0="WPA inet 192.168.88.51 netmask 255.255.255.0"
defaultrouter="192.168.88.1"

En FreeBSD, la forma más sencilla y moderna de obtener el firmware para tu tarjeta inalámbrica es usando la herramienta fwget. Esta utilidad detecta automáticamente el hardware de tu sistema e instala los paquetes de firmware necesarios

sudo wget

Este comando analizará el hardware, identificará qué controladores y firmwares necesita e instalará los paquetes correspondientes automáticamente.

Configuración en /boot/loader.conf

Después de instalado el firmware, para que el sistema lo cargue al arrancar, es necesario agregar las siguientes líneas al archivo /boot/loader.conf

if_iwm_load="YES"
iwm8265fw_load="YES"
legal.intel_iwm.license_ack=1

Reiniciar el sistema para que los módulos se carguen correctamente

 reboot
Notas importantes: Asegúrarse de que /etc/wpa_supplicant.conf contiene los datos de su red WiFi. cat /etc/wpa_supplicant.conf
# /etc/wpa_supplicant.conf written by wifimgr(8)
ctrl_interface=/var/run/wpa_supplicant
eapol_version=2
fast_reauth=1

network={
	ssid="MikroTik-39F9C0"
	psk=755864a8b1a4ef59e2e62590977a75f5317193637a8629dccb18c40e8638a68e
}

Al usar IP fija, no se necesita dhclient. Por tanto, desactivar background_dhclient (ponerlo a "NO" o eliminar la línea) para evitar que sobrescriba la configuración estática al despertar:

background_dhclient="NO"

¿El sistema se recupera solo tras hibernación?

En la mayoría de los casos, sí. Al despertar, FreeBSD intenta restaurar el estado de las interfaces. Si el driver (iwm) y el subsistema WiFi funcionan correctamente, la interfaz se reasociará y mantendrá la IP fija. Esto suele funcionar bien con hardware Intel y configuraciones sencillas.

Sin embargo, si la asociación falla o la interfaz queda en un estado inconsistente (por ejemplo, si el firmware no responde o hay eventos de red que disparan dhclient). Si al despertar no tiene conexión, se puede usar el script de resume que se describe a continuación.

Script opcional para recuperar la conexión (resume-wlan.sh)

Este script destruye y recrea wlan0, fuerza la asociación, asigna la IP fija y la ruta por defecto, y mata posibles procesos dhclient que puedan interferir.

Crear el script en /usr/local/sbin/resume-wlan.sh

#!/bin/sh
# Script para restaurar WiFi tras hibernación (IP fija)
# Destruye y recrea wlan0, espera asociación, asigna IP y ruta.

sleep 3

# Destruir y recrear wlan0
ifconfig wlan0 destroy 2>/dev/null
ifconfig wlan0 create wlandev iwm0 country ES
ifconfig wlan0 up

# Iniciar wpa_supplicant
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf

# Esperar asociación (hasta 30s)
i=0
while [ $i -lt 30 ]; do
    status=$(ifconfig wlan0 | grep "status:" | awk '{print $2}')
    [ "$status" = "associated" ] && break
    sleep 1
    i=$((i + 1))
done

logger "resume-wlan: status=$status tras ${i}s"

if [ "$status" != "associated" ]; then
    logger "resume-wlan: ERROR - no asociado"
    exit 1
fi

# Asignar IP fija
ifconfig wlan0 inet 192.168.88.51 netmask 255.255.255.0

# Añadir ruta por defecto
route add default 192.168.88.1

# Matar posibles dhclient que interfieran
pkill -f "dhclient.*wlan0" 2>/dev/null

# Verificar pasados 5 segundos y reañadir si es necesario
sleep 5
if ! netstat -rn | grep -q "^default.*wlan0"; then
    route add default 192.168.88.1
    logger "resume-wlan: ruta reañadida por seguridad"
fi

logger "resume-wlan: completado (IP fija y ruta activas)"

Darle permisos de ejecución:

chmod +x /usr/local/sbin/resume-wlan.sh

Configurar devd para ejecutar el script al despertar

Crea el archivo /etc/devd/resume-wlan.conf con este contenido:

notify 100 {
    match "system"      "ACPI";
    match "subsystem"   "Resume";
    action "/usr/local/sbin/resume-wlan.sh &";
};

Reiniciar devd:

service devd restart

¿Cuándo usar el script y cuándo no?

Si no se utiliza el script:

El sistema se encarga de restaurar la interfaz al despertar. Es la opción más simple y recomendable si funciona correctamente en el hardware. Si después de probar varias hibernaciones todo va bien, no es necesario el script.

Si se usa el script:

Aporta una capa extra de fiabilidad, especialmente si se ha tenido problemas de conectividad tras la hibernación. También es útil si se cambia de red o si el driver WiFi es propenso a fallar.

Recomendación: Pruebrar primero sin el script. Si la conexión se pierde ocasionalmente, activar el script.

Notas adicionales Si en algún momento se cambia a DHCP, recordar eliminar la IP fija y habilitar background_dhclient de nuevo. Hay que asegurar de que el país (country ES) coincide con la región. Puede omitirse si no es necesario.

Script si se utiliza IP dinámica (cambia a DHCP)

Vamos a adaptar el script para que funcione con DHCP en lugar de IP fija. Esto es útil si quieremos que la interfaz obtenga la IP automáticamente al despertar (por ejemplo, si cambias de red con frecuencia o el router asigna IPs dinámicamente).

#!/bin/sh
# Script para restaurar WiFi tras hibernación (DHCP)
# Destruye y recrea wlan0, espera asociación, y lanza dhclient.

sleep 3

# Destruir y recrear wlan0
ifconfig wlan0 destroy 2>/dev/null
ifconfig wlan0 create wlandev iwm0 country ES
ifconfig wlan0 up

# Iniciar wpa_supplicant
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf

# Esperar asociación (hasta 30s)
i=0
while [ $i -lt 30 ]; do
    status=$(ifconfig wlan0 | grep "status:" | awk '{print $2}')
    [ "$status" = "associated" ] && break
    sleep 1
    i=$((i + 1))
done

logger "resume-wlan: status=$status tras ${i}s"

if [ "$status" != "associated" ]; then
    logger "resume-wlan: ERROR - no asociado"
    exit 1
fi

# Obtener IP por DHCP
dhclient wlan0

# Asegurar ruta por defecto (por si dhclient no la pone)
sleep 2
if ! netstat -rn | grep -q "^default.*wlan0"; then
    # Intenta obtener el gateway de la concesión DHCP (opcional)
    # Si no funciona, puedes poner un gateway fijo o simplemente reintentar dhclient.
    logger "resume-wlan: ruta por defecto no encontrada, reintentando dhclient"
    dhclient wlan0
fi

logger "resume-wlan: completado (DHCP)"
FreeBSD es genial!.

viernes, 4 de septiembre de 2026

Conectar Surfshark VPN protocolo WireGuard FreeBSD 14.4

2. FreeBSD 14.4 Wireguard VPN Surfshark
Surfshark no ofrece port forwarding Varias fuentes coinciden (incluida la propia respuesta de Surfshark a sus usuarios): Surfshark VPN no soporta "port forwarding". Split tunneling (Bypasser de Surfshark): saca qBittorrent de la VPN para que use tu IP real directamente. Si lo que buscas es privacidad + port forwarding, la solución no está en el split tunneling ni en el jail, sino en un proveedor de VPN que sí ofrezca reenvío de puertos (por ejemplo, ProtonVPN, AirVPN, etc.)
Elegir una ubicación y descargar el archivo de configuración. Asegurarse de generar el par de claves con anticipación de modo que la clave privada se agregue al archivo de configuración.

Descargar el archivo de configuración

Archivo /etc/rc.conf

wireguard_enable="YES"
wireguard_interface="wg0"

Copiar el archivo de configuración descargado y renombrado como wg0

cp fr-par.conf /usr/local/etc/wireguard/wg0.conf

Luego ejecutar

wg-quick up wg0

comprobar dirección IP VPN

 curl ifconfig.me
 82.102.18.210
FreeBSD es genial!.

jueves, 3 de septiembre de 2026

FreeBSD 14.4 WireGuard VPN Surfshark

1. Conectar Surfshark VPN WireGuard FreeBSD 14.4
Análisis de la tabla de rutas

ifconfig

  em0: flags=8842<BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
	options=4e524bb<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,JUMBO_MTU,VLAN_HWCSUM,LRO,WOL_MAGIC,VLAN_HWFILTER,VLAN_HWTSO,RXCSUM_IPV6,TXCSUM_IPV6,HWSTATS,MEXTPG>
	ether e4:b9:7a:6b:96:cd
	media: Ethernet autoselect
	status: no carrier
	nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
lo0: flags=1008049<UP,LOOPBACK,RUNNING,MULTICAST,LOWER_UP> metric 0 mtu 16384
	options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6>
	inet 127.0.0.1 netmask 0xff000000
	inet6 ::1 prefixlen 128
	inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
	groups: lo
	nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
wlan0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
	options=0
	ether 18:56:80:f8:98:e6
	inet 192.168.88.51 netmask 0xffffff00 broadcast 192.168.88.255
	groups: wlan
	ssid MikroTik-39F9C0 channel 1 (2412 MHz 11g) bssid c4:ad:34:39:f9:c0
	regdomain FCC country US authmode WPA2/802.11i privacy ON
	deftxkey UNDEF AES-CCM 2:128-bit AES-CCM 3:128-bit
	AES-CCM ucast:128-bit txpower 30 bmiss 10 scanvalid 60 protmode CTS
	wme roaming MANUAL
	parent interface: iwm0
	media: IEEE 802.11 Wireless Ethernet OFDM/54Mbps mode 11g
	status: associated
	nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
pflog0: flags=1000141<UP,RUNNING,PROMISC,LOWER_UP> metric 0 mtu 33152
	options=0
	groups: pflog
wg0: flags=10080c1<UP,RUNNING,NOARP,MULTICAST,LOWER_UP> metric 0 mtu 1420
	options=80000<LINKSTATE>
	inet 10.14.0.2 netmask 0xffff0000
	groups: wg
	nd6 options=109<PERFORMNUD,IFDISABLED,NO_DAD>
tailscale0: flags=1008003<UP,BROADCAST,MULTICAST,LOWER_UP> metric 0 mtu 1280
	options=4080000<LINKSTATE,MEXTPG>
	groups: tun
	nd6 options=109<PERFORMNUD,IFDISABLED,NO_DAD>
	Opened by PID 1452

netstat -rn

Routing tables

Internet:
Destination        Gateway            Flags         Netif Expire
0.0.0.0/1          link#5             US              wg0
default            192.168.88.1       UGS           wlan0
10.14.0.2          link#2             UH              lo0
85.204.70.87       192.168.88.1       UGHS          wlan0
128.0.0.0/1        link#5             US              wg0
192.168.88.0/24    link#3             U             wlan0
192.168.88.51      link#2             UHS             lo0

Ruta por defecto

Ruta por defecto (default) 192.168.88.1 por wlan0 es la ruta genérica, pero no es la que se usa para la mayoría del tráfico.

Rutas VPN (Wireguard)
0.0.0.0/1 y 128.0.0.0/1 ambas apuntan a wg0

Estas dos rutas cubren todo el rango IPv4 (0.0.0.0/1 y 128.0.0.0/1 = 0.0.0.0/0).

Como son más específicas que default, todo el tráfico (excepto las excepciones) se envía por wg0, es decir, por la VPN

Excepción para el servidor VPN

85.204.70.87 va directamente por wlan0 (gateway local)

Esa IP es el endpoint de WireGuard de Surfshark. Sin esta ruta, el tráfico del túnel intetaría pasar por el propio túnel (bucle) y fallaría.

Red local

192.168.88.0/24 por wlan0. Así puedes acceder a tu router, impresoras, etc., sin pasar por la VPN.

IP propia (loopback)

192.168.88.51 y 10.14.0.2 están en lo0 como direcciones locales (para evitar enrutamiento externo).

¿Qué significa esto en la práctica?

Tu IP pública (la que ven los sitios web) es la de Surfshark, no la de tu router.

El tráfico de navegación (HTTP/HTTPS, DNS, etc.) pasa por el túnel WireGuard.

El tráfico local (a tu router, otros PCs en 192.168.88.x) no pasa por la VPN.

El servidor VPN se alcanza directamente por WiFi, sin pasar por el túnel (para evitar bucles).

Tailscale está presente pero no parece estar enrutando tráfico por defecto (quizás solo para acceso a otros nodos).

Destruir lagg0 y limpiar wlan0

sudo ifconfig lagg0 destroy
sudo ifconfig wlan0 destroy

Crear wlan0 limpio con la configuración correcta

sudo ifconfig wlan0 create wlandev iwm0 country ES
sudo ifconfig wlan0 up

Archivo /etc/wpa_supplicant

 
/etc/wpa_supplicant.conf written by wifimgr(8)
ctrl_interface=/var/run/wpa_supplicant
eapol_version=2
fast_reauth=1

network={
	ssid="MikroTik-39F9C0"
	psk=755862a5b1a4ef59e9e62590977a75f5317193637a8629dccb18c40e8638a68e
}

Iniciar wpa_supplicant (si no está corriendo)

sudo wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf

Esperar a que se asocie (opcional, pero recomendado)

sleep 5
ifconfig wlan0 | grep status

Asignar IP fija y ruta por defecto

sudo ifconfig wlan0 inet 192.168.88.51 netmask 255.255.255.0
sudo route add default 192.168.88.1

Reiniciar WireGuard para que use la nueva interfaz

sudo wg-quick down wg0 && wg-quick up wg0 # 0 sudo service wireguard restart

Archivo /etc/pf.conf

/etc/pf.conf
ext_if="wlan0"
torrent="53541"
vpn_if="wg0"
wireguard="51820"
lan_net="192.168.88.0/24"

set block-policy return
set skip on lo

scrub in on $ext_if all fragment reassemble

table <jails> persist

nat on $ext_if from <jails> to any -> ($ext_if:0)
rdr-anchor "rdr/*"

block in all

antispoof for $ext_if inet

# Salida: todo el tráfico local permitido y trackeado
pass out quick keep state

# Entrada SSH
pass in quick inet proto tcp from $lan_net to any port ssh flags S/SA keep state

# Entrada VPN WireGuard (túnel Surfshark)
pass in quick on $vpn_if all keep state
pass out quick on $vpn_if all keep state

# Entrada qBittorrent
pass in quick on $ext_if inet proto { tcp } to port $torrent

# Entrada ICMP básico (diagnóstico)
pass in quick on $ext_if inet proto { icmp } all icmp-type { echoreq, unreach }

Verificar

¿La ruta por defecto apunta a wlan0 y las rutas VPN cubren el resto?

netstat -rn | grep -E "(default|wg0|wlan0)"

¿WireGuard está activo?

wg show 
interface: wg0
  public key: pkpZvkWaVDd6DmEb3cl9DEvsnOKl9dvZVAP+KMJLZCs=
  private key: (hidden)
  listening port: 58461

peer: AsvLuvKKAGdc67aA/vHA3vb61S6YnDGx2Pd4aP4wal8=
  endpoint: 82.102.18.179:51820
  allowed ips: 0.0.0.0/0
  latest handshake: 1 minute, 14 seconds ago
  transfer: 1.39 MiB received, 1.51 MiB sent

ping -c 8.8.8.8

Mostrar la IP pública (la IP de Surfshark)

curl -s ifconfig.me
FreeBSD es genial!.