tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1184Shell 70Rust 62Markdown 501503 Definitions

feat(instalador): el firmware que el kernel pide ANTES de montar la raiz lo decide la maquina

«que ni ethernet funciona» no se arreglaba con tener el firmware en la imagen. El driver va
COMPILADO EN EL KERNEL (linux-metal no habilita CONFIG_MODULES) y hace su probe a los 0,49 s de
arranque, cuando el unico filesystem que existe es el initramfs:

  iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-QuZ-a0-hr-b0-77.ucode failed: -2
  iwlwifi 0000:00:14.3: no suitable firmware found!

/lib/firmware de la raiz todavia no esta montada, y request_firmware no reintenta nunca: el
dispositivo queda sin inicializar para toda la sesion. Es el motivo de que firmware-metal (1047
MiB, declarado en base hoy mismo) no alcance por si solo.

Meter /lib/firmware entero en el initramfs es 1 GB en RAM por arranque. Meter «el wifi» tampoco
sirve: la familia iwlwifi sola son 287 MB, y recortarla por generacion es adivinar — el nombre que
el driver pide sale del chip Y de la version de API que ese kernel soporta, probando de N hacia
abajo.

La salida es que lo decida quien sabe en que maquina esta: el instalador, que corre EN el metal de
destino. Y no adivina nada, porque EL MEDIO ARRANCA CON EL MISMO PROBLEMA — su initramfs tampoco
trae firmware — asi que su propio `dmesg` ya tiene la lista EXACTA de blobs que ese hardware pidio,
con el nombre que el driver uso. Se leen de ahi y se copian del arbol que se va a instalar.

lib/firmware-initramfs.sh, aparte para poder certificarla sin root ni hardware, y viaja sola al
initramfs del instalador porque los generadores derivan las librerias del propio script (son cinco
ahora). Cubre los TRES formatos del kernel —«Direct firmware load for X failed», «firmware: failed
to load X» y «Falling back to sysfs fallback for: X»— porque cambian entre versiones y entre el
camino directo y el de respaldo, y mirar uno solo da «no falta nada» sobre un wifi muerto.

Guardas, todas con su motivo: tope de 24 MiB (la ESP son 64 y ahi vive el kernel); no se pisa el
initramfs hasta que el nuevo esta reempaquetado y se comprueba que tiene /init; se deja respaldo;
una ruta con `..` o absoluta se descarta NOMBRANDOLA (el dmesg es texto del kernel, pero esto
escribe ficheros); un blob que falta no aborta la instalacion, se nombra — una maquina instalada
sin wifi es recuperable, una que no arranca porque alguien reempaqueto mal su initramfs no.

Certificado: scripts/probar-firmware-initramfs.sh, 4 bloques verdes sin root y sin hardware, con
un dmesg copiado del arranque real. Incluye el BRAZO DE CONTROL que importa: con un dmesg donde
todo cargo bien, el initramfs queda BIT A BIT igual. Y que un blob que en la imagen es un ENLACE
—los 2415 que linux-firmware reconstruye desde WHENCE— viaje adentro como FICHERO, porque en el
initramfs no hay a donde apuntar y un symlink roto falla igual que un fichero ausente pero se
diagnostica peor.

Falta metal: instalar en una maquina y ver `loaded firmware version` en el dmesg del primer
arranque.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

19df6c1485ec9afccb3a14ab9bcaa8d12df9e931 · by sergio · (7 d ago) · parent a25d43565 · browse the tree at this commit

Semantic delta

No definition changed: this change is text, documentation or data.

3 files · +257 −0

