🔩 cosmic/metal: imagen EFI con iris HW — el viaje que cierra ScreenCast

Hermano de kde/metal-desktop-image-dual.sh, pero con la decisión INVERTIDA y ése es
el motivo de existir. La de KDE va por mesa-llvmpipe a propósito (tenía que aguantar
también una NVIDIA Pascal, cuya mesa HW sólo existe como binario Alpine ⇒ rompería
soberanía).

Acá el objetivo es cerrar ScreenCast, y el diagnóstico de hoy midió que lo que falla
es el COMPOSITOR en una llamada EGL —8 ms antes del `Paused`— porque en QEMU mesa es
kms_swrast: software, SIN exportación dmabuf. El `mesa` del corpus se construye con
-Dgallium-drivers=iris y YA está en el rootfs de COSMIC; en QEMU nunca se usaba, en
un TigerLake es el driver correcto y trae dmabuf de verdad. O sea que este viaje no
es "lo mismo pero en metal": es la única forma de saber si ScreenCast está bien.

⚠ Por eso NO se inyecta llvmpipe: mezclarlos no es aditivo — llvmpipe trae su propio
libgbm/libEGL/libgallium y sobrescribirlos DESACTIVA iris. Si la máquina no tuviera
Intel, la imagen correcta sería la de KDE, no ésta con un parche.

Y lo que NO hace falta inyectar, medido: la de KDE mete libLLVM.so.18 + libgcc_s +
libstdc++ porque el JIT de llvmpipe los NEEDea. Acá NO — iris_dri.so pide sólo
libglapi/libdrm/libz/libzstd/libc (mesa va con -Dllvm=disabled) y cosmic-comp ocho
NEEDED sin C++. CERO binarios ajenos de Alpine. Verificado con clausura ELF completa
del rootfs: 472 objetos escaneados, 42 raíces de runtime, 0 con NEEDED sin proveedor
(los 3 huecos son llvm-*/perl, herramientas de build que el escritorio no arranca).

Se instala EL MISMO cosmic-start validado en QEMU, no una variante: nada de lo que
hace es específico del emulador, y que corra el mismo fichero es lo que hace que
"validado en QEMU" signifique algo para el metal.

VALIDADO EN OVMF CON PANTALLA, que es la regla de oro que costó un USB en el 1er
viaje de KDE (-serial null para simular metal sin puerto serie): arje-zero PID 1, las
4 particiones montadas, el motd VISIBLE y el prompt `/ #` en la pantalla — o sea que
el getty de tty1 hace su trabajo. El shell responde: iris_dri.so presente,
/dev/dri/card0, y cosmic-start/portal-probe/wireplumber en PATH.

