Compré una caja externa con dos discos de 4 TB. La idea era simple: un espejo, para que si un disco muere el otro siga teniendo todo. Lo armé desde Utilidad de Discos, le copié unos archivos de prueba, y me quedé mirando la pantalla con una duda incómoda.

¿Está bien hecho? ¿Y cómo me voy a enterar el día que falle algo?

Esta es la bitácora de responder esas dos preguntas. Terminó con el RAID destruido y vuelto a crear, y con tres hallazgos que no aparecen en los tutoriales de “cómo hacer RAID en Mac en 5 pasos”.

El laboratorio

ComponenteDetalle
EquipoMac mini (Mac16,10), chip Apple M4
SistemamacOS 26.6.2 (build 25G83)
Caja externaPuente USB JMicron JMS56x (0x152D:0xA580)
ConexiónUSB, enlace negociado a 5 Gb/s (SuperSpeed), modo UAS
Disco ASeagate ST4000DM004, 4 TB — grabación SMR
Disco BSeagate ST4000DM000, 4 TB — grabación CMR
smartmontools7.5 (Darwin 25.6.0 arm64), instalado con Homebrew

Dos cosas de esa tabla importan más de lo que parece y reaparecen más adelante: el chipset del puente y el hecho de que los discos no son equivalentes. Casi todo sale de un comando:

ioreg -c IOUSBHostDevice -r -l | grep -iE "USB Product Name|USB Vendor Name|idProduct|idVendor|Device Speed"

De ahí salen el 0x152D:0xA580 y el Device Speed = 3, que en la escala de macOS significa SuperSpeed: 5 Gb/s. En la misma salida, un bInterfaceProtocol de 98 (0x62) confirma modo UAS.

Curiosamente, system_profiler SPUSBDataType no devolvía nada en mi equipo. ioreg sí.

Lo que un espejo no hace

Antes de seguir, la aclaración que ahorra disgustos: un RAID 1 no es un backup.

El espejo te protege contra el fallo físico de un disco. No te protege contra:

  • Borrar una carpeta por error — se borra en los dos discos, instantáneamente
  • Ransomware — cifra los dos discos
  • Corrupción de archivos — se replica fielmente
  • Que se te caiga la caja por las escaleras

Un backup es una copia separada en el tiempo y en el espacio. El espejo es redundancia. Ambas sirven; ninguna reemplaza a la otra.

Y el chequeo trivial que conviene hacer de entrada: dos discos de 4 TB en espejo dan 4 TB, no 8. Si tu sistema reporta la suma, no tienes un espejo — tienes un stripe o un concat, que es lo contrario: más espacio, cero protección.

Punto de partida

Utilidad de Discos me mostraba esto:

  • Volumen Caja_Raid, formato APFS, 4 TB
  • “Estado del grupo mirror (RAID 1): Conectado”
  • Los dos discos listados como “Conectado”

Los 4 TB confirmaban que era espejo y no stripe. Aparentemente todo bien. Pero la interfaz gráfica dice poco, así que me fui a la terminal.

Paso 1: verificar de verdad

macOS trae diskutil, la versión de línea de comandos de Utilidad de Discos. Con muchas más opciones y mucha más información.

diskutil appleRAID list

Esta es la salida del RAID original, el que creé desde la interfaz gráfica. Guárdala mentalmente, porque más adelante voy a compararla con la de un RAID recreado a mano y la diferencia va a ser una sola línea:

AppleRAID sets (1 found)
===============================================================================
Name:                 Caja_Raid
Unique ID:            9B4E10E4-00AE-4B02-B7CC-0ED0EBA41580
Type:                 Mirror
Status:               Online
Size:                 4.0 TB (4000443039744 Bytes)
Rebuild:              automatic
Device Node:          disk8
-------------------------------------------------------------------------------
#  DevNode   UUID                                  Status     Size
-------------------------------------------------------------------------------
0  disk4s2   848718FB-9213-4868-BE55-370F0188DDF8  Online     4000443039744
1  disk5s2   49798402-AEC3-466D-A338-5E8FABAE6733  Online     4000443039744
===============================================================================

Cómo se lee esto:

  • Type: Mirror — es un espejo. Confirmado.
  • Status: Online — el conjunto está sano. Los estados de fallo que verás son Degraded (falta un disco) y Failed.
  • Rebuild: automatic — si reinserto un disco caído, se reconstruye solo. Guarda este dato, que reaparece al final del artículo de forma incómoda.
  • Los dos miembros en Online — ambos discos respondiendo.

La cadena de cuatro capas

diskutil list revela algo que al principio marea: entre los discos físicos y el volumen que ves en el Finder hay cuatro niveles de abstracción.

   ┌──────────────────┐   ┌──────────────────┐
   │  Disco físico A  │   │  Disco físico B  │
   │  ST4000DM004     │   │  ST4000DM000     │
   └────────┬─────────┘   └────────┬─────────┘
            └──────────┬───────────┘
                       ▼
            ┌─────────────────────┐
            │   Set AppleRAID     │  disco virtual
            │   (Mirror, 4 TB)    │  no existe físicamente
            └──────────┬──────────┘
                       ▼
            ┌─────────────────────┐
            │  Contenedor APFS    │
            └──────────┬──────────┘
                       ▼
            ┌─────────────────────┐
            │  Volumen Caja_Raid  │  /Volumes/Caja_Raid
            └─────────────────────┘

