salud del disco: smartmontools, y el hueco que no era de espacio
El censo marcaba «discos y salud del equipo» 4 de 4 ausente: «nada que avise de
un disco muriéndose». En este proyecto el argumento no es hipotético — la
memoria tiene documentado un disco de 5,9 G que al llenarse NO se degrada, se
cae entero; 19 G de montones muertos; y un «0 fallan» que era mentira porque no
quedaban inodos para escribir el .fail. Todo eso es ESPACIO. La salud del
hardware no la miraba nadie.
Cuesta una receta: sus cuatro deps externas (libcap, systemd, selinux, libusb)
son todas opcionales por AC_CHECK_LIB, y el tarball del release trae configure.
⚠ EL ANCLA DEL GUARDIÁN SALE DE LEER SU FUENTE, no de suponer el formato. Mi
primera versión contaba «líneas que empiezan por letra» de `-P showall`, que es
frágil y podía rechazar un build correcto. knowndrives.cpp:showallpresets()
imprime una línea literal:
pout("Total number of entries :%5u\n", knowndrives.size(), …)
⇒ se lee ESE número. Verificado: 792 entradas. Sin la base de modelos smartctl
arranca y devuelve atributos crudos numerados sin saber qué significan — sellado
y sin la función que promete.
Y de paso otra cosa que salió de leer la fuente: esa función devuelve errcnt, o
sea que `-P showall` sale != 0 SÓLO si la base tiene errores de sintaxis. Tratar
su estado como «no arrancó» habría sido un malentendido.
⚠ También arreglé una precedencia mal entendida en mi propio control: encadenaba
`a || b | c || d`, y eso agrupa el pipe con `b` y evalúa el estado de `c`. Es la
misma trampa del estado de tubería que hoy ya me selló un fallo en automake, en
otra forma sintáctica.
Dicho y no disimulado: --without-libusb, así que NO se lee el SMART de un disco
externo por USB; los internos (ATA/SCSI/NVMe) van por ioctl. Y no se instala
script de arranque: el init de esta distro es arje, así que smartd se declarará
ahí cuando alguien decida vigilar discos de forma continua.
cxxopts entra como dep de pamixer (parser de argumentos, header-only). Lo único
que importa de él es el `.pc`: una cabecera instalada sin él es invisible para
meson, y el consumidor falla con «Dependency cxxopts not found» sin mencionar que
el fichero está ahí. Comprobado con el chequeo fuerte (PKG_CONFIG_LIBDIR +
--cflags, no --exists).
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
2 archivos · +168 −0
- recipes/cxxopts.toml +57 −0
- recipes/smartmontools.toml +111 −0
diff --git a/recipes/cxxopts.toml b/recipes/cxxopts.toml
new file mode 100644
index 00000000..3c3d24c8
--- /dev/null
+++ b/recipes/cxxopts.toml
@@ -0,0 +1,57 @@
+# cxxopts 3.3.1 — parser de argumentos de línea de comandos para C++, header-only.
+#
+# Entra como dep de `pamixer`, que la busca por pkg-config (`dependency('cxxopts')` en su
+# `meson.build`). Es una sola cabecera: no compila nada y no publica biblioteca.
+#
+# ⚠ **Lo único que importa acá es el `.pc`**, y por eso el guardián lo comprueba con el chequeo
+# FUERTE. Una cabecera instalada sin su `.pc` es invisible para meson y para cualquier
+# `PKG_CHECK_MODULES`: el consumidor falla diciendo «Dependency cxxopts not found», que no menciona
+# que el fichero está ahí. Upstream trae la plantilla (`packaging/pkgconfig.pc.in`) y su cmake la
+# instala si `CXXOPTS_ENABLE_INSTALL` está en On.
+name = "cxxopts"
+version = "3.3.1"
+license = "MIT"
+
+[source]
+tarball = "https://github.com/jarro2783/cxxopts/archive/refs/tags/v3.3.1/cxxopts-3.3.1.tar.gz"
+sha256 = "3bfc70542c521d4b55a46429d808178916a579b28d048bd8c727ee76c39e2072"
+
+[build]
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+configure = '''
+ZW="$PWD/.zwrap"; mkdir -p "$ZW"
+printf '#!/bin/sh\nexec /opt/zig/zig cc -mcpu=baseline "$@"\n' > "$ZW/cc"
+printf '#!/bin/sh\nexec /opt/zig/zig c++ -mcpu=baseline "$@"\n' > "$ZW/cxx"
+chmod +x "$ZW/cc" "$ZW/cxx"
+rm -rf build
+cmake -B build -G Ninja -Wno-dev \
+ -DCMAKE_C_COMPILER="$ZW/cc" -DCMAKE_CXX_COMPILER="$ZW/cxx" \
+ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr \
+ -DCMAKE_INSTALL_LIBDIR=lib \
+ -DCXXOPTS_BUILD_EXAMPLES=OFF -DCXXOPTS_BUILD_TESTS=OFF \
+ -DCXXOPTS_ENABLE_INSTALL=ON
+'''
+compile = "cmake --build build"
+install = '''
+set -e
+DESTDIR=/out cmake --install build
+[ -f /out/usr/include/cxxopts.hpp ] || { echo "cxxopts: falta include/cxxopts.hpp" >&2; find /out -name 'cxxopts*' >&2; exit 1; }
+pc=$(find /out/usr -name 'cxxopts.pc' 2>/dev/null | head -1)
+[ -n "$pc" ] || { echo "cxxopts: no quedó cxxopts.pc — meson la busca con dependency('cxxopts') y diría «not found» sin mencionar que la cabecera está" >&2; find /out -name '*.pc' >&2; exit 1; }
+# CHEQUEO FUERTE: con PKG_CONFIG_LIBDIR puesto, para no leer el anfitrión, y pidiendo cflags de
+# verdad en vez de `--exists`, que no valida nada.
+PKG_CONFIG_LIBDIR=$(dirname "$pc") pkgconf --cflags cxxopts >/dev/null 2>&1 || {
+ echo "cxxopts: pkgconf no resuelve 'cxxopts'" >&2
+ PKG_CONFIG_LIBDIR=$(dirname "$pc") pkgconf --cflags cxxopts >&2
+ exit 1
+}
+echo "cxxopts: cxxopts.hpp + cxxopts.pc $(PKG_CONFIG_LIBDIR=$(dirname "$pc") pkgconf --modversion cxxopts)"
+'''
+
+[deps]
+build = ["cmake", "samurai", "pkgconf"]
diff --git a/recipes/smartmontools.toml b/recipes/smartmontools.toml
new file mode 100644
index 00000000..33681d27
--- /dev/null
+++ b/recipes/smartmontools.toml
@@ -0,0 +1,111 @@
+# smartmontools 7.5 — SMART: preguntarle a un disco si se está muriendo.
+#
+# ══ POR QUÉ ENTRA ══════════════════════════════════════════════════════════════════════════════
+# `censo-distro.py` marca «discos y salud del equipo» **4 de 4 ausente**, con el comentario «nada
+# que avise de un disco muriéndose». Y en este proyecto eso no es hipotético: la memoria del repo
+# tiene documentados un disco de 5,9 G que **al llenarse no se degrada, se cae entero**, 19 G de
+# montones muertos, y un «0 fallan» que era mentira porque no quedaban inodos para escribir el
+# `.fail`. Todo eso es espacio; **la salud del hardware no la mira nadie**.
+#
+# Un disco con sectores reasignados en aumento avisa días antes de morir — pero sólo si alguien
+# pregunta. `smartctl` es quien pregunta.
+#
+# ══ CUESTA UNA RECETA: TODAS SUS DEPS SON OPCIONALES ═══════════════════════════════════════════
+# Medido sobre su `configure.ac`: sus únicas deps externas son `AC_CHECK_LIB` de `cap`, `systemd`,
+# `selinux` y `usb` — **las cuatro opcionales**, y ninguna hace falta para lo que importa (hablar
+# ATA/SCSI/NVMe por ioctl). El tarball del release trae `configure` generado, así que tampoco
+# necesita autotools.
+#
+# ══ LAS PERILLAS ═══════════════════════════════════════════════════════════════════════════════
+# --without-libsystemd no hay systemd en esta distro; es para que `smartd` notifique por él
+# --without-libcap-ng `libcap-ng` no está en el corpus. Lo que se pierde es que `smartd`
+# suelte capacidades al arrancar — relevante sólo para el daemon
+# --without-selinux no hay SELinux acá
+# --without-libusb los puentes USB-SATA. Sin esto no se lee el SMART de un disco externo
+# por USB; los internos (ATA/SCSI/NVMe) van por ioctl y no lo necesitan.
+# **Se dice porque es la limitación que alguien va a notar**
+# --with-nvme-devicescan que `smartd`/`smartctl --scan` encuentren los NVMe, que es lo que tiene
+# esta clase de máquina. Sin esto hay que nombrar el dispositivo a mano
+# --without-update-smart-drivedb el script que BAJA la base de datos de modelos desde internet.
+# No en una distro que se construye entera desde fuente y sin red
+#
+# ⚠ `--with-initscriptdir=no` y `--without-systemdsystemunitdir`: el init de esta distro es `arje`,
+# no systemd ni sysvinit, así que instalar un script de arranque ajeno sería basura en la imagen.
+# `smartd` se declara en `arje` cuando alguien decida vigilar discos de forma continua; hoy lo que
+# cierra el hueco es `smartctl`, que se invoca a mano.
+name = "smartmontools"
+version = "7.5"
+license = "GPL-2.0-or-later"
+
+[source]
+tarball = "https://sourceforge.net/projects/smartmontools/files/smartmontools/7.5/smartmontools-7.5.tar.gz/download"
+sha256 = "690b83ca331378da9ea0d9d61008c4b22dde391387b9bbad7f29387f2595f76e"
+
+[build]
+# Sus deps son todas opcionales y ninguna se enciende ⇒ no hay dep shared-only que fuerce dinámico.
+# (La regla: `link` tiene que coincidir con lo que publican las deps de la receta — ver la nota de
+# `recipes/gdb.toml`, donde `gmp`/`mpfr` son sólo `.so` y el estático falló.)
+compiler = "zig-cc"
+target = "x86_64-linux-musl"
+link = "static"
+flags = []
+
+[build.phases]
+configure = '''
+set -e
+[ -f ./configure ] || { echo "smartmontools: el tarball no trae ./configure" >&2; exit 1; }
+./configure --build=$CBUILD --host=$CHOST --prefix=/usr --sysconfdir=/etc \
+ --without-libsystemd --without-libcap-ng --without-selinux --without-libusb \
+ --with-nvme-devicescan \
+ --without-update-smart-drivedb \
+ --with-initscriptdir=no --without-systemdsystemunitdir
+'''
+compile = 'make -j"$(nproc)"'
+# ⚠ EL GUARDIÁN LO EJECUTA Y LE PIDE LA BASE DE DATOS DE MODELOS.
+#
+# `smartctl` sin `drivedb.h` compilada adentro **arranca igual** y pierde lo que lo hace útil: los
+# nombres y los umbrales específicos de cada modelo de disco. Daría atributos crudos numerados sin
+# saber qué significan — sellado, con contenido, y sin la función que promete.
+#
+# No se intenta consultar un disco de verdad: el sandbox va con `--unshare-all` y sin `/dev`
+# propio, así que pedirlo haría fallar un build correcto (el error que ya pagué cinco veces hoy).
+# Lo que sí se puede comprobar es que el binario arranque y que la base de datos esté dentro.
+install = '''
+set -e
+make install DESTDIR=/out
+for b in smartctl smartd; do
+ [ -x "/out/usr/sbin/$b" ] || [ -x "/out/usr/bin/$b" ] || { echo "smartmontools: falta $b" >&2; find /out -maxdepth 4 -type f -perm -u+x >&2; exit 1; }
+done
+SC=$(find /out -name smartctl -type f -perm -u+x | head -1)
+# Ejecución y truncado SEPARADOS: el estado de una tubería es el del último comando.
+sal=$("$SC" --version 2>&1) || { echo "smartmontools: smartctl no arranca" >&2; printf '%s\n' "$sal" >&2; exit 1; }
+printf '%s\n' "$sal" | head -1 | grep -qE 'smartctl 7\.5' || { echo "smartmontools: --version inesperado: $(printf '%s\n' "$sal" | head -1)" >&2; exit 1; }
+
+# La base de datos de modelos: es lo que traduce «atributo 5» a «sectores reasignados». Sin ella
+# `smartctl` arranca y devuelve números crudos sin nombre — sellado y sin la función que promete.
+#
+# ⚠ **EL ANCLA SALE DE LEER SU FUENTE, no de suponer el formato.** Mi primera versión contaba
+# «líneas que empiezan por letra» de `-P showall`, que es frágil y podía rechazar un build correcto.
+# `knowndrives.cpp:showallpresets()` imprime una línea literal al final:
+#
+# pout("Total number of entries :%5u\n" …, knowndrives.size(), …)
+#
+# ⇒ se lee ESE número, que es exactamente el tamaño de la base compilada. Y de paso: esa función
+# devuelve `errcnt`, o sea que sale ≠ 0 **sólo** si la base tiene errores de sintaxis — otra cosa
+# que vale saber antes de tratar su estado como «no arrancó».
+db=$("$SC" -P showall 2>&1) || true
+n=$(printf '%s\n' "$db" | sed -n 's/^Total number of entries *: *\([0-9]\+\).*/\1/p' | head -1)
+[ -n "$n" ] || {
+ echo "smartmontools: -P showall no imprimió la línea 'Total number of entries' — el formato cambió o la base no se compiló" >&2
+ printf '%s\n' "$db" | tail -5 >&2
+ exit 1
+}
+[ "$n" -gt 50 ] || {
+ echo "smartmontools: la base de modelos trae $n entrada(s) — esperaba más de 50; smartctl daría atributos crudos numerados sin saber qué significan" >&2
+ exit 1
+}
+echo "smartmontools: $(printf '%s\n' "$sal" | head -1) · smartctl + smartd · $n entrada(s) de drivedb"
+'''
+
+[deps]
+build = ["make", "pkgconf"]