#!/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 hammer 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="${HAMMER:-target/release/hammer}" 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. PKGS="mesa-swrast wayland libdrm seatd libinput libudev-zero libxkbcommon pixman linux-pam mtdev libevdev mirada-compositor mirada-greeter mirada-ctl" 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; 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 hammer: 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 # 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 (software/swrast). Arranca seatd y el compositor en modo greeter. 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 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 # GPU: por defecto se INTENTA hardware (nouveau, si el firmware GSP lo deja hacer modeset) y, si no, # mesa cae a swrast/llvmpipe sobre simpledrm solo. Con `mirada.sw` en la cmdline se FUERZA software # (necesario en la VM virtio-sin-virgl de validación, donde mesa intentaría el driver virtio-3D y # fallaría). El compositor (smithay/GlesRenderer) corre en ambos; el greeter (wgpu/Vello) NECESITA la # capacidad de shaders float16 (SHADER_FLOAT16_IN_FLOAT32) que los rasterizadores software # (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)" 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 fi # En modo hardware el loader Vulkan escanea /usr/share/vulkan/icd.d/ y elige el mejor ICD (nouveau NVK # si está, si no lavapipe); no fijamos VK_ICD_FILENAMES para no tapar el hardware. echo "" echo " ## hammer · mirada live" echo " ## DRM: $(ls /dev/dri 2>/dev/null | tr '\n' ' ')" echo "" # seatd en background (gestiona el DRM master y la sesión sobre la VT). /usr/bin/seatd -g video >/run/seatd.log 2>&1 & sleep 1 echo "== lanzando mirada-compositor --drm --greeter ==" /usr/bin/mirada-compositor --drm --greeter 2>&1 | /bin/busybox tee /run/mirada.log echo "" echo "== mirada terminó — shell de rescate (logs en /run/mirada.log, /run/seatd.log) ==" 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). ROOTFS="$RFS" KERNEL="$KGEN" IMG="work/hammer-mirada-usb.img" \ OPTIONS="${CMDLINE:-mirada.sw nouveau.modeset=0 console=tty0 loglevel=4}" \ scripts/metal-usb-sdboot.sh echo "" echo "✓ imagen: work/hammer-mirada-usb.img" fi