Las dos capas del medio son las que confunden. El set AppleRAID es un disco que macOS inventa: no existe físicamente, es la abstracción que hace que los dos discos se vean como uno. Y el contenedor APFS existe porque APFS no formatea discos directamente — crea un contenedor que puede alojar varios volúmenes compartiendo espacio.

Te habrás fijado en que el diagrama no lleva identificadores de disco. Es a propósito, y conviene explicar por qué antes de que veas números distintos en cada salida de este artículo y pienses que hay una errata.

macOS no asigna identificadores fijos. Los discos físicos reciben el suyo según el orden en que el sistema los descubre, que depende del puerto, del hub y de cuándo arranca cada dispositivo. Los discos sintetizados —el set AppleRAID y el contenedor APFS, que no existen como hardware— son aún menos predecibles: la documentación de diskutil describe ese número como arbitrario y recomienda tratarlo como una referencia opaca, no como algo deducible. Borrar volúmenes deja huecos, y los huecos se reutilizan.

La mejor prueba la tengo de dos sesiones consecutivas, sin tocar nada entre medias: ni recrear el conjunto, ni cambiar de puerto, ni mover un cable. Solo apagar y encender la caja.

Sesión ASesión B
Set AppleRAIDdisk4disk6
Miembro 0disk6s2disk4s2
Miembro 1disk7s2disk5s2
UUID del setB560FCE0…B560FCE0…
UUID miembro 072478A7A…72478A7A…
UUID miembro 1F3D61ED5…F3D61ED5…

Se intercambiaron por completo: lo que era el conjunto pasó a ser un miembro y al revés. Los tres UUID, idénticos. Ni siquiera se cumple la intuición de que el disco virtual lleve número mayor que los físicos.

Así que a lo largo del artículo verás identificadores distintos para las mismas cosas. No es un descuido: es cómo funciona el sistema.

¿Y por qué no usar el UUID, como en Linux?

Es la primera objeción de cualquiera que venga de RHEL o Debian, donde lo correcto es anclar todo por UUID y olvidarse de /dev/sdX. La respuesta corta es que en macOS puedes hacerlo a medias, y conviene saber dónde está el límite.

Lo que sí funciona: diskutil acepta el UUID donde espera un dispositivo.

diskutil info B560FCE0-592B-41B5-BA35-1E6AB43EB552

Eso devuelve el conjunto completo, con su Status, sin importar qué número le haya tocado ese día. Y funciona igual con el UUID de un miembro. De hecho cada capa de la cadena tiene su propio UUID estable, mientras el diskN de todas ellas baila:

Volumen Caja_Raid    217F34BB-9698-450E-87CC-3AE964B9DB37
Set AppleRAID        B560FCE0-592B-41B5-BA35-1E6AB43EB552
├─ Miembro 0         72478A7A-BE6C-4E92-AD59-CA9A4DBF72F5
└─ Miembro 1         F3D61ED5-FC88-404B-8B74-025410C9BFF9

Ahora los límites, que son tres y ninguno es menor.

No existe /dev/disk/by-uuid/ ni /dev/disk/by-id/. En Linux tienes rutas persistentes que le pasas a cualquier comando o a /etc/fstab. En macOS el UUID es un argumento que diskutil entiende, no una ruta del sistema de archivos. Fuera de diskutil, no te sirve de nada.

Los comandos destructivos no lo admiten. eraseDisk y appleRAID create operan sobre discos crudos: todavía no hay conjunto, así que no hay UUID de conjunto, y un disco sin formato no tiene UUID de sistema de archivos al que apuntar. Es un problema de huevo y gallina, y deja una ironía incómoda: los dos comandos donde equivocarse cuesta caro son justo los que te obligan a escribir disk6.

Tampoco hay identidad de hardware. En Linux, /dev/disk/by-id/ te da un nombre construido con el número de serie del disco. Mi caja no expone ninguno:

"Device Characteristics" = {"Serial Number"="0ba028e404a86022", "Product Name"="APPLE SSD AP0256Z", ...}
"Device Characteristics" = {"Vendor Name"="ST4000DM", "Product Name"="004-2CV104", ...}
"Device Characteristics" = {"Vendor Name"="ST4000DM", "Product Name"="000-1F2168", ...}

El SSD interno reporta serial. Los dos Seagate detrás del puente USB, no. Es la misma capa que bloquea el SMART. Lo único que los distingue es el modelo, que por suerte es distinto en mi caso — y es exactamente lo que uso más adelante antes de borrar nada.

Una trampa al copiar el UUID. diskutil info de un miembro devuelve dos identificadores distintos:

   Disk / Partition UUID:     036BEE9D-F448-42AC-9D8D-ACCF1A6ABFEA
   RAID Slice UUID:           72478A7A-BE6C-4E92-AD59-CA9A4DBF72F5

