Files
hammer/scripts/kde/metal-desktop-image-dual.sh
T
sergioandClaude Opus 5 84e088fd13 imágenes: PID1 sale del artefacto sellado, no del directorio base congelado
Las 4 imágenes de escritorio (GNOME + los 3 de KDE: qemu, metal, dual) fundían `work/metal-rootfs`
por hardlinks y se llevaban SU arje-zero, que era del 2026-06-19: ese directorio se armó a mano en
la campaña de metal y nadie lo regeneraba al re-sellar la receta. Resultado: todas arrancaban con un
PID1 de hace mes y medio, en silencio.

No es hipotético — se comió un fix real. El `identity mismatch` del bus del fractal estaba arreglado
río arriba y la VM seguía imprimiendo el mensaje viejo porque el binario de la imagen no venía de la
receta (ver el comentario de recipes/arje-zero.toml).

El síntoma engaña porque el directorio base es LEGÍTIMO para todo lo demás —busybox, firmware, el
kernel EFI-stub, la estructura de /etc—: sólo la pieza que TAMBIÉN es receta se queda atrás, y justo
esa es PID1. La regla que queda escrita en el helper: si algo del rootfs tiene receta, la imagen lo
toma del artefacto sellado, no de la copia congelada.

El helper va en scripts/lib/ y no inline ×4 a propósito: el porqué es largo y vale una sola copia.
Elige el artefacto por `hammer hash` (el de la receta de HOY, no el más nuevo por fecha, que miente
en cuanto conviven dos) y ABORTA si falta, porque seguir con el PID1 congelado es exactamente el
modo de falla que cierra.

Probado: las 4 pasan `bash -n` y la imagen GNOME lo ejecuta bien sourceado (`✓ e570c1482432 (el de
work/metal-rootfs era de 2026-06-19)`), con la VM ya validada booteando ese PID1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 19:52:00 -04:00

140 lines
7.5 KiB
Bash
Executable File

