Files
Sergio 476168bb07 takana etapa 5c: comentarios de scripts, MOTD, y un BUG que introdujo la etapa 4
250 líneas de comentario en 151 scripts. Control verificado: el diff no toca
NI UNA línea que no empiece por #, y la sintaxis de los 151 pasa.

El barrido saltea heredocs y cadenas triples, y el guardián DISPARÓ 3 veces:
las tres eran el MOTD que el script escribe DENTRO de la imagen construida —
texto del producto, no comentario del script. Se cambiaron aparte y a
propósito, que es rebranding, no limpieza.

Y el hallazgo caro:  casaba contra ,
que es el TARGET de tracing — o sea el module_path!, o sea el nombre del crate.
La etapa 4 lo movió a  y el script quedó casando NADA. No fallaba:
imprimía cero atribuciones, indistinguible de un log sin problemas. Comprobado
con el binario (RUST_LOG=info sobre zlib), no deducido. Ahora acepta las dos, y
tiene que seguir aceptándolas porque los logs viejos en disco dicen la vieja.

Además 14 rutas de módulo  en docs, que el barrido anterior no tocó
porque  no es frontera de palabra.
2026-09-09 19:28:48 +00:00

426 lines
23 KiB
Bash

#!/usr/bin/env bash
# mirada-usb.sh — arma el ROOTFS live de mirada por SOFTWARE (mesa-swrast/softpipe, sin GPU driver ni
# firmware) para probar el compositor en GPU ajena (NVIDIA/AMD/lo-que-sea). Genérico: arranca sobre
# simpledrm (framebuffer EFI, anda en cualquier UEFI), virtio-gpu (VM) o nouveau-sin-accel.
#
# Es la ruta "uso normal" del HANDOFF-nvidia-mirada.md: valida el compositor SIN depender del driver.
# La aceleración real por nouveau (NVK/gallium-nouveau + GSP firmware) queda para una v2.
#
# Qué hace:
# 1) Rootfs SLIM: loader musl + busybox + .so runtime tomados de .dev-fs/alpine (la musl userland
# contra la que se construyeron los binarios), NO el 1.2G entero.
# 2) Hydrata los artefactos takana del stack gráfico + mirada desde el store (por hash).
# 3) Cierra el grafo DT_NEEDED: escanea los ELF, y toda .so que falte la trae de .dev-fs/alpine.
# 4) Escribe /sbin/init = lanzador (seatd + `mirada-compositor --drm --greeter`).
# 5) Empaqueta la imagen USB con el kernel linux-generic vía scripts/metal-usb-sdboot.sh.
#
# Uso: scripts/mirada-usb.sh # arma rootfs + imagen work/hammer-mirada-usb.img
# PACKAGE=0 scripts/mirada-usb.sh # sólo el rootfs (work/mirada-rootfs), sin imagen
# Quemar: sudo dd if=work/hammer-mirada-usb.img of=/dev/sdX bs=4M oflag=sync status=progress
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT"
STORE="${STORE:-./store}"
DEVFS="${DEVFS:-.dev-fs/alpine}"
RFS="${RFS:-work/mirada-rootfs}"
HAMMER="${TAKANA:-${HAMMER:-target/release/takana}}"
PACKAGE="${PACKAGE:-1}"
[ -d "$DEVFS" ] || { echo "no existe $DEVFS (corré scripts/bootstrap-devfs.sh)" >&2; exit 1; }
[ -x "$HAMMER" ] || { echo "no existe $HAMMER (cargo build --release)" >&2; exit 1; }
# --- artefactos del store a hydratar (hash explícito para los recién construidos; resto por nombre) ---
declare -A PIN=(
[mesa-swrast]=410e4fb9806598921048f492508725f55ee8475a1e9ef8ea2dc64f357154a0d9
[libxkbcommon]=cef7db4a82882ee942fef5e758344da67c9f3fce6b221610a7db9cb05bfb3b53
)
# Orden: libs primero, binarios mirada al final. Todos aportan .so o /usr/bin. El set sale del
# MANIFIESTO DE OBJETIVO (docs/state/targets.toml, perfil `escritorio-mirada`), que preserva el orden.
PKGS="$("$(dirname "$0")/targets.py" escritorio-mirada)"
resolve() { # nombre -> hash del store (pin si existe, si no el más reciente por mtime)
local n="$1"
if [ -n "${PIN[$n]:-}" ]; then echo "${PIN[$n]}"; return; fi
local d; d=$(ls -dt "$STORE"/*-"$n" 2>/dev/null | grep -E "\-$n\$" | head -1) || true
[ -n "$d" ] || { echo "" ; return; }
basename "$d" | sed "s/-$n\$//"
}
echo "==> rootfs slim en $RFS"
rm -rf "$RFS"; mkdir -p "$RFS"/{bin,sbin,usr/bin,usr/sbin,usr/lib,lib,etc,dev,proc,sys,run,tmp,root}
mkdir -p "$RFS"/usr/lib/dri "$RFS"/var/lib/hammer
# 1) loader musl + libc.so + .so runtime base desde el dev-fs
echo "==> base musl + runtime .so desde $DEVFS"
cp -aL "$DEVFS"/lib/ld-musl-x86_64.so.1 "$RFS"/lib/ 2>/dev/null || true
for so in libc.so libz.so.1 libzstd.so.1 libexpat.so.1 libffi.so.8 libstdc++.so.6 libgcc_s.so.1 libbz2.so.1 liblzma.so.5; do
f=$(find "$DEVFS" -name "$so" 2>/dev/null | head -1)
# -aL: DESREFERENCIA symlinks (libz.so.1→libz.so.1.3.2) ⇒ archivo real con el SONAME que pide NEEDED.
[ -n "$f" ] && cp -aL "$f" "$RFS"/usr/lib/ || true
done
# 2) busybox + applets
echo "==> busybox"
cp -a "$DEVFS"/bin/busybox "$RFS"/bin/busybox
for a in sh mount umount mkdir ln cp mv rm cat echo sleep ls ps kill chmod chown ip udhcpc dmesg mknod switch_root getty login setsid \
findfs blkid sync tee date uname free df head tail grep sed awk cut tr wc find md5sum dd stat readlink basename; do
ln -sf busybox "$RFS/bin/$a"
done
ln -sf /bin/busybox "$RFS/sbin/init.busybox"
# 3) hydratar el stack gráfico + mirada desde el store
echo "==> hydratando artefactos hammer"
for p in $PKGS; do
h=$(resolve "$p")
[ -n "$h" ] || { echo " ✗ $p — no está en el store" >&2; exit 1; }
"$HAMMER" --store "$STORE" hydrate "$h" --into "$RFS" --link static >/dev/null 2>&1
printf ' ✓ %-18s %s\n' "$p" "${h:0:12}"
done
# 4) cierre DT_NEEDED: toda .so requerida y ausente, traerla del dev-fs (desreferenciada).
# Varias rondas fijas (una .so nueva puede arrastrar NEEDED propios). Todo guardado del set -e.
echo "==> cerrando el grafo DT_NEEDED (faltantes ← $DEVFS)"
libdirs=("$RFS/usr/lib" "$RFS/lib" "$RFS/lib64" "$RFS/usr/lib/dri")
have_so() { local s="$1" d; for d in "${libdirs[@]}"; do [ -e "$d/$s" ] && return 0; done; return 1; }
list_elf() { find "$RFS" -type f 2>/dev/null | while read -r f; do head -c4 "$f" 2>/dev/null | grep -q ELF && echo "$f"; done; }
needed_sonames() { local e; list_elf | while read -r e; do readelf -d "$e" 2>/dev/null | awk -F'[][]' '/NEEDED/{print $2}'; done | sort -u; }
for round in 1 2 3; do
falto=0
for so in $(needed_sonames); do
[ -n "$so" ] || continue
have_so "$so" && continue
f=$(find "$DEVFS" -name "$so" 2>/dev/null | head -1 || true)
if [ -n "$f" ]; then cp -aL "$f" "$RFS/usr/lib/" && echo " + $so"; else echo " ! FALTA (no en dev-fs): $so"; falto=1; fi
done
[ "$falto" -eq 0 ] && break
done
: <<'DEADCODE'
DEADCODE
# 5) config mínima: passwd/group/pam + ld.so path
echo "==> config (passwd/group/pam)"
cat > "$RFS/etc/passwd" <<'EOF'
root:x:0:0:root:/root:/bin/sh
EOF
cat > "$RFS/etc/group" <<'EOF'
root:x:0:
video:x:27:root
input:x:28:root
seat:x:29:root
EOF
mkdir -p "$RFS/etc/pam.d"
# PAM permisivo: el greeter RENDERIZA antes de autenticar; con esto un login vacío/root pasa.
cat > "$RFS/etc/pam.d/other" <<'EOF'
auth sufficient pam_permit.so
account sufficient pam_permit.so
password sufficient pam_permit.so
session sufficient pam_permit.so
EOF
for svc in login mirada mirada-greeter system-auth; do cp "$RFS/etc/pam.d/other" "$RFS/etc/pam.d/$svc"; done
# ld musl busca en /usr/lib y /lib por defecto; dejamos un ld-musl path explícito por las dudas.
mkdir -p "$RFS/etc"
# musl REEMPLAZA su path por defecto con este fichero ⇒ hay que listar TODOS los dirs, incl. /lib64
# (linux-pam instala ahí) y /usr/lib/dri (drivers mesa).
printf '/usr/lib\n/lib\n/lib64\n/usr/lib/dri\n' > "$RFS/etc/ld-musl-x86_64.path"
# 5b) DATOS XKB (xkeyboard-config): libxkbcommon compila el keymap por defecto (evdev/pc105/us) desde
# /usr/share/X11/xkb; sin estos datos xkb_keymap_new falla y el compositor SEGFAULTEA en
# libxkbcommon. Son datos de texto/XML AGNÓSTICOS de arquitectura ⇒ los tomamos del host.
# (TODO soberanía: recipes/xkeyboard-config.toml — hoy no existe; para la imagen de test basta.)
XKB_SRC="${XKB_SRC:-/usr/share/X11/xkb}"
if [ -d "$XKB_SRC" ]; then
echo "==> datos XKB (xkeyboard-config) ← $XKB_SRC"
mkdir -p "$RFS/usr/share/X11"
cp -aL "$XKB_SRC" "$RFS/usr/share/X11/xkb"
else
echo " ⚠ no encuentro datos XKB en $XKB_SRC — el compositor crasheará en libxkbcommon" >&2
fi
# 5c) STACK GL/VULKAN por SOFTWARE + nouveau — de Alpine edge (musl, mismo libLLVM que el dev-fs).
# Por qué NO la mesa-swrast de takana: su softpipe basta para el COMPOSITOR, pero el greeter (wgpu)
# necesita un GL/Vulkan más completo. La mesa 26.1.1 de Alpine trae:
# · llvmpipe (swrast_dri.so + libgallium + libLLVM.so.22) → software GL robusto (compositor),
# · lavapipe (libvulkan_lvp.so + ICD) → software Vulkan (backend primario de wgpu),
# · nouveau_dri.so → GL por HARDWARE en la NVIDIA (si nouveau bindea con firmware GSP).
# Es la vía pragmática de la imagen de TEST (deuda de soberanía: build LLVM+mesa+NVK desde fuente).
# El greeter/Vello igual EXIGE shaders float16 ⇒ su login sólo pinta con GPU real (ver init).
MESAROOT="${MESAROOT:-work/alpine-mesa-root}"
if [ ! -e "$MESAROOT/usr/lib/libgallium-26.1.1.so" ]; then
echo "==> bajando mesa+lavapipe de Alpine edge (apk) → $MESAROOT"
APK="$DEVFS/sbin/apk"; ALD="$DEVFS/lib/ld-musl-x86_64.so.1"
rm -rf "$MESAROOT"; mkdir -p "$MESAROOT"
"$ALD" --library-path "$DEVFS/usr/lib:$DEVFS/lib" "$APK" add --root "$PWD/$MESAROOT" --initdb --usermode --no-cache \
--repository https://dl-cdn.alpinelinux.org/alpine/edge/main \
--repository https://dl-cdn.alpinelinux.org/alpine/edge/community \
--arch x86_64 --allow-untrusted \
mesa-dri-gallium mesa-egl mesa-gles mesa-gbm mesa-vulkan-swrast vulkan-loader >/dev/null 2>&1 \
|| { echo " ✗ apk falló (¿red?); no puedo bajar mesa" >&2; exit 1; }
fi
echo "==> overlay mesa+lavapipe en el rootfs"
cp -aL "$MESAROOT"/usr/lib/*.so* "$RFS/usr/lib/" 2>/dev/null || true
for sub in dri gbm; do
[ -d "$MESAROOT/usr/lib/$sub" ] && { mkdir -p "$RFS/usr/lib/$sub"; cp -aL "$MESAROOT/usr/lib/$sub/"*.so "$RFS/usr/lib/$sub/" 2>/dev/null || true; }
done
mkdir -p "$RFS/usr/share/vulkan/icd.d"
cp -aL "$MESAROOT"/usr/share/vulkan/icd.d/*.json "$RFS/usr/share/vulkan/icd.d/" 2>/dev/null || true
[ -d "$MESAROOT/usr/share/drirc.d" ] && cp -aL "$MESAROOT/usr/share/drirc.d" "$RFS/usr/share/" 2>/dev/null || true
rm -f "$RFS/usr/lib/libgcc_s.so" # symlink glibc espurio (el musl .so.1 sí está)
# cerrar deps de los drivers/ICD nuevos ← alpine-root + dev-fs
for round in 1 2 3; do
for so in $(needed_sonames); do
[ -n "$so" ] || continue
have_so "$so" && continue
f=$(find "$MESAROOT/usr/lib" "$DEVFS" -name "$so" 2>/dev/null | grep -v ld-linux | head -1 || true)
[ -n "$f" ] && cp -aL "$f" "$RFS/usr/lib/" && echo " + $so" || true
done
done
# 5c-bis) libudev = SIEMPRE el de libudev-zero. El apk de mesa arrastra `eudev-libs` y el overlay de
# arriba (`cp *.so*`) PISA el libudev.so.1 de libudev-zero con el de eudev. Sin udevd corriendo, el
# libudev de eudev enumera CERO dispositivos de entrada (no hay ID_INPUT sin la db de udevd) y
# libinput los ignora EN SILENCIO ⇒ pantalla viva, teclado y mouse muertos. Es EXACTAMENTE lo que
# pasó en el tester NVIDIA (2026-07-16); reproducido y verificado A/B en QEMU (usb-kbd: eudev = 0
# dispositivos; libudev-zero = 5 y Ctrl+Alt+BackSpace corta el compositor). La GPU no lo delata:
# smithay la encuentra por syspath, sin propiedades udev — por eso "grafica bien" no salva al input.
UZ=$(resolve libudev-zero)
[ -n "$UZ" ] || { echo "✗ libudev-zero no está en el store" >&2; exit 1; }
echo "==> libudev.so.1 ← libudev-zero (pisa el de eudev que arrastró el apk de mesa)"
rm -f "$RFS/usr/lib/libudev.so.1" "$RFS/usr/lib/libudev.so.1."* # el de eudev + sus versionados (.so.1.6.3)
cp -L "$STORE/$UZ-libudev-zero/usr/lib/libudev.so.1" "$RFS/usr/lib/libudev.so.1"
chmod 755 "$RFS/usr/lib/libudev.so.1"
cmp -s "$STORE/$UZ-libudev-zero/usr/lib/libudev.so.1" "$RFS/usr/lib/libudev.so.1" \
|| { echo "✗ el libudev.so.1 del rootfs NO es el de libudev-zero — input muerto garantizado" >&2; exit 1; }
# 5d) FIRMWARE nouveau (aceleración 3D). Pascal/Maxwell2 (GP10x/GM20x) piden los blobs firmados de
# ctxsw (gr/) + acr/sec2 para dar GL por hardware; sin ellos nouveau da display pero NO 3D ⇒ el
# greeter caería a software (sin float16). Turing+ (RTX) piden ADEMÁS gsp/. CHIP=gp106 para la GTX
# 1060. FW_SRC = árbol linux-firmware del host (o pasá el tuyo). NO es soberano (blob firmado NVIDIA).
CHIP="${CHIP:-gp106}"
FW_SRC="${FW_SRC:-/lib/firmware}"
if [ -n "$CHIP" ] && [ -d "$FW_SRC/nvidia/$CHIP" ]; then
echo "==> firmware nouveau nvidia/$CHIP$FW_SRC"
mkdir -p "$RFS/lib/firmware/nvidia"
cp -aL "$FW_SRC/nvidia/$CHIP" "$RFS/lib/firmware/nvidia/$CHIP"
# DESCOMPRIMIR .bin.zst/.xz → .bin: el request_firmware de nouveau busca `.bin`; el kernel sólo
# descomprime al vuelo si tiene FW_LOADER_COMPRESS (incierto en linux-generic). Descomprimir asegura.
find "$RFS/lib/firmware/nvidia/$CHIP" -name '*.zst' -exec sh -c 'zstd -q -d -f "$1" && rm -f "$1"' _ {} \; 2>/dev/null || true
find "$RFS/lib/firmware/nvidia/$CHIP" -name '*.xz' -exec sh -c 'xz -q -d -f "$1"' _ {} \; 2>/dev/null || true
elif [ -n "$CHIP" ]; then
echo " ⚠ no encuentro firmware nvidia/$CHIP en $FW_SRC — nouveau no dará 3D (greeter caerá a sw)" >&2
fi
# 6) /sbin/init = lanzador de mirada (software)
echo "==> lanzador /sbin/init"
cat > "$RFS/sbin/init" <<'INIT'
#!/bin/sh
# init de la imagen live de mirada. Arranca seatd + el compositor en modo greeter, y — lo importante para
# probar en una GPU AJENA — vuelca TODA la telemetría a la partición FAT32 del propio pendrive, para
# traerlo de vuelta con la información. El rootfs es un initramfs (RAM): sin esto no sobrevive NADA.
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
/bin/busybox mount -t proc proc /proc 2>/dev/null || true
/bin/busybox mount -t sysfs sys /sys 2>/dev/null || true
/bin/busybox mount -t devtmpfs dev /dev 2>/dev/null || true
/bin/busybox mount -t tmpfs tmpfs /run 2>/dev/null || true
/bin/busybox mkdir -p /run/user/0 /dev/pts /dev/shm /run/seatd
/bin/busybox mount -t devpts devpts /dev/pts 2>/dev/null || true
/bin/busybox chmod 0700 /run/user/0
# ---------------------------------------------------------------- telemetría persistente al pendrive
# La partición la crea metal-usb-sdboot.sh (DATA_MB/DATA_LABEL). Se monta con -o sync: cada escritura va
# al medio, así un cuelgue duro de la GPU o un corte de luz no se lleva los logs.
LOG=/telemetry
TELOK=0
/bin/busybox mkdir -p $LOG
for try in 1 2 3 4 5 6 7 8 9 10; do
DEV=$(/bin/busybox findfs LABEL=MIRADALOG 2>/dev/null)
[ -n "$DEV" ] && break
/bin/busybox sleep 1 # el USB puede tardar en enumerar
done
if [ -n "$DEV" ] && /bin/busybox mount -t vfat -o rw,sync "$DEV" $LOG 2>/dev/null; then
TELOK=1
else
echo " !! NO pude montar la partición de telemetría (LABEL=MIRADALOG) — los logs quedan sólo en RAM"
LOG=/run
fi
# Cada arranque en su propia carpeta: run-0001, run-0002… (sin RTC confiable, se numera)
N=1
while [ -d "$LOG/run-$(printf %04d $N)" ]; do N=$((N+1)); done
RUN="$LOG/run-$(printf %04d $N)"
/bin/busybox mkdir -p "$RUN"
echo " ## telemetría → $RUN (persistente=$TELOK)"
# La bitácora interna del compositor (dlog → eventos.log + reportes de panic) también al pendrive:
# sin esto caía en ~/.local/state (RAM) y un viaje sin datos es un viaje perdido.
export MIRADA_DEBUG_DIR="$RUN"
snap() { # snap <fichero> <titulo> <comando…>
local f="$RUN/$1"; shift; local t="$1"; shift
{ echo "===== $t ====="; "$@" 2>&1; echo; } >> "$f"
/bin/busybox sync
}
# PCI desde sysfs: este busybox NO trae el applet `lspci`. Con vendor/device/class/driver alcanza para
# saber QUÉ GPU hay y, sobre todo, QUÉ DRIVER la bindeó (nouveau vs nada) — que es el dato del viaje.
# vendor 0x10de = NVIDIA · class 0x0300xx = VGA · 0x0302xx = 3D controller
pci_dump() {
for d in /sys/bus/pci/devices/*/; do
[ -r "$d/class" ] || continue
drv=$(/bin/busybox readlink "$d/driver" 2>/dev/null | /bin/busybox sed 's|.*/||')
echo "$(/bin/busybox basename $d) class=$(/bin/busybox cat $d/class) vendor=$(/bin/busybox cat $d/vendor) device=$(/bin/busybox cat $d/device) driver=${drv:-NINGUNO}"
done
}
gpu_line() {
for d in /sys/bus/pci/devices/*/; do
[ -r "$d/class" ] || continue
case "$(/bin/busybox cat $d/class)" in
0x0300*|0x0302*)
drv=$(/bin/busybox readlink "$d/driver" 2>/dev/null | /bin/busybox sed 's|.*/||')
echo "$(/bin/busybox basename $d) vendor=$(/bin/busybox cat $d/vendor) device=$(/bin/busybox cat $d/device) driver=${drv:-NINGUNO}" ;;
esac
done
}
# --- hardware / kernel: qué GPU hay, si nouveau bindeó, si cargó el firmware
snap 01-hardware.txt "uname" /bin/busybox uname -a
snap 01-hardware.txt "cmdline" /bin/busybox cat /proc/cmdline
snap 01-hardware.txt "cpu" /bin/busybox head -20 /proc/cpuinfo
snap 01-hardware.txt "memoria" /bin/busybox free -m
snap 01-hardware.txt "PCI (sysfs; vendor 0x10de=NVIDIA)" pci_dump
snap 01-hardware.txt "modulos" /bin/busybox cat /proc/modules
snap 01-hardware.txt "DRM /dev/dri" /bin/busybox ls -la /dev/dri
snap 01-hardware.txt "DRM /sys" /bin/busybox find /sys/class/drm -maxdepth 1
snap 01-hardware.txt "nouveau sysfs" /bin/busybox find /sys/module/nouveau -maxdepth 2
snap 01-hardware.txt "input" /bin/busybox cat /proc/bus/input/devices
snap 01-hardware.txt "firmware nvidia" /bin/busybox find /lib/firmware/nvidia -type f
for c in /sys/class/drm/card*/status /sys/class/drm/card*/modes /sys/class/drm/card*/enabled; do
[ -r "$c" ] && snap 02-pantallas.txt "$c" /bin/busybox cat "$c"
done
# dmesg TEMPRANO: acá se ve si nouveau bindeó y si el firmware gp106 cargó (o falló).
snap 03-dmesg-early.txt "dmesg (antes de mirada)" /bin/busybox dmesg
export XDG_RUNTIME_DIR=/run/user/0
export LIBGL_DRIVERS_PATH=/usr/lib/dri
export LIBSEAT_BACKEND=seatd
export XDG_SEAT=seat0
export XDG_SESSION_TYPE=wayland
export MIRADA_GREETER_BIN=/usr/bin/mirada-greeter
export XKB_DEFAULT_LAYOUT=us
export XKB_CONFIG_ROOT=/usr/share/X11/xkb
# Telemetría MÁXIMA del stack gráfico: por qué wgpu eligió (o no) el backend, y si hay float16.
export RUST_LOG="${RUST_LOG:-info,wgpu_core=debug,wgpu_hal=debug,mirada=debug,smithay=info}"
export RUST_BACKTRACE=full
export EGL_LOG_LEVEL=debug
export MESA_DEBUG=1
export LIBGL_DEBUG=verbose
# GPU: por defecto se INTENTA hardware (nouveau, si el firmware deja hacer modeset) y, si no, mesa cae a
# swrast/llvmpipe sobre simpledrm. Con `mirada.sw` en la cmdline se FUERZA software (necesario en la VM
# virtio-sin-virgl de validación). El compositor (smithay/GlesRenderer) corre en ambos; el greeter
# (wgpu/Vello) NECESITA shaders float16 (SHADER_FLOAT16_IN_FLOAT32) que llvmpipe/lavapipe NO dan ⇒ su
# login sólo pinta con GPU real (nouveau). Ver HANDOFF/README.
if grep -qw 'mirada.sw' /proc/cmdline 2>/dev/null; then
echo " ## modo SOFTWARE forzado (mirada.sw): compositor sí; greeter no (sin float16)"
MODO="software (mirada.sw)"
export LIBGL_ALWAYS_SOFTWARE=1
export GALLIUM_DRIVER=llvmpipe
export VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json
export VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json
else
# HARDWARE (nouveau). En Pascal (GTX 10xx / GP10x) NVK NO existe (sólo Turing+) ⇒ el greeter debe ir
# por el backend GL de wgpu sobre gallium-nouveau (que sí da unpackHalf2x16 = float16). Forzamos GL y
# tapamos lavapipe: si dejáramos Vulkan, wgpu caería a lavapipe (software, sin float16) y fallaría.
echo " ## modo HARDWARE (nouveau GL): greeter por gallium-nouveau"
MODO="hardware (nouveau GL)"
export WGPU_BACKEND=gl
export VK_ICD_FILENAMES=/dev/null
fi
{ echo "modo : $MODO"; echo "cmdline : $(/bin/busybox cat /proc/cmdline)"; echo "DRM : $(/bin/busybox ls /dev/dri 2>/dev/null | tr '\n' ' ')";
echo "GPU (PCI) : $(gpu_line)";
echo "persistente: $TELOK"; echo "RUST_LOG : $RUST_LOG"; } > "$RUN/00-resumen.txt"
/bin/busybox sync
echo ""
echo " ## hammer · mirada live"
echo " ## DRM: $(ls /dev/dri 2>/dev/null | tr '\n' ' ')"
echo " ## telemetría: $RUN (persistente=$TELOK)"
echo ""
# seatd en background (gestiona el DRM master y la sesión sobre la VT).
/usr/bin/seatd -g video > "$RUN/04-seatd.log" 2>&1 &
/bin/busybox sleep 1
# El compositor: su salida va A LA VEZ a la pantalla y al pendrive (tee), para verlo y llevárselo.
echo "== lanzando mirada-compositor --drm --greeter =="
/usr/bin/mirada-compositor --drm --greeter 2>&1 | /bin/busybox tee "$RUN/05-mirada.log"
RC=$?
# --- post-mortem: lo que pasó DESPUÉS de correr mirada (cuelgues de GPU, oops, reset de nouveau)
snap 06-dmesg-final.txt "dmesg (después de mirada)" /bin/busybox dmesg
snap 06-dmesg-final.txt "rc del compositor" echo "exit=$RC"
snap 07-estado-final.txt "procesos" /bin/busybox ps
snap 07-estado-final.txt "memoria" /bin/busybox free -m
snap 07-estado-final.txt "DRM final" /bin/busybox ls -la /dev/dri
/bin/busybox sync
echo ""
echo "== mirada terminó (exit=$RC) — telemetría VOLCADA en $RUN =="
[ "$TELOK" = "1" ] && {
echo " Ficheros en el pendrive (partición MIRADALOG):"
/bin/busybox ls -la "$RUN"
echo ""
echo " >> Podés apagar y traer el pendrive: los logs ya están sincronizados al medio."
echo " >> Si querés reintentar antes de apagar, corré: relanzar"
} || echo " !! sin partición de telemetría: los logs están en RAM y se pierden al apagar"
echo ""
# `relanzar`: reintentar el compositor sin reiniciar; cada intento crea su propio run-NNNN.
cat > /usr/bin/relanzar <<'RL'
#!/bin/sh
echo "relanzando init (nuevo run-NNNN en el pendrive)…"
exec /sbin/init
RL
chmod +x /usr/bin/relanzar
echo "== shell de rescate (telemetría en $RUN; 'relanzar' para reintentar) =="
exec /bin/sh
INIT
chmod +x "$RFS/sbin/init"
# NO creamos /init: metal-usb-sdboot.sh escribe su propio /init (monta pseudofs y hace exec /sbin/init).
echo "==> rootfs listo: $RFS ($(du -sh "$RFS" | cut -f1))"
# 7) empaquetar imagen USB con el kernel linux-generic
if [ "$PACKAGE" = "1" ]; then
KGEN=$(ls -dt "$STORE"/*-linux-generic/boot/bzImage 2>/dev/null | head -1)
[ -n "$KGEN" ] || { echo "no encuentro el kernel linux-generic (build recipes/linux-generic.toml)" >&2; exit 1; }
echo "==> empaquetando USB (kernel linux-generic: $KGEN)"
# Cmdline por defecto = SOFTWARE (mirada.sw) + simpledrm (nouveau.modeset=0): garantiza que el
# COMPOSITOR arranca y se VE mirada en la pantalla de la NVIDIA en el primer boot, sin depender del
# driver ni del firmware. Para el greeter completo (login) hace falta GPU real: en el menú de
# systemd-boot, tecla 'e', quitá `mirada.sw nouveau.modeset=0` para que nouveau intente el hardware
# (requiere firmware GSP en /lib/firmware — ver FW_SRC).
# Default = HARDWARE nouveau (ya horneamos el firmware gp106): nouveau maneja la GTX 1060 y el greeter
# pinta por gallium-nouveau GL. Si nouveau diera problemas, en el menú (tecla 'e') agregá
# `mirada.sw nouveau.modeset=0` para caer a software (compositor sí, greeter no).
# DATA_MB: partición FAT32 de TELEMETRÍA. El rootfs de esta imagen es un initramfs = RAM pura, así que
# sin esta partición no sobrevive ni un log al apagado — y la GPU a probar está en otra máquina, lejos.
# El init la monta por LABEL=MIRADALOG con -o sync y vuelca ahí todo (ver /sbin/init).
# FAT32 ⇒ el pendrive se lee después desde cualquier SO, sin herramientas.
# `gpt` (force_gpt): la imagen dd-eada deja el GPT de respaldo en el MEDIO del disco (fin de la
# imagen, no del pendrive) y hay firmwares UEFI que lo "reparan" en el POST — reescriben el header
# primario apuntando las entradas a otro LBA sin copiarlas ⇒ primario INVÁLIDO. Linux, sin `gpt`,
# NO cae al respaldo con primario roto (block/partitions/efi.c) ⇒ cero particiones ⇒ findfs no ve
# MIRADALOG y la telemetría muere en RAM (pasó en el tester NVIDIA, 2026-07-16). Con `gpt` el kernel
# usa el respaldo y la telemetría sobrevive incluso con el primario mutilado.
ROOTFS="$RFS" KERNEL="$KGEN" IMG="work/hammer-mirada-usb.img" \
DATA_MB="${DATA_MB:-512}" DATA_LABEL="MIRADALOG" \
OPTIONS="${CMDLINE:-console=tty0 loglevel=4 gpt}" \
scripts/metal-usb-sdboot.sh
echo ""
echo "✓ imagen: work/hammer-mirada-usb.img"
echo " Al volver, leé la telemetría montando la partición MIRADALOG (FAT32) — carpetas run-NNNN/."
fi