El primero es el de la partición GPT; el segundo es el que usa AppleRAID. No son intercambiables, y el que necesitas es el segundo.

Paso 2: lo que la interfaz gráfica no te dice

Aquí empezaron las sorpresas. Tres cosas que Utilidad de Discos no menciona.

AppleRAID no te avisa de nada

Si un disco falla, macOS no muestra ninguna notificación. No hay correo, no hay alerta, no hay icono rojo. El volumen sigue montado y funcionando con normalidad, en modo degradado, y tú sigues trabajando feliz sobre un espejo que ya no es espejo.

Si en ese estado falla el segundo disco, pierdes todo. Y nunca supiste que estabas en riesgo.

Hallazgo 1 AppleRAID no genera ninguna alerta cuando un espejo entra en estado degradado. El volumen sigue funcionando con normalidad y no hay indicación visual de que la redundancia se perdió.

Esto es la razón de la mitad de esta bitácora.

El SMART no está disponible (y la causa no es la que parece)

SMART es lo que te deja detectar un disco que se está muriendo antes de que muera: horas de uso, sectores defectuosos, temperatura, errores de lectura. Utilidad de Discos decía Estado S.M.A.R.T.: Incompatible.

La explicación que dan casi todos los foros es que el puente USB no soporta SAT (SCSI/ATA Translation, el mecanismo que pasa comandos SMART a través de USB). Fui a comprobarlo con smartmontools:

brew install smartmontools
sudo smartctl -a -d sat /dev/disk6
/dev/disk6: Type 'sat+...': Not a device of type 'scsi'

Aquí es donde vale la pena no conformarse con la primera hipótesis. Probé cinco caminos distintos. Todos fallaron, pero no todos de la misma forma, y esa diferencia cambia la conclusión:

sudo smartctl --scan                          # solo lista el NVMe interno
sudo smartctl -a /dev/rdisk6                  # Operation not supported by device
sudo smartctl -a -d auto /dev/rdisk6          # Operation not supported by device
sudo smartctl -a -d sat,auto /dev/rdisk6      # Not a device of type 'scsi'
sudo smartctl -a -d jms56x,1 /dev/disk6       # Not a device of type 'scsi'

El último es el relevante. jms56x es el driver que smartmontools incluye específicamente para el puente JMicron de mi caja, el mismo que identifiqué con ioreg, y falla igual que los genéricos.

Fíjate en el patrón: sin -d, el error es al abrir el dispositivo, antes de enviar comando alguno. Con cualquier -d, macOS no expone el disco como objeto SCSI, que es el requisito previo para intentar SAT. Y --scan no ve ningún disco USB, solo el NVMe interno.

Es decir: el bloqueo está en la capa de dispositivo de macOS, no en el puente USB. smartctl ni siquiera llega a hablar con la caja, así que la capacidad SAT del JMS56x queda sin probar, no descartada. Con este mismo hardware en un Linux, es probable que funcionara. No afirmo que pase en toda combinación de macOS y hardware; afirmo que en este equipo, con cinco variantes de driver, smartctl no logró abrir el dispositivo ni una sola vez.

Quien mejor confirma esto, curiosamente, es el fabricante de la solución comercial: BinaryFruit, que vende DriveDx, lo dice en su propia documentación — aunque muchas cajas USB modernas sí envían datos SMART por SAT, macOS no soporta esa función de fábrica.

Opciones reales, ninguna gratis:

  1. Aceptarlo. Vives sin monitoreo de salud y confías en el chequeo del RAID.
  2. DriveDx (comercial). No trae un driver propio: empaqueta y firma el SAT SMART Driver, un kext de código abierto de terceros. Funciona —el fabricante confirma compatibilidad con macOS 26 y con Apple Silicon desde la versión 1.11— pero tiene un precio que nadie menciona: en un Mac con chip Apple, cargar un kext de terceros exige poner el arranque seguro en Seguridad Reducida. Para ver el SMART de dos discos de laboratorio, bajas la postura de seguridad de todo el equipo. De forma permanente.
  3. Conectar los discos por SATA a otra máquina de vez en cuando para revisarlos.

Elegí la 1 por ahora. La 2 la descarté por lo del arranque seguro, no por el precio de la licencia. Pero conviene entender qué significa quedarse sin SMART: la primera señal de que un disco se está muriendo es que ya se murió. El espejo te salva esa vez. No te avisa antes.

Los propietarios venían desactivados

diskutil info mostraba Owners: Disabled. Traducción: macOS estaba ignorando los permisos de archivo en ese volumen — cualquier usuario puede leer y escribir todo, sin importar a quién pertenezca cada archivo.

Es el comportamiento por defecto en discos externos, y para una memoria USB que pasa de mano en mano tiene sentido. Para un disco donde voy a guardar proyectos de trabajo, no.

sudo diskutil enableOwnership /Volumes/Caja_Raid
diskutil info /Volumes/Caja_Raid | grep -i owners

Debe responder Enabled. Y comprueba en el volumen, no en el conjunto: si haces diskutil info sobre el set, te devolverá Owners: Disabled siempre, porque ese objeto es un disco virtual sin montar y el dato ahí no significa nada. Casi me lleva a “corregir” algo que estaba bien.