Falta quemar el USB, que es destructivo y necesita que el usuario confirme el device.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-05 11:41:16 -04:00
co-authored by Claude Opus 5
parent b38f54e25c
commit 11e6a3c78c
2 changed files with 151 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
#!/usr/bin/env bash
# metal-desktop-image.sh — imagen de disco EFI del escritorio COSMIC para METAL REAL, con
# **aceleración de hardware Intel (iris)**. Hermano de scripts/kde/metal-desktop-image-dual.sh, pero
# con una diferencia de fondo que es el motivo de existir.
#
# ── POR QUÉ ESTA IMAGEN ES HW-ACCEL Y LA DE KDE ES SOFTWARE ─────────────────────────────────────
# La de KDE va por `mesa-llvmpipe` a propósito: tenía que aguantar DOS máquinas (Intel y una NVIDIA
# Pascal) y la mesa HW de nouveau sólo existe como binario Alpine, o sea rompiendo soberanía.
#
# Acá el objetivo es otro y por eso la decisión se invierte: **cerrar el frente de ScreenCast**. El
# diagnóstico del 2026-08-05 (ver el runbook) midió que la cadena entera del portal funciona —el nodo
# `cosmic-screencast` existe en el grafo de pipewire— y que lo que falla es el COMPOSITOR en una
# llamada EGL, 8 ms antes de que el stream quede en `Paused`, porque en QEMU mesa es `kms_swrast`:
# software, **sin exportación dmabuf**. Un stream de vídeo continuo la necesita; una captura de una
# sola toma no (por eso `cosmic-screenshot` sí anda, va por shm).
#
# El `mesa` del corpus se construye con `-Dgallium-drivers=iris` y **ya está en el rootfs de COSMIC**
# (`usr/lib/dri/iris_dri.so`). En QEMU nunca se usaba; en un TigerLake es el driver correcto y trae
# dmabuf de verdad. O sea que este viaje no es «lo mismo pero en metal»: es la única forma de saber si
# ScreenCast está bien.
#
# ⚠ POR ESO NO SE INYECTA llvmpipe ACÁ. Mezclarlos no es aditivo: llvmpipe trae su propio
# `libgbm`/`libEGL`/`libgallium` y sobrescribirlos DESACTIVA iris. Si esta máquina no tuviera Intel,
# la imagen correcta sería la de KDE, no ésta con un parche.
#
# ── LO QUE **NO** HACE FALTA INYECTAR, Y ES MEDIBLE ─────────────────────────────────────────────
# La imagen de KDE inyecta `libLLVM.so.18`, `libgcc_s.so.1` y `libstdc++.so.6` porque el JIT de
# llvmpipe los NEEDea. Acá NO: `iris_dri.so` pide sólo `libglapi`, `libdrm`, `libz`, `libzstd` y
# `libc` —mesa va con `-Dllvm=disabled`— y `cosmic-comp` sólo `libdisplay-info`, `libgbm`, `libseat`,
# `libudev`, `libinput`, `libpixman`, `libxkbcommon` y `libc`. **Cero C++ y cero libgcc**: la imagen
# COSMIC de metal no arrastra un solo binario ajeno de Alpine. Verificado con clausura ELF completa
# del rootfs: 42 raíces de runtime, 0 con NEEDED sin proveedor.
#
# work/metal-rootfs base: arje-zero PID1 + busybox + firmware i915 (estática ⇒ inyectar musl)
# work/cosmic-rootfs COSMIC runtime-completo (89 recetas: compositor, apps, portal, pipewire,
# wireplumber, portal-probe) — ya trae iris_dri.so
# + kernel linux-metal-dual (i915 builtin)
# → work/hammer-cosmic-metal.img
#
# Uso: scripts/cosmic/metal-desktop-image.sh
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
BASE="${BASE:-work/metal-rootfs}"
COSMIC="${COSMIC:-work/cosmic-rootfs}"
KERNEL="${KERNEL:-$(ls -dt store/*-linux-metal-dual/boot/bzImage 2>/dev/null | head -1)}"
MERGED="${MERGED:-work/cosmic-metal-rootfs}"
IMG="${IMG:-work/hammer-cosmic-metal.img}"
for x in "$BASE" "$COSMIC"; do [ -d "$x" ] || { echo "falta $x (corré scripts/cosmic/hydrate-cosmic.sh)" >&2; exit 1; }; done
[ -n "$KERNEL" ] && [ -r "$KERNEL" ] || { echo "falta linux-metal-dual (hammer build recipes/linux-metal-dual.toml)" >&2; exit 1; }
echo "==> fundiendo rootfs por hardlinks (base metal + COSMIC)"
rm -rf "$MERGED"
cp -al "$BASE" "$MERGED"
cp -al --remove-destination "$COSMIC"/. "$MERGED"/
. "$ROOT/scripts/lib/pid1-desde-store.sh"
inyectar_pid1 "$MERGED" "$BASE"
# El driver HW tiene que estar SÍ O SÍ: si no está, esta imagen no tiene razón de ser y es mejor
# fallar acá que descubrirlo con el USB en la mano frente a una pantalla negra.
[ -r "$MERGED/usr/lib/dri/iris_dri.so" ] || { echo " ✗ falta iris_dri.so — sin GPU Intel esta imagen no sirve" >&2; exit 1; }
echo "==> GL por HARDWARE: $(ls "$MERGED"/usr/lib/dri/ | tr '\n' ' ')"
echo "==> inyectando musl (loader + libc) — COSMIC es dinámico, 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"
# ── el arranque del escritorio ──────────────────────────────────────────────────────────────────
# Se instala EL MISMO script que se validó en QEMU, y no una variante: nada de lo que hace es
# específico del emulador. Levanta XDG_RUNTIME_DIR, seatd, el bus de sistema, el shim de logind,
# pipewire, wireplumber, el compositor y los clientes — todos pasos de escritorio, no de QEMU. Que
# corra el mismo fichero es lo que hace que «validado en QEMU» signifique algo para el metal.
echo "==> instalando /usr/bin/cosmic-start (el MISMO script validado en QEMU)"
install -Dm755 scripts/cosmic/cosmic-start-qemu.sh "$MERGED/usr/bin/cosmic-start"
# ── getty en tty1: el prompt TIENE que salir por la PANTALLA ────────────────────────────────────
# Lección del 1er viaje físico de KDE, que costó un USB quemado: 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 supervisa un solo getty, sobre `console` ⇒ en un metal sin puerto serie el
# shell nace INVISIBLE: la pantalla se congela en el último printk y parece que el arranque murió,
# cuando está vivo. Nunca se vio en QEMU porque siempre se validaba con `-nographic`, donde el serial
# ES la pantalla.
#
# Se arregla acá y no en el cmdline: un getty sobre tty1 no depende del cmdline (en metal siempre hay
# VT) y no obliga a re-sellar el kernel. El getty de `console` se conserva para debug por serie.
echo "==> añadiendo getty en tty1 (el prompt de `console` va al SERIAL, invisible en metal)"
cat > "$MERGED/usr/bin/console-login" <<'LOGIN'
#!/bin/sh
# arje-zero lanza los getty con envp VACÍO ⇒ sin PATH no se resuelve ni `cat` ni `cosmic-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"] = "01HQAR53D4M2NBV8KZTYXFQA04"
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` antes del heredoc para ROMPER EL HARDLINK: `cat >` escribiría el inodo COMPARTIDO con
# work/metal-rootfs y le cambiaría el motd a todas las demás imágenes.
rm -f "$MERGED/etc/motd"
cat > "$MERGED/etc/motd" <<'MOTD'
#-- hammer :: escritorio COSMIC (metal, Intel i915 + iris HW) ---------------
GL por HARDWARE (iris). Es la diferencia con la imagen de KDE, y el motivo
de este viaje: en QEMU mesa era software y ScreenCast no podía exportar
dmabuf.
Arrancar el escritorio: cosmic-start
Probar el portal: portal-probe version
portal-probe screencast <-- LO QUE VINIMOS A VER
GPU detectada: ls /dev/dri ; dmesg | grep -iE 'i915|drm'
Logs si algo falla: /tmp/cosmic-session.log /tmp/pipewire.log
---------------------------------------------------------------------------
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