#!/usr/bin/env bash
# metal-desktop-image-dual.sh — imagen de disco EFI KDE para METAL que aguanta DOS GPUs (Intel i915 y
# NVIDIA Pascal/nouveau) con render por SOFTWARE (mesa-llvmpipe soberano). USB "live" que se adapta a la
# máquina donde se bootee. Ver [[kde-metal-qemu-desktop]] y el runbook docs/runbooks/kde-qemu-desktop.md.
#
# Por qué software y no iris/nouveau HW: el mesa hammer-built es swrast/llvmpipe; una mesa HW con nouveau
# hoy sólo existe como binario Alpine (rompe soberanía). llvmpipe sobre el KMS del kernel (i915 O nouveau)
# funciona idéntico en ambas máquinas y reusa los fixes ya validados. HW-accel = iteración siguiente.
#
# work/metal-rootfs base: arje-zero PID1 + busybox + firmware i915 (static ⇒ hay que inyectar musl)
# work/kde-metal-rootfs KDE runtime-completo (Qt/KF6/Plasma/apps)
# + store/*-mesa-llvmpipe swrast/llvmpipe (software GL) + libLLVM.18
# + firmware nvidia/gp106 de work/mirada-rootfs (nouveau necesita el blob firmado para modeset en Pascal)
# + kernel linux-generic (i915 + nouveau + radeon builtin) ← NO linux-metal (ese apaga nouveau)
# → work/hammer-kde-metal-dual.img
#
# Uso: scripts/kde/metal-desktop-image-dual.sh
# BOOT=1 scripts/kde/metal-desktop-image-dual.sh # + arranca en OVMF para validar el boot chain
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
BASE="${BASE:-work/metal-rootfs}"
KDE="${KDE:-work/kde-metal-rootfs}"
MESA="${MESA:-$(ls -dt store/*-mesa-llvmpipe 2>/dev/null | head -1)}"
LLVM="${LLVM:-$(ls -dt store/*-llvm18 2>/dev/null | head -1)}"
KERNEL="${KERNEL:-$(ls -dt store/*-linux-metal-dual/boot/bzImage 2>/dev/null | head -1)}"
NVFW_SRC="${NVFW_SRC:-work/mirada-rootfs/lib/firmware/nvidia}"
MERGED="${MERGED:-work/metal-desktop-dual-rootfs}"
IMG="${IMG:-work/hammer-kde-metal-dual.img}"
for x in "$BASE" "$KDE"; do [ -d "$x" ] || { echo "falta $x" >&2; exit 1; }; done
[ -n "$MESA" ] && [ -d "$MESA" ] || { echo "falta mesa-llvmpipe en store/" >&2; exit 1; }
[ -n "$LLVM" ] && [ -d "$LLVM" ] || { echo "falta llvm18 en store/" >&2; exit 1; }
[ -n "$KERNEL" ] && [ -r "$KERNEL" ] || { echo "falta kernel linux-generic (build recipes/linux-generic.toml)" >&2; exit 1; }
echo "==> fundiendo rootfs por hardlinks (base metal + KDE)"
rm -rf "$MERGED"
cp -al "$BASE" "$MERGED"
cp -al --remove-destination "$KDE"/. "$MERGED"/
. "$ROOT/scripts/lib/pid1-desde-store.sh"
inyectar_pid1 "$MERGED" "$BASE"
echo "==> inyectando mesa-llvmpipe (software GL) desde $MESA"
cp -a "$MESA"/usr/lib/dri/. "$MERGED"/usr/lib/dri/ 2>/dev/null || true
for so in libglapi libgbm libEGL libGLESv2 libgallium; do
cp -a "$MESA"/usr/lib/$so* "$MERGED"/usr/lib/ 2>/dev/null || true
done
ls "$MERGED"/usr/lib/dri/*swrast* >/dev/null 2>&1 || { echo " ✗ swrast no entró" >&2; exit 1; }
echo "==> inyectando libLLVM.18 + libgcc_s + libstdc++ (llvmpipe JIT los NEEDea)"
cp -a "$LLVM"/usr/lib/libLLVM.so.18.1 "$MERGED"/usr/lib/
ln -sf libLLVM.so.18.1 "$MERGED"/usr/lib/libLLVM.so.18
ln -sf libLLVM.so.18.1 "$MERGED"/usr/lib/libLLVM.so
GCCS="${GCCS:-.dev-fs/alpine/usr/lib/libgcc_s.so.1}"; [ -r "$GCCS" ] && cp -a "$GCCS" "$MERGED"/usr/lib/ || echo " ⚠ sin libgcc_s"
STDCXX="${STDCXX:-.dev-fs/alpine/usr/lib/libstdc++.so.6}"; [ -r "$STDCXX" ] && cp -a "$STDCXX"* "$MERGED"/usr/lib/ 2>/dev/null || echo " ⚠ sin libstdc++"
echo "==> inyectando musl (loader + libc) — los binarios KDE son dinámicos, la base metal es estática"
MUSL="${MUSL:-/usr/lib/musl/lib/libc.so}"
[ -r "$MUSL" ] || { echo "no encuentro musl libc ($MUSL)" >&2; exit 1; }
mkdir -p "$MERGED/usr/lib/musl/lib" "$MERGED/lib"
cp -a "$MUSL" "$MERGED/usr/lib/musl/lib/libc.so"
ln -sf /usr/lib/musl/lib/libc.so "$MERGED/lib/ld-musl-x86_64.so.1"
ln -sf /usr/lib/musl/lib/libc.so "$MERGED/usr/lib/libc.so"
echo "==> inyectando firmware NVIDIA gp106 (nouveau lo exige para modeset en Pascal; i915 ya está en la base)"
if [ -d "$NVFW_SRC" ]; then
mkdir -p "$MERGED/lib/firmware/nvidia"
cp -a "$NVFW_SRC"/. "$MERGED/lib/firmware/nvidia/"
echo " ✓ nvidia fw: $(ls "$MERGED/lib/firmware/nvidia" | tr '\n' ' ')"
else
echo " ⚠ sin $NVFW_SRC — la rama NVIDIA no hará modeset (Intel sí)"
fi
echo "==> instalando /usr/bin/plasma-start (metal software-GL, lanzamiento manual)"
install -Dm755 scripts/kde/plasma-start-metal-sw.sh "$MERGED/usr/bin/plasma-start"
# --- getty en tty1: el prompt TIENE que salir por la PANTALLA -----------------------------------------
# El CMDLINE horneado es "console=tty0 console=ttyS0,115200 …" y el kernel hace /dev/console = la ÚLTIMA
# console= ⇒ ttyS0. La seed card de la base metal supervisa un solo getty, sobre `console` ⇒ en un metal
# sin puerto serie el shell nace INVISIBLE: la pantalla se congela en el último printk del kernel (los
# printk sí van a las dos consolas) y parece que el arranque murió, cuando en realidad está vivo. Eso
# nunca se vio en QEMU porque siempre validamos con -nographic, donde el serial ES la pantalla.
# Fix acá y no en el cmdline: un getty sobre tty1 es independiente del cmdline (en metal siempre hay VT)
# y no obliga a recompilar/re-sellar el kernel. El getty de `console` se conserva (debug por serial).
echo "==> añadiendo getty en tty1 a la seed card (el prompt de console va al SERIAL, invisible en metal)"
cat > "$MERGED/usr/bin/console-login" <<'LOGIN'
#!/bin/sh
# arje-zero lanza el getty con envp VACÍO ⇒ sin PATH no se resuelve ni `cat` ni `plasma-start`.
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
export HOME=/root TERM=linux
[ -r /etc/motd ] && /bin/busybox cat /etc/motd
exec /bin/sh
LOGIN
chmod 0755 "$MERGED/usr/bin/console-login"
python3 - "$MERGED/ente/seed.card.json" <<'PY'
# OJO: $MERGED es un árbol de HARDLINKS a la base metal ⇒ escribir in-place mutaría también
# work/metal-rootfs (y toda otra imagen que comparta el inodo). Se escribe a un temporal y se
# os.replace ⇒ se reemplaza la ENTRADA de directorio, el inodo compartido queda intacto.
import json, sys, copy, os
p = sys.argv[1]
card = json.load(open(p))
gen = card["genesis"]
gen[:] = [g for g in gen if g["label"] != "tty1-getty"] # idempotente
base = next(g for g in gen if g["label"] == "console-getty")
tty1 = copy.deepcopy(base)
tty1["id"] = "01HQAR53D4M2NBV8KZTYXFQA03"
tty1["label"] = "tty1-getty"
tty1["payload"]["Native"]["argv"] = ["getty", "-n", "-l", "/usr/bin/console-login", "38400", "tty1"]
gen.append(tty1)
json.dump(card, open(p + ".new", "w"), indent=2, sort_keys=True)
os.replace(p + ".new", p)
print(" ✓ genesis: " + ", ".join(g["label"] for g in gen))
PY
rm -f "$MERGED/etc/motd" # romper el hardlink: `cat >` escribiría el inodo COMPARTIDO con work/metal-rootfs
cat > "$MERGED/etc/motd" <<'MOTD'
#-- hammer :: escritorio KDE Plasma 6 (metal, dual-GPU software) ------------
Render por software (llvmpipe soberano) sobre i915 (Intel) o nouveau (NVIDIA).
Arrancar el escritorio: plasma-start
Logs si algo falla: /tmp/kwin.log /tmp/plasmashell.log
Ver GPU detectada: ls /dev/dri ; dmesg | grep -iE 'i915|nouveau|drm'
---------------------------------------------------------------------------
MOTD
mkdir -p "$MERGED/root/.config" "$MERGED/root/.cache" "$MERGED/run/user/0" "$MERGED/tmp"
chmod 1777 "$MERGED/tmp"
echo "==> rootfs fundido: $(du -sh "$MERGED" | cut -f1)"
need_mb=$(du -sm "$MERGED" | cut -f1)
ROOT_SIZE="${ROOT_SIZE:-$(( need_mb + 1536 ))}"
echo "==> ROOT_SIZE=${ROOT_SIZE} MiB · kernel: $(basename "$(dirname "$(dirname "$KERNEL")")")"
export ROOTFS="$MERGED" IMG ROOT_SIZE KERNEL
export ESP_MB="${ESP_MB:-160}" STORE_SIZE="${STORE_SIZE:-512}" STATE_SIZE="${STATE_SIZE:-768}"
exec ./scripts/install-image-efi.sh