Conviene verificarlo de nuevo después de recrear el RAID o de cambios importantes en el volumen: más adelante en esta bitácora el ajuste se perdió al recrear el conjunto, y no había ninguna señal de ello.

Paso 3: el script de monitoreo

Como macOS no avisa, hay que construir el aviso. La idea: un script que revise el estado del RAID y mande una notificación si algo está mal.

Primera versión, en ~/bin/check-raid.sh:

#!/bin/bash
VOL="/Volumes/Caja_Raid"

[ -d "$VOL" ] || exit 0
sleep 10

if diskutil appleRAID list | grep -qiE "degraded|failed|rebuild|missing|offline"; then
  osascript -e 'display notification "Caja_Raid en estado anormal" with title "RAID degradado"'
  logger -t raidcheck "RAID anormal: $(diskutil appleRAID list)"
else
  logger -t raidcheck "RAID OK"
fi

La lógica es directa: si el estado contiene alguna palabra de fallo, alerta. El sleep 10 le da tiempo al set a ensamblarse antes de leer su estado — si consultas de inmediato tras conectar la caja, puedes leer un estado transitorio y alarmarte por nada.

osascript ejecuta AppleScript desde bash, y es la forma de lanzar una notificación nativa de macOS.

No olvides hacerlo ejecutable, o el LaunchAgent del siguiente paso cargará bien y fallará en silencio:

chmod +x ~/bin/check-raid.sh

Lo ejecuté. Y me alertó.

Con el RAID perfectamente sano.

El bug: Rebuild: automatic

Volví a leer la salida de diskutil appleRAID list línea por línea:

Status:               Online
Rebuild:              automatic

Ahí está. Mi patrón buscaba la palabra rebuild, y Rebuild: automatic es una línea de configuración que aparece siempre, en todo momento, esté sano o no el RAID. No es un estado de fallo. Es la etiqueta que dice cómo se comportará el sistema si llegara a haber uno.

Mi script iba a gritar “¡fallo!” cada vez que conectara la caja. Para siempre.

Esto es peor que no tener monitoreo. Un sistema de alertas que da falsos positivos constantes entrena a su dueño a ignorarlo, y el día que la alerta es real, la descartas por reflejo.

La corrección: filtrar primero las líneas que sí describen estado, y cambiar rebuild por rebuilding (que es el estado activo durante una reconstrucción real).

#!/bin/bash
VOL="/Volumes/Caja_Raid"
LOG="$HOME/raidcheck.log"

[ -d "$VOL" ] || exit 0
sleep 10

SALIDA=$(diskutil appleRAID list)

# Solo la línea "Status:" y las filas de los miembros
PROBLEMA=$(echo "$SALIDA" | grep -E "^Status:|^[0-9]+ +disk" | grep -icE "degraded|failed|missing|offline|rebuilding")

if [ "$PROBLEMA" -gt 0 ]; then
  osascript -e 'display notification "Caja_Raid en estado anormal" with title "RAID degradado" sound name "Basso"'
  echo "$(date '+%F %T') RAID ANORMAL" >> "$LOG"
  echo "$SALIDA" >> "$LOG"
else
  echo "$(date '+%F %T') RAID OK" >> "$LOG"
fi

También cambié logger por escribir a un archivo propio. logger manda al unified log de macOS, que es potente pero incómodo de consultar — necesitas predicados y banderas específicas, y por defecto oculta los mensajes de nivel informativo. Un archivo de texto plano se lee con tail y ya.

Con esto me di por satisfecho. Spoiler: me faltaban dos pasos para descubrir que esta versión también mentía, y peor que la primera.

Paso 4: ¿cuándo ejecutar el chequeo?

Mi primer instinto fue programarlo a diario. Pero la caja no está encendida todos los días — la conecto cuando la necesito.

Un chequeo diario habría intentado revisar un RAID ausente y generado ruido. Lo correcto es disparar el chequeo cuando se conecta el disco.

macOS tiene exactamente eso. Se llama StartOnMount.

Qué es un LaunchAgent

macOS usa launchd para gestionar procesos automáticos: servicios, tareas programadas, cosas que se ejecutan al iniciar sesión. Es el equivalente moderno de cron en Linux, y mucho más flexible.

Le indicas qué ejecutar y cuándo con un archivo .plist (un XML de configuración). Si va en ~/Library/LaunchAgents/, se llama LaunchAgent y corre con tu usuario.

Mi archivo ~/Library/LaunchAgents/com.mauro.raidcheck.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.mauro.raidcheck</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Users/TU_USUARIO/bin/check-raid.sh</string>
    </array>
    <key>StartOnMount</key>
    <true/>
    <key>RunAtLoad</key>
    <true/>
    <key>StandardErrorPath</key>
    <string>/tmp/raidcheck.err</string>
</dict>
</plist>