diff --git a/scripts/lib/firmware-initramfs.sh b/scripts/lib/firmware-initramfs.sh
new file mode 100644
index 00000000..a1d9b3d2
--- /dev/null
+++ b/scripts/lib/firmware-initramfs.sh
@@ -0,0 +1,136 @@
+#!/bin/sh
+# lib/firmware-initramfs.sh — el firmware que el kernel pide ANTES de montar la raíz.
+#
+# ══ EL DEFECTO, MEDIDO EN METAL EL 2026-10-14/15 ═══════════════════════════════════════════════
+# «que ni ethernet funciona». La imagen ya trae el firmware completo (`firmware-metal`, 1047 MiB en
+# `/lib/firmware`) y **para el arranque no alcanza**, por una razón de tiempos:
+#
+#   iwlwifi 0000:00:14.3: Detected Intel(R) Wi-Fi 6 AX201 160MHz
+#   iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-QuZ-a0-hr-b0-77.ucode failed: -2
+#   iwlwifi 0000:00:14.3: no suitable firmware found!
+#
+# Esos mensajes salen a los **0,49 s de arranque**: el driver va COMPILADO EN EL KERNEL
+# (`linux-metal` no habilita `CONFIG_MODULES`), así que hace su `probe` cuando el único filesystem
+# que existe es el initramfs. `/lib/firmware` de la raíz todavía no está montada, y `request_firmware`
+# no reintenta nunca: el dispositivo queda sin inicializar para toda la sesión.
+#
+# ══ POR QUÉ ESTO VA EN EL INSTALADOR Y NO EN LA IMAGEN ═════════════════════════════════════════
+# Meter `/lib/firmware` entero en el initramfs es 1 GB cargado en RAM en cada arranque. Meter «el
+# wifi» tampoco sirve: la familia `iwlwifi` sola son 287 MB, y recortarla por generación es adivinar
+# —el nombre que el driver pide sale del chip Y de la versión de API que ese kernel soporta—.
+#
+# La salida es que lo decida **quien sabe en qué máquina está**: el instalador, que corre EN el metal
+# de destino. Y no tiene que adivinar nada, porque **el medio arranca con el MISMO problema**: su
+# initramfs tampoco trae firmware, así que su propio `dmesg` ya tiene la lista EXACTA de ficheros
+# que ese hardware pidió, con el nombre que el driver usó. Se leen de ahí y se copian; en una máquina
+# donde todo cargó bien, la lista sale vacía y esto no toca nada.
+#
+# Vive aparte para poder certificarla sin root ni hardware, igual que `lib/usuarios-del-home.sh` y
+# `lib/cuenta.sh`: `scripts/probar-firmware-initramfs.sh` le da un `dmesg` de mentira y un cpio de
+# juguete. Y viaja sola al initramfs del instalador porque los generadores derivan las librerías del
+# propio script que las sourcea.
+
+# El techo. La ESP del instalador son 64 MiB y ahí viven el kernel (~12 MiB) y este initramfs; un
+# injerto sin tope podría no entrar, y «no entra en la ESP» se descubre al final de una instalación.
+FW_IRD_TOPE_KIB="${FW_IRD_TOPE_KIB:-24576}"   # 24 MiB
+
+# firmware_pedido_del_dmesg [fichero] — las rutas de firmware que el kernel pidió y NO encontró, una
+# por línea, sin repetir. Sin argumento lee el `dmesg` de esta máquina.
+#
+# Se cubren los tres formatos que emite el kernel, porque cambian entre versiones y entre el camino
+# directo y el de respaldo — y mirar sólo uno da «no falta nada» sobre un wifi muerto:
+#   · `Direct firmware load for <X> failed with error -2`
+#   · `firmware: failed to load <X> (-2)`
+#   · `Falling back to sysfs fallback for: <X>`
+firmware_pedido_del_dmesg() {
+	{ if [ -n "${1:-}" ]; then cat "$1"; else dmesg 2>/dev/null; fi; } | sed -n \
+		-e 's/.*Direct firmware load for \([^ ]*\) failed.*/\1/p' \
+		-e 's/.*firmware: failed to load \([^ ]*\) .*/\1/p' \
+		-e 's/.*Falling back to sysfs fallback for: *\([^ ]*\).*/\1/p' \
+		| sed 's/[,:]$//' | sort -u
+}
+
+# firmware_al_initramfs <ird.cpio.gz> <dir-con-lib-firmware> [fichero-dmesg] — injerta en el
+# initramfs los blobs que el dmesg pidió. Devuelve 0 siempre que el initramfs quede USABLE: si no
+# hay nada que poner, lo deja intacto y lo dice.
+#
+# ⚠ Lo que NO hace: no falla la instalación porque falte un blob. Una máquina que se instala sin wifi
+# es recuperable; una que no arranca porque alguien reempaquetó mal su initramfs, no.
+firmware_al_initramfs() {
+	_fi_ird="$1"; _fi_src="$2"; _fi_dmesg="${3:-}"
+	[ -r "$_fi_ird" ] || { echo "    firmware: no encuentro el initramfs $_fi_ird — lo salteo" >&2; return 0; }
+
+	_fi_lista=$(firmware_pedido_del_dmesg "$_fi_dmesg")
+	if [ -z "$_fi_lista" ]; then
+		echo "    firmware: el kernel de este arranque no pidió ningún blob que le faltara — nada que injertar"
+		return 0
+	fi
+
+	_fi_tmp="${TMPDIR:-/tmp}/fw-ird.$$"
+	rm -rf "$_fi_tmp"; mkdir -p "$_fi_tmp/arbol" || return 0
+
+	# Desempaquetar. Si esto falla, el initramfs queda tal cual: no se toca el original hasta el final.
+	if ! ( cd "$_fi_tmp/arbol" && gzip -dc "$_fi_ird" | cpio -idm --quiet ) 2>/dev/null; then
+		echo "    firmware: no pude desempaquetar el initramfs; lo dejo como está" >&2
+		rm -rf "$_fi_tmp"; return 0
+	fi
+	# El `/init` es la razón de existir de este fichero. Si no está, algo entendí mal y me aparto.
+	[ -f "$_fi_tmp/arbol/init" ] || {
+		echo "    firmware: el initramfs no trae /init — no lo toco" >&2
+		rm -rf "$_fi_tmp"; return 0; }
+
+	_fi_n=0; _fi_kib=0; _fi_falta=0
+	for _fi_f in $_fi_lista; do
+		# Una ruta con `..` o absoluta saldría del árbol: el dmesg es texto del kernel, pero esto
+		# escribe ficheros y no se confía en una cadena parseada.
+		case "$_fi_f" in
+			/*|*..*) echo "    firmware: ⚠ descarto una ruta sospechosa: $_fi_f" >&2; continue ;;
+		esac
+		_fi_org=""
+		for _fi_c in "$_fi_src/lib/firmware/$_fi_f" "$_fi_src/$_fi_f"; do
+			[ -e "$_fi_c" ] && { _fi_org="$_fi_c"; break; }
+		done
+		if [ -z "$_fi_org" ]; then
+			# Se NOMBRA. Un blob que el hardware pide y la imagen no trae es una capacidad que esa
+			# máquina no va a tener, y el dueño merece saber cuál.
+			echo "    firmware: ⚠ el hardware pide «$_fi_f» y la imagen no lo trae"
+			_fi_falta=$((_fi_falta + 1))
+			continue
+		fi
+		_fi_peso=$(du -k "$(readlink -f "$_fi_org")" 2>/dev/null | cut -f1)
+		_fi_peso=${_fi_peso:-0}
+		if [ $((_fi_kib + _fi_peso)) -gt "$FW_IRD_TOPE_KIB" ]; then
+			echo "    firmware: ⚠ «$_fi_f» no entra en el tope de $((FW_IRD_TOPE_KIB / 1024)) MiB del initramfs: lo salteo"
+			continue
+		fi
+		mkdir -p "$_fi_tmp/arbol/lib/firmware/$(dirname "$_fi_f")"
+		# `-L`: si en la imagen el blob es un enlace (los 2415 que `linux-firmware` reconstruye desde
+		# WHENCE), al initramfs va el CONTENIDO — adentro no hay a dónde apuntar.
+		cp -L "$_fi_org" "$_fi_tmp/arbol/lib/firmware/$_fi_f" 2>/dev/null || continue
+		_fi_n=$((_fi_n + 1)); _fi_kib=$((_fi_kib + _fi_peso))
+	done
+
+	if [ "$_fi_n" = 0 ]; then
+		echo "    firmware: no pude poner ninguno de los $(echo "$_fi_lista" | wc -w) pedidos; el initramfs queda igual"
+		rm -rf "$_fi_tmp"; return 0
+	fi
+
+	# Reempaquetar y comprobar ANTES de pisar el original. Un initramfs a medio escribir es una
+	# máquina que no arranca, y eso no se diagnostica desde la máquina que no arranca.
+	( cd "$_fi_tmp/arbol" && find . | cpio -o -H newc --quiet --owner=root:root 2>/dev/null | gzip -9 > "$_fi_tmp/nuevo.cpio.gz" ) || {
+		echo "    firmware: falló el reempaquetado; dejo el initramfs original" >&2
+		rm -rf "$_fi_tmp"; return 0; }
+	# ⚠ `cpio -t` imprime las rutas SIN el `./` con que `find .` las guardó: el patrón acepta las dos
+	# formas. Costó tres «NO» sobre un initramfs correcto el 2026-10-14.
+	_fi_dentro=$(gzip -dc "$_fi_tmp/nuevo.cpio.gz" | cpio -t --quiet 2>/dev/null | grep -c "^\(\./\)\?init$" || true)
+	if [ "${_fi_dentro:-0}" -lt 1 ]; then
+		echo "    firmware: el initramfs nuevo no tiene /init — NO lo uso" >&2
+		rm -rf "$_fi_tmp"; return 0
+	fi
+	cp "$_fi_ird" "$_fi_ird.antes-del-firmware" 2>/dev/null || true
+	cp "$_fi_tmp/nuevo.cpio.gz" "$_fi_ird"
+	echo "    firmware: $_fi_n blob(s) injertados en el initramfs ($((_fi_kib)) KiB)$([ "$_fi_falta" -gt 0 ] && echo ", $_fi_falta sin encontrar")"
+	echo "              ahora pesa $(wc -c < "$_fi_ird") bytes"
+	rm -rf "$_fi_tmp"
+	return 0
+}
diff --git a/scripts/probar-firmware-initramfs.sh b/scripts/probar-firmware-initramfs.sh
new file mode 100644
index 00000000..e1b66746
--- /dev/null
+++ b/scripts/probar-firmware-initramfs.sh
@@ -0,0 +1,105 @@
+#!/bin/sh
+# probar-firmware-initramfs.sh — ¿entra al initramfs el firmware que ESTE hardware pidió, y sólo ese?
+#
+# Sin root y sin hardware: un `dmesg` de mentira (copiado del arranque real del operador), un árbol
+# de firmware de juguete y un cpio con su `/init`. Lo que se mide es lo que decide si una máquina
+# arranca con red o sin red.
+#
+#   sh scripts/probar-firmware-initramfs.sh
+set -eu
+
+ROOT="$(cd "$(dirname "$0")/.." && pwd)"
+. "$ROOT/scripts/lib/firmware-initramfs.sh"
+
+W=$(mktemp -d); trap 'rm -rf "$W"' EXIT
+fail() { echo "✗ FALLO: $*"; exit 1; }
+
+# ── El dmesg: las tres formas que emite el kernel, más ruido que NO debe casar ─────────────────
+cat > "$W/dmesg.log" <<'EOF'
+[    0.481237] iwlwifi 0000:00:14.3: Detected Intel(R) Wi-Fi 6 AX201 160MHz
+[    0.492003] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-QuZ-a0-hr-b0-77.ucode failed with error -2
+[    0.492101] iwlwifi 0000:00:14.3: no suitable firmware found!
+[    0.501119] i915 0000:00:02.0: Direct firmware load for i915/tgl_dmc_ver2_12.bin failed with error -2
+[    0.511200] platform regulatory.0: firmware: failed to load regulatory.db (-2)
+[    0.521300] r8169 0000:03:00.0: Falling back to sysfs fallback for: rtl_nic/rtl8168h-2.fw
+[    0.600000] usb 1-2: new high-speed USB device number 3 using xhci_hcd
+[    0.610000] EXT4-fs (nvme1n1p2): mounted filesystem with ordered data mode
+EOF
+
+echo "== 1: la lista sale del dmesg, completa y sin ruido =="
+LISTA=$(firmware_pedido_del_dmesg "$W/dmesg.log")
+N=$(printf '%s\n' "$LISTA" | wc -l)
+[ "$N" = 4 ] || { printf '%s\n' "$LISTA"; fail "esperaba 4 pedidos y conté $N"; }
+for esperado in iwlwifi-QuZ-a0-hr-b0-77.ucode i915/tgl_dmc_ver2_12.bin regulatory.db rtl_nic/rtl8168h-2.fw; do
+	printf '%s\n' "$LISTA" | grep -qx "$esperado" || fail "falta «$esperado» en la lista"
+done
+printf '%s\n' "$LISTA" | grep -q "xhci\|EXT4\|Detected\|no suitable" && fail "casó una línea que no es un pedido de firmware"
+echo "  ✓ los 4 pedidos, en los tres formatos, y ninguna línea de ruido"
+
+# ── El árbol de firmware de la imagen y el initramfs de juguete ───────────────────────────────
+mkdir -p "$W/img/lib/firmware/i915" "$W/img/lib/firmware/rtl_nic" "$W/img/lib/firmware/intel/iwlwifi"
+# El wifi vive adentro y se pide por el nombre de la RAÍZ: en la imagen eso es un enlace, igual que
+# los 2415 que `linux-firmware` reconstruye desde WHENCE.
+dd if=/dev/zero of="$W/img/lib/firmware/intel/iwlwifi/iwlwifi-QuZ-a0-hr-b0-77.ucode" bs=1k count=140 2>/dev/null
+ln -s intel/iwlwifi/iwlwifi-QuZ-a0-hr-b0-77.ucode "$W/img/lib/firmware/iwlwifi-QuZ-a0-hr-b0-77.ucode"
+dd if=/dev/zero of="$W/img/lib/firmware/i915/tgl_dmc_ver2_12.bin" bs=1k count=20 2>/dev/null
+dd if=/dev/zero of="$W/img/lib/firmware/regulatory.db" bs=1k count=7 2>/dev/null
+# `rtl_nic/rtl8168h-2.fw` NO se pone: es el caso «el hardware lo pide y la imagen no lo trae».
+
+mkdir -p "$W/ird/bin"
+printf '#!/bin/sh\necho init de juguete\n' > "$W/ird/init"; chmod 755 "$W/ird/init"
+printf 'busybox\n' > "$W/ird/bin/busybox"
+( cd "$W/ird" && find . | cpio -o -H newc --quiet | gzip -9 > "$W/pivote.cpio.gz" )
+ANTES=$(wc -c < "$W/pivote.cpio.gz")
+
+echo "== 2: el injerto pone lo que hay, nombra lo que falta y conserva el /init =="
+SALIDA=$(firmware_al_initramfs "$W/pivote.cpio.gz" "$W/img" "$W/dmesg.log")
+printf '%s\n' "$SALIDA" | sed 's/^/    /'
+echo "$SALIDA" | grep -q "3 blob(s) injertados" || fail "esperaba 3 injertados (el cuarto no está en la imagen)"
+echo "$SALIDA" | grep -q "rtl_nic/rtl8168h-2.fw.*no lo trae" || fail "no nombró el blob que falta"
+
+DENTRO=$(gzip -dc "$W/pivote.cpio.gz" | cpio -t --quiet 2>/dev/null)
+for e in "lib/firmware/iwlwifi-QuZ-a0-hr-b0-77.ucode" "lib/firmware/i915/tgl_dmc_ver2_12.bin" "lib/firmware/regulatory.db" "init" "bin/busybox"; do
+	printf '%s\n' "$DENTRO" | grep -qx "\(\./\)\?$e" || { printf '%s\n' "$DENTRO"; fail "«$e» no quedó dentro del initramfs"; }
+done
+printf '%s\n' "$DENTRO" | grep -q "rtl8168h" && fail "metió un blob que la imagen no tiene"
+echo "  ✓ los 3 adentro, el /init y el busybox intactos, y nada inventado"
+
+# El wifi se pide por el nombre de la raíz y en la imagen es un ENLACE: adentro tiene que ir el
+# CONTENIDO, porque en el initramfs no hay a dónde apuntar. Si viajara como enlace, el driver
+# abriría un symlink roto — que falla igual que un fichero ausente y se diagnostica peor.
+mkdir -p "$W/ver" && ( cd "$W/ver" && gzip -dc "$W/pivote.cpio.gz" | cpio -idm --quiet )
+[ -f "$W/ver/lib/firmware/iwlwifi-QuZ-a0-hr-b0-77.ucode" ] || fail "el .ucode no es un fichero regular adentro"
+[ -L "$W/ver/lib/firmware/iwlwifi-QuZ-a0-hr-b0-77.ucode" ] && fail "viajó como enlace: adentro no hay a dónde apuntar"
+[ "$(wc -c < "$W/ver/lib/firmware/iwlwifi-QuZ-a0-hr-b0-77.ucode")" = "143360" ] || fail "el .ucode llegó truncado"
+echo "  ✓ el blob que en la imagen es un enlace viaja como FICHERO (143360 B)"
+[ "$(wc -c < "$W/pivote.cpio.gz")" -gt "$ANTES" ] || fail "el initramfs no creció"
+[ -r "$W/pivote.cpio.gz.antes-del-firmware" ] || fail "no dejó respaldo del initramfs original"
+echo "  ✓ creció de $ANTES a $(wc -c < "$W/pivote.cpio.gz") bytes, con respaldo del original"
+
+echo "== 3: BRAZO DE CONTROL — una máquina donde no faltó nada no se toca =="
+cat > "$W/dmesg-ok.log" <<'EOF'
+[    0.492003] iwlwifi 0000:00:14.3: loaded firmware version 77.a0a4b1e2.0 op_mode iwlmvm
+[    0.501119] i915 0000:00:02.0: [drm] Finished loading DMC firmware i915/tgl_dmc_ver2_12.bin
+EOF
+( cd "$W/ird" && find . | cpio -o -H newc --quiet | gzip -9 > "$W/limpio.cpio.gz" )
+SUMA_ANTES=$(sha256sum < "$W/limpio.cpio.gz")
+SAL2=$(firmware_al_initramfs "$W/limpio.cpio.gz" "$W/img" "$W/dmesg-ok.log")
+echo "$SAL2" | grep -q "nada que injertar" || { echo "$SAL2"; fail "debió decir que no hay nada que injertar"; }
+[ "$(sha256sum < "$W/limpio.cpio.gz")" = "$SUMA_ANTES" ] || fail "tocó un initramfs que no hacía falta tocar"
+echo "  ✓ sin pedidos fallidos: el initramfs queda BIT A BIT igual"
+
+echo "== 4: lo que no se deja hacer =="
+cat > "$W/dmesg-malo.log" <<'EOF'
+[    0.1] x: Direct firmware load for ../../etc/shadow failed with error -2
+[    0.2] y: Direct firmware load for /etc/passwd failed with error -2
+EOF
+( cd "$W/ird" && find . | cpio -o -H newc --quiet | gzip -9 > "$W/malo.cpio.gz" )
+# ⚠ Con `2>&1`: los avisos de ruta sospechosa van por stderr —son avisos, no salida— y `$(...)`
+# sólo captura stdout. Sin esto el test fallaba sobre un código que hacía lo correcto.
+SAL3=$(firmware_al_initramfs "$W/malo.cpio.gz" "$W/img" "$W/dmesg-malo.log" 2>&1 || true)
+echo "$SAL3" | grep -q "sospechosa" || { echo "$SAL3"; fail "no descartó las rutas con .. ni las absolutas"; }
+echo "  ✓ una ruta con «..» o absoluta se descarta nombrándola (el dmesg es texto, y esto escribe ficheros)"
+
+echo
+echo "✓✓ probar-firmware-initramfs: el firmware que pidió ESTE hardware entra, y sólo ese"
diff --git a/scripts/takana-install-partition.sh b/scripts/takana-install-partition.sh
index ab149c8b..6dce3484 100755
--- a/scripts/takana-install-partition.sh
+++ b/scripts/takana-install-partition.sh
@@ -122,6 +122,10 @@ case "$ON_ACTIVE" in auto|kexec|free-space|abort) ;; *) echo "--on-active-root:
 # no preguntaba NADA: los usuarios salían del `/home` conservado con `!` en shadow y root quedaba
 # cerrado, o sea una máquina instalada e inadministrable. Medido en metal el 2026-10-14.
 . "$(dirname "$0")/lib/cuenta.sh"
+# El firmware que el kernel pide ANTES de montar la raíz. El driver del wifi va compilado en el
+# kernel y pide su blob a los 0,49 s, cuando el único filesystem es el initramfs: `/lib/firmware` de
+# la raíz no alcanza, y «que ni ethernet funciona» era eso. Medido el 2026-10-15.
+. "$(dirname "$0")/lib/firmware-initramfs.sh"
 SFDISK=$(herramienta util-linux sbin/sfdisk sfdisk) || exit 1
 # `e2label` sólo hace falta para `--adopt`, que no formatea: ahí la etiqueta por la que el arranque
 # encuentra la raíz hay que ponerla a mano. Se resuelve acá arriba —con el resto— y sólo cuando se
@@ -1136,6 +1140,18 @@ fi
 ROOT_TREE="$STAGE/root"; IRD="$STAGE/initramfs.cpio.gz"
 [ -d "$ROOT_TREE" ] && [ -r "$IRD" ] || { echo "takana-install-partition: el stage no quedó completo" >&2; exit 1; }
 
+# ── EL FIRMWARE DEL ARRANQUE, DECIDIDO POR LA MÁQUINA QUE SE ESTÁ INSTALANDO ─────────────────────
+# Va acá, con el IRD ya validado y antes de que se copie a la ESP (y antes del cálculo de espacio,
+# que es lo que se mide contra los 64 MiB de la partición de arranque).
+#
+# El instalador corre en el metal de destino y **arrancó con el mismo problema** —su initramfs
+# tampoco trae firmware—, así que su propio `dmesg` ya tiene la lista EXACTA de blobs que este
+# hardware pidió, con el nombre que el driver usó. No se adivina nada: se leen de ahí y se copian
+# del árbol que se va a instalar. En una máquina donde todo cargó bien, la lista sale vacía y el
+# initramfs queda bit a bit igual.
+echo "══ el firmware que este hardware pidió al arrancar ══════════════════════════"
+firmware_al_initramfs "$IRD" "$ROOT_TREE"
+
 # El /home conservado se declara en el ÁRBOL, antes de que el árbol entre al filesystem nuevo. Si
 # hay un choque de uid, `keep_aplicar` aborta acá — con el destino todavía sin tocar.
 if [ -n "$KEEP_SPEC" ]; then keep_aplicar "$ROOT_TREE"; fi