Las tres claves que importan:

  • ProgramArguments — qué ejecutar. Debe ser ruta absoluta: launchd no expande ~.
  • StartOnMount — se dispara cada vez que se monta cualquier volumen. Por eso el script comprueba primero si el suyo está presente.
  • RunAtLoad — también al iniciar sesión, por si la caja ya estaba conectada.

Validar y cargar:

plutil -lint ~/Library/LaunchAgents/com.mauro.raidcheck.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.mauro.raidcheck.plist
launchctl list | grep raidcheck

plutil -lint verifica que el XML esté bien formado, bootstrap registra el agente y launchctl list muestra PID y último código de salida.

Ojo con ese código, que es fácil leerlo mal: un 0 significa que el script terminó sin errores, no que el RAID esté sano. Cuando probé el script contra cinco escenarios, los cinco devolvieron 0, incluidos los tres en los que disparaba una alerta.

Y un detalle que puede dejarte el monitoreo en decorativo: un display notification lanzado desde launchd se atribuye a Script Editor. Si ese permiso está desactivado en Ajustes → Notificaciones, el script corre perfecto, escribe en el log, y la notificación no aparece nunca.

Nota para Apple Silicon: intenté poner el script en /usr/local/bin y ese directorio no existe. En Macs con chip Apple, Homebrew usa /opt/homebrew y /usr/local queda vacío. Terminé usando ~/bin, que además evita necesitar sudo.

Paso 5: romper el RAID a propósito

Aquí está la parte que casi nadie hace.

Yo tenía un script que decía “RAID OK”. Pero nunca lo había visto detectar un fallo real. Y acababa de descubrir, por casualidad, que el mismo script tenía un bug que lo hacía mentir en la dirección contraria. Confiar en él sin probarlo habría sido ingenuo.

La única prueba válida es provocar el fallo. Con datos de prueba, el costo es cero.

El procedimiento:

  1. Desmontar y apagar la caja. El número que va aquí es el del contenedor APFS, no el del set ni el de los discos físicos — confírmalo con diskutil list antes de ejecutarlo, porque en tu equipo será otro:
    diskutil unmountDisk /dev/disk9
    
  2. Sacar físicamente uno de los dos discos.
  3. Encender la caja con un solo disco.
  4. Ver qué pasa.

Y pasó lo que debía:

Status:               Degraded
-------------------------------------------------------------------------------
#  DevNode   UUID                                  Status     Size
-------------------------------------------------------------------------------
-  -none-    848718FB-9213-4868-BE55-370F0188DDF8  Missing/Damaged
1  disk4s2   49798402-AEC3-466D-A338-5E8FABAE6733  Online     4000443039744

Status: Degraded. Un miembro como Missing/Damaged. El script escribió RAID ANORMAL en el log con el volcado completo del estado. La notificación apareció.

Y lo más importante: el volumen siguió montado y accesible con un solo disco. Los archivos ahí, funcionando. Eso es exactamente lo que un espejo debe hacer.

La reintegración

Apagué, reinserté el disco, encendí:

0  disk4s2   848718FB-...  0% (Rebuilding)  4000443039744
1  disk5s2   49798402-...  Online           4000443039744

Reconstrucción automática, sin comandos manuales. Eso era el Rebuild: automatic de la primera salida haciendo su trabajo.

Pero la reconstrucción es lenta. Muy lenta. AppleRAID copia a nivel de bloque: replica los 4 TB completos, sin importar cuántos datos hubiera dentro.

Es tentador culpar al USB, y sería falso. El enlace negocia a 5 Gb/s, que dan unos 400 MB/s reales. Un disco mecánico de 5400 RPM entrega como mucho 150-190 MB/s en secuencial. Sobra bus. Lo que tarda son los 4 TB a la velocidad de un disco duro: casi seis horas en el mejor de los casos, siete y media a un ritmo realista, y más de diez si el rendimiento cae.

Y aquí mi hardware tiene un problema que tardé en ver. Los dos discos no son equivalentes: el ST4000DM004 usa grabación SMR y el ST4000DM000 es CMR. Compré “dos discos de 4 TB” dando por hecho que eran intercambiables, y no lo son.

En un espejo eso importa. Cada escritura va a ambos discos y no termina hasta que termina el más lento, así que el SMR fija el techo del conjunto. Y una reconstrucción es escritura sostenida sin ventanas de reposo, que es justo el escenario donde un disco SMR se ahoga: la caché interna que usa para amortiguar se llena en minutos y el rendimiento se desploma.

No llegué a medir la reconstrucción, así que no puedo poner una cifra ni afirmar que el SMR fue el factor dominante en mi caso. Lo que sí queda claro es que el costo de un fallo depende de cuál disco falle: si muere el CMR es un trámite largo, y si muere el SMR reconstruyes 4 TB sostenidos sobre un disco que odia exactamente eso.

Y durante todo ese tiempo no tienes redundancia. Si el disco sano falla mientras reconstruye, perdiste el volumen.

Paso 6: el error que salió gratis

Como eran datos de prueba, pensé: no voy a esperar horas. Apago la caja y ya.

Interrumpí la reconstrucción. Y cuando volví a conectar:

/dev/disk6 (external, physical):
   2:         Apple_RAID_Offline                         4.0 TB     disk6s2
/dev/disk7 (external, physical):
   2:         Apple_RAID_Offline                         4.0 TB     disk7s2

Apple_RAID_Offline. El set virtual había desaparecido. diskutil appleRAID list respondía “No AppleRAID sets found”. Los discos quedaron con las particiones RAID huérfanas, sin conjunto al cual pertenecer, y los datos dejaron de ser accesibles.

(Son los mismos dos discos que en el Paso 1 aparecían como disk4s2 y disk5s2. Al desaparecer el set, el sistema los volvió a numerar.)

Seré preciso con lo que puedo afirmar: en mi caso, interrumpir la reconstrucción dejó el conjunto en un estado del que no lo recuperé. No agoté las opciones — iba a recrear el RAID de todos modos, así que no insistí. Puede que existiera una forma de reensamblarlo.

Aun con ese matiz, la lección práctica se sostiene: la reconstrucción es el momento más frágil de la vida de un RAID. No es un trámite en segundo plano. Es el escenario donde alguien descubre que el espejo no lo salvó — falla un disco, empieza la reconstrucción, el proceso se corta (un apagón, un cable suelto, alguien impaciente que apaga la caja) y el margen de error desaparece.

En mi caso perdí archivos desechables. Lección barata.

La segunda mentira del script

Días después, escribiendo esto, se me ocurrió pasarle la salida del conjunto destruido a mi script corregido.

Sigue el recorrido. El filtro busca la línea Status: y las filas de miembros; no hay ninguna de las dos, así que devuelve vacío. El contador da cero. La condición [ 0 -gt 0 ] es falsa. El script se va por el else.

Escribe RAID OK.

El escenario más grave de toda esta bitácora es el único que produce un registro afirmando que todo está bien. No es que falle en avisar: es que afirma lo contrario de la realidad.

Y hay dos capas más de ceguera sobre el mismo fallo. El [ -d "$VOL" ] || exit 0 hace que el script salga en silencio si el volumen no monta. Y StartOnMount solo se dispara cuando algo se monta: si el conjunto no ensambla, no hay evento ni ejecución.

Hallazgo 2 El script corregido detectaba el estado degradado, pero no la ausencia del conjunto. Ante un RAID destruido registraba RAID OK. El bug de la primera versión gritaba fallo sin fallo; el de la segunda certificaba salud sin salud.

De los dos, el segundo es peor. Un falso positivo te entrena a ignorar las alertas; un falso negativo te deja tranquilo mientras pierdes los datos.

La versión que quedó ataca las dos cosas a la vez: detectar la ausencia del conjunto, y dejar de depender de identificadores que cambian solos.

#!/bin/bash
# Vigila un conjunto AppleRAID concreto, identificado por UUID.
#
# OJO: el UUID sobrevive reconexiones y reinicios, pero NO sobrevive a
# recrear el conjunto. Si algún día lo destruyes y lo vuelves a crear,
# actualiza esta línea o el script alertará indefinidamente.
#   Consúltalo con: diskutil appleRAID list | grep "Unique ID"
SET_UUID="B560FCE0-592B-41B5-BA35-1E6AB43EB552"
SET_NAME="Caja_Raid"
LOG="$HOME/raidcheck.log"

sleep 10

SALIDA=$(diskutil info "$SET_UUID" 2>&1)
RC=$?

alerta() {
  osascript -e "display notification \"$1\" with title \"RAID $SET_NAME\" sound name \"Basso\""
  {
    echo "$(date '+%F %T') [check-raid] ALERTA: $1"
    echo "$SALIDA"
    echo "---"
  } >> "$LOG"
}

if [ $RC -ne 0 ]; then
  if diskutil list 2>/dev/null | grep -q "Apple_RAID"; then
    alerta "Discos RAID presentes pero el conjunto no responde"
  fi
  exit 0
fi

ESTADO=$(echo "$SALIDA" | grep -E "^ +Status:" | sed -E 's/^ +Status: +//' | sed -E 's/ +$//')

case "$ESTADO" in
  Online) echo "$(date '+%F %T') [check-raid] RAID OK ($ESTADO)" >> "$LOG" ;;
  "")     alerta "No pude leer el estado del conjunto" ;;
  *)      alerta "Conjunto en estado $ESTADO" ;;
esac

El cambio de diseño importante no es el UUID: es el case. Las dos versiones anteriores buscaban palabras de fallo, y por eso fallaron dos veces — una porque la lista sobraba (rebuild), otra porque faltaba un caso entero (no había salida que buscar). Esta versión no busca fallos: exige Online y alerta ante cualquier otra cosa, incluidos estados que yo no he visto nunca. Deja de importar si mi lista estaba completa.

Lo demás, en corto. diskutil info con un UUID inexistente devuelve código 1 y Could not find disk, así que ese RC es una señal fiable de que el conjunto no está. Si además quedan particiones Apple_RAID conectadas, es un conjunto roto y merece alerta; si no las hay, la caja está apagada y el script se calla.

De paso se arregla un detalle que había pasado por alto: en las versiones anteriores el sleep 10 iba después de la comprobación del volumen, así que la espera nunca servía para lo que yo creía. Si launchd disparaba antes de que el punto de montaje apareciera, el script ya había salido.

El prefijo [check-raid] tampoco es cosmético: probando estas versiones acabé con dos scripts escribiendo líneas idénticas en el mismo archivo, sin forma de saber cuál había escrito qué. Un registro sin autor no vale nada.

Cómo lo probé

Esta vez no me fié. Cinco escenarios, y digo con cuál método se validó cada uno:

EscenarioResultado esperadoCómo se probó
Conjunto sanoRAID OK (Online)Hardware real
Caja apagadasilencioHardware real
Conjunto degradadoalerta con el estadoSalida guardada
Estado inesperadoalerta con el estadoSalida guardada
Conjunto ausente, discos presentesalerta críticadiskutil sustituido por un stub

El último es el del Hallazgo 2, y reproducirlo de verdad implicaba volver a destruir el RAID — que ahora tiene datos que me importan. En su lugar sustituí diskutil por un script falso que devuelve las salidas del fallo real, y ejecuté el archivo definitivo contra él. No es lo mismo que romper el hardware, y por eso lo digo aquí en vez de dejarlo pasar.

Un matiz que salió de estas pruebas y que casi me engaña: expulsar la caja desde el Finder no es desconectarla. Expulsar desmonta el volumen, pero el conjunto AppleRAID sigue existiendo y respondiendo Online. La primera vez lo di por fallido cuando el script era el que tenía razón.

Para poner en marcha esta versión, sobrescribe el script y recarga el agente, que mantiene en memoria la ruta pero no el contenido:

chmod +x ~/bin/check-raid.sh
launchctl bootout gui/$(id -u)/com.mauro.raidcheck
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.mauro.raidcheck.plist

Paso 7: reconstruir desde cero

Como iba a recrear el RAID de todos modos, aproveché.

Un detalle contraintuitivo: borrar archivos no acelera la reconstrucción. Como copia bloques, no archivos, vaciar el volumen no ahorra ni un minuto. En cambio, destruir y recrear el set sí es instantáneo — un espejo nuevo nace vacío y sincronizado, sin nada que copiar.

# Confirmar identificadores — los números cambian
diskutil list

# Verificar que son los correctos ANTES de borrar
diskutil info /dev/disk6 | grep -E "Device / Media Name|Disk Size"
diskutil info /dev/disk7 | grep -E "Device / Media Name|Disk Size"

# Limpiar
diskutil eraseDisk APFS Temp1 GPT /dev/disk6
diskutil eraseDisk APFS Temp2 GPT /dev/disk7

# Recrear
diskutil appleRAID create mirror Caja_Raid APFS /dev/disk6 /dev/disk7

Sobre la verificación de identificadores: yo tenía otro disco externo conectado, con 1.1 TB de datos reales dentro. eraseDisk no pide confirmación ni permite deshacer: si te equivocas de número, borras el disco equivocado y se acabó. La comprobación más rápida es el tamaño — los discos del RAID son de 4 TB y el otro es de 2 TB, así que cualquier Disk Size que no diga 4 TB es la señal para detenerse.

La trampa que casi se me pasa: Rebuild: manual

El set nuevo quedó Online con ambos discos sanos. Todo perfecto. Pero al comparar la salida completa con la del RAID original, apareció una sola línea distinta:

Rebuild:              manual

El RAID creado desde Utilidad de Discos tenía automatic. Al crearlo por línea de comandos, diskutil usa manual por defecto.

En modo manual, si falla un disco y lo reinsertas, no se reconstruye solo: hay que lanzar la reparación a mano. El comportamiento que yo acababa de probar y verificar habría dejado de funcionar en silencio, y de no haber comparado línea por línea lo habría descubierto el día que un disco fallara de verdad.

Hallazgo 3 El RAID creado desde Utilidad de Discos quedó con reconstrucción automática. El mismo RAID recreado con diskutil appleRAID create quedó con reconstrucción manual. Se ven idénticos en todo lo demás.

sudo diskutil appleRAID update AutoRebuild 1 <UUID-del-set>

Si te devuelve Error updating RAID: Could not modify RAID (-9960), no te asustes: ese error también aparece cuando intentas fijar el valor que ya estaba puesto. Comprueba con diskutil appleRAID list antes de dar por hecho que algo salió mal.

Y también hubo que reaplicar los propietarios, que se pierden al recrear:

sudo diskutil enableOwnership /Volumes/Caja_Raid

Un tercer efecto de recrear, menos evidente: los UUID cambian todos. El conjunto pasó de 9B4E10E4… a B560FCE0… y los miembros de 848718FB…/49798402… a 72478A7A…/F3D61ED5…. Si tu monitoreo está anclado al UUID —como acaba estándolo el mío— recrear el RAID lo deja apuntando a un conjunto que ya no existe, y alertará indefinidamente. Es el precio de anclar por UUID: ganas estabilidad frente a reconexiones y reinicios, y a cambio adquieres un paso obligatorio cada vez que rehagas el conjunto.

Si te llevas una sola cosa de este artículo, que sea esta: recrear algo por CLI no reproduce automáticamente la configuración que hizo la interfaz gráfica. Un RAID que se ve idéntico puede comportarse distinto en el único momento que importa.

Estado final

ElementoEstado
RAID 1 espejoOnline, ambos discos sanos
DiscosSMR + CMR — no equivalentes
Reconstrucción automáticaHabilitada (tras corregirla)
Propietarios POSIXEnabled (verificado en el volumen)
MonitoreoAnclado al UUID del conjunto
Alerta ante disco ausenteProbada contra un fallo físico real
Alerta ante caja apagadaProbada contra hardware real
Alerta ante conjunto ausenteProbada con diskutil simulado
Notificación en pantallaVista durante el fallo real del Paso 5
SMARTNo disponible
Backup externoPendiente

Lo que me llevo

AppleRAID sirve, pero exige más supervisión de la que esperaba. La mecánica del espejo funciona: detectó el fallo, mantuvo el volumen accesible con un solo disco, reintegró el disco reinsertado sin intervención. Lo que no hace es todo lo demás — no hay alertas, ni panel de estado, ni correo, ni historial. Es la capa de redundancia y nada más; la observabilidad la construyes tú, y con discos USB la construyes a ciegas.

Verificar no es mirar la interfaz gráfica. Utilidad de Discos decía “Conectado” mientras los propietarios estaban desactivados, el SMART era inaccesible y no existía ninguna alerta configurada.

Un monitoreo sin probar no es monitoreo. Mi script tenía un falso positivo garantizado y yo no lo sabía. Lo corregí, me quedé tranquilo, y la versión corregida resultó tener un falso negativo peor. Probé el fallo que se me ocurrió, no el que me pasó de verdad dos pasos después. La tercera versión dejó de buscar fallos y pasó a exigir que todo esté bien, que es lo que debí hacer desde el principio.

El identificador es del sistema; el UUID es del objeto. disk6 describe dónde encontró macOS algo esta vez. El UUID describe qué es. Viniendo de Linux, uno espera poder anclarlo todo por UUID y olvidarse — en macOS puedes hacerlo a medias, y justo los comandos que borran discos son los que no te dejan.

Probar cuesta poco cuando los datos son desechables. Después de mover proyectos reales, sacar un disco a ver qué pasa deja de ser una prueba y se vuelve un riesgo. El momento es al principio, siempre.

No te fíes de la primera hipótesis. Estuve a punto de publicar que mi caja USB no soportaba SAT. Cinco comandos más tarde resultó que el problema estaba en otra capa, y que el chipset ni siquiera había llegado a ser puesto a prueba.

“Dos discos de 4 TB” no son dos discos iguales. Uno es SMR y el otro CMR, y ninguna herramienta de macOS me lo iba a decir. Lo supe leyendo los números de modelo.

Y el espejo sigue sin ser un backup. Me protege de que un disco muera. No de que yo borre una carpeta, ni de ransomware, ni de que la caja se caiga. Eso sigue pendiente, y es lo próximo.

¿Lo usaría para datos críticos de producción? No. ¿Para el disco de trabajo de un equipo personal, con backup aparte y un script de treinta líneas vigilando? Sí, sin problema. Es lo que estoy haciendo.


Apéndice: comandos de referencia

ComandoPara quéRiesgo
diskutil listTopología completa de discos y volúmenesSeguro
diskutil appleRAID listEstado del conjunto y de cada miembroSeguro
diskutil info /Volumes/<vol>Detalle del volumen: propietarios, cifrado, protocoloSeguro
diskutil info <UUID>Lo mismo, pero anclado al objeto y no al número de discoSeguro
ioreg -c IOUSBHostDevice -r -lIdentificar el chipset y la velocidad del puente USBSeguro
plutil -lint <plist>Validar el XML antes de cargarloSeguro
diskutil unmountDisk <disco>Desmontar el volumenBajo
diskutil enableOwnership <vol>Activar permisos POSIX en disco externoBajo
launchctl bootstrap gui/$(id -u) <plist>Registrar un LaunchAgentBajo
launchctl bootout gui/$(id -u)/<label>Descargar un LaunchAgentBajo
diskutil appleRAID update AutoRebuild 1 <UUID>Activar reconstrucción automáticaBajo
diskutil appleRAID repairMirror <set> <disco>Reparación manual de un miembroMedio — inicia una reconstrucción larga
diskutil appleRAID create mirror <nombre> APFS <d1> <d2>Crear el espejoDestructivo — sobrescribe ambos discos
diskutil appleRAID delete <UUID>Destruir el conjuntoDestructivo — el volumen deja de existir
diskutil eraseDisk APFS <nombre> GPT <disco>Borrar y reformatear un discoDestructivo — sin confirmación, sin retorno

Los tres últimos no preguntan nada antes de ejecutarse. Verifica el identificador de disco dos veces — cambian entre reconexiones, y en mi caso tenía otro disco externo con datos reales a un número de distancia.

Los identificadores de disco de los ejemplos son los de mi equipo y cambian entre reconexiones — verifica siempre los tuyos antes de ejecutar nada destructivo.