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>
130 lines
7.2 KiB
Bash
Executable File
130 lines
7.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# qemu-desktop-image.sh — imagen de disco EFI KDE para QEMU (virtio-gpu + software GL), auto-lanza Plasma.
|
|
#
|
|
# Igual que metal-desktop-image.sh pero para una VM: parchea swrast (mesa-swrast softpipe, sin LLVM) al
|
|
# rootfs runtime-completo, instala plasma-start-qemu.sh (software GL sobre el DRM de virtio) y lo AUTO-
|
|
# LANZA desde /etc/profile del console-getty. Así la ventana QEMU bootea → arje-zero → Plasma pinta solo.
|
|
#
|
|
# work/metal-rootfs base: arje-zero PID1 + busybox + kernel(EFI-stub) + firmware
|
|
# work/kde-metal-rootfs KDE runtime-completo (206 pkgs) — ya trae fuentes/iconos/kactivitymanagerd/
|
|
# KDEPlasmaPlatformTheme/módulo QML kwindowsystem
|
|
# + store/*-mesa-swrast swrast_dri/kms_swrast (software GL, softpipe)
|
|
# → work/qemu-desktop-rootfs (hardlinks) → work/hammer-kde-qemu.img
|
|
#
|
|
# Uso: scripts/kde/qemu-desktop-image.sh # arma la imagen
|
|
set -euo pipefail
|
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
|
|
|
|
BASE="${BASE:-work/metal-rootfs}"
|
|
KDE="${KDE:-work/kde-metal-rootfs}"
|
|
# mesa-llvmpipe (GBM/EGL completo, lo que kwin exige) si está sellado; si no, cae a mesa-swrast (softpipe).
|
|
MESA="${MESA:-$(ls -dt store/*-mesa-llvmpipe 2>/dev/null | head -1)}"
|
|
[ -n "$MESA" ] && [ -d "$MESA" ] || MESA="$(ls -dt store/*-mesa-swrast 2>/dev/null | head -1)"
|
|
LLVM="${LLVM:-$(ls -dt store/*-llvm18 2>/dev/null | head -1)}"
|
|
MERGED="${MERGED:-work/qemu-desktop-rootfs}"
|
|
IMG="${IMG:-work/hammer-kde-qemu.img}"
|
|
|
|
[ -d "$BASE" ] || { echo "falta $BASE" >&2; exit 1; }
|
|
[ -d "$KDE" ] || { echo "falta $KDE" >&2; exit 1; }
|
|
[ -n "$MESA" ] && [ -d "$MESA" ] || { echo "falta artefacto mesa-llvmpipe/mesa-swrast en store/" >&2; exit 1; }
|
|
case "$MESA" in *mesa-llvmpipe) LLVMPIPE=1;; *) LLVMPIPE=0;; esac
|
|
|
|
echo "==> fundiendo rootfs por hardlinks (base + 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 "==> parcheando mesa (software GL) desde $MESA (llvmpipe=$LLVMPIPE)"
|
|
# el artefacto trae usr/lib/{dri/*swrast*,libEGL,libgbm,libglapi,libGLESv2}; copiamos SIN --remove para
|
|
# no pisar sonames del metal salvo lo que falte, pero swrast_dri debe entrar sí o sí.
|
|
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_dri instalado" || { echo " ✗ swrast no entró" >&2; exit 1; }
|
|
|
|
if [ "$LLVMPIPE" = 1 ]; then
|
|
echo "==> inyectando libLLVM.18 + libgcc_s (llvmpipe JIT-linkea libLLVM; ésta NEEDea libgcc_s)"
|
|
[ -n "$LLVM" ] && [ -d "$LLVM" ] || { echo " ✗ falta artefacto llvm18 en store/ (set LLVM=)" >&2; exit 1; }
|
|
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
|
|
# libgcc_s del sandbox (libLLVM.so lo NEEDea; el -static-libgcc no aplica full a un DYLIB)
|
|
GCCS="${GCCS:-.dev-fs/alpine/usr/lib/libgcc_s.so.1}"
|
|
[ -r "$GCCS" ] && cp -a "$GCCS" "$MERGED"/usr/lib/libgcc_s.so.1 || echo " ⚠ sin libgcc_s ($GCCS)"
|
|
# mesa-llvmpipe se construye con gcc/libstdc++ (para matchear el ABI de libLLVM) ⇒ NEEDea libstdc++.so.6
|
|
STDCXX="${STDCXX:-.dev-fs/alpine/usr/lib/libstdc++.so.6}"
|
|
[ -r "$STDCXX" ] && cp -a "$STDCXX"* "$MERGED"/usr/lib/ 2>/dev/null || echo " ⚠ sin libstdc++ ($STDCXX)"
|
|
echo " ✓ libLLVM.18 ($(du -h "$MERGED"/usr/lib/libLLVM.so.18.1 | cut -f1)) + libgcc_s + libstdc++ inyectados"
|
|
fi
|
|
|
|
echo "==> inyectando musl (loader + libc) — los binarios KDE son dinámicos"
|
|
# La base metal es 100% ESTÁTICA (arje-zero+busybox) y el KDE-rootfs no trae libc ⇒ el merge no tiene
|
|
# loader musl. Los binarios KDE piden interp /lib/ld-musl-x86_64.so.1 + NEEDED libc.so (única lib base
|
|
# que falta; el resto del closure Qt/KF6 está). Copiamos el musl soberano del host (mismo que corre la
|
|
# mirada del laptop ⇒ ejecuta binarios hammer-musl dinámicos). En musl el loader ES libc (mismo fichero).
|
|
MUSL="${MUSL:-/usr/lib/musl/lib/libc.so}"
|
|
[ -r "$MUSL" ] || { echo "no encuentro musl libc ($MUSL) — set 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" # intérprete
|
|
ln -sf /usr/lib/musl/lib/libc.so "$MERGED/usr/lib/libc.so" # NEEDED soname en el search path
|
|
echo " ✓ musl inyectado ($(du -h "$MERGED/usr/lib/musl/lib/libc.so" | cut -f1))"
|
|
|
|
echo "==> usuario messagebus (el system bus de dbus hace setuid a él; falta en el passwd base)"
|
|
# dbus-daemon --system lee system.conf que hace <user>messagebus</user> ⇒ sin ese uid, arrancado como
|
|
# root, falla el setuid. Lo agregamos rompiendo el hardlink read-only del passwd/group base con `mv`
|
|
# (rename reemplaza la entrada de directorio sin tocar el inode fuente). uid/gid 81 = convención Alpine.
|
|
if ! grep -q '^messagebus:' "$MERGED/etc/passwd"; then
|
|
tmp="$MERGED/etc/passwd.new"
|
|
{ cat "$MERGED/etc/passwd"; echo 'messagebus:x:81:81:messagebus:/dev/null:/sbin/nologin'; } > "$tmp"
|
|
chmod 644 "$tmp"; mv "$tmp" "$MERGED/etc/passwd"
|
|
fi
|
|
if ! grep -q '^messagebus:' "$MERGED/etc/group"; then
|
|
tmp="$MERGED/etc/group.new"
|
|
{ cat "$MERGED/etc/group"; echo 'messagebus:x:81:'; } > "$tmp"
|
|
chmod 644 "$tmp"; mv "$tmp" "$MERGED/etc/group"
|
|
fi
|
|
echo " ✓ messagebus:81 en passwd/group"
|
|
|
|
echo "==> instalando /usr/bin/plasma-start (variante QEMU software-GL)"
|
|
install -Dm755 scripts/kde/plasma-start-qemu.sh "$MERGED/usr/bin/plasma-start"
|
|
|
|
echo "==> auto-lanzar Plasma: console-getty ejecuta plasma-start (no /bin/sh)"
|
|
# El console-getty es un getty de SERIAL (115200 console=ttyS0) que corría /bin/sh — pero /bin/sh así NO
|
|
# es login shell, no lee /etc/profile. En vez de eso, hacemos que getty ejecute plasma-start DIRECTO
|
|
# (-l /usr/bin/plasma-start). kwin presenta al DRM de virtio-gpu (ventana gráfica), independiente de que
|
|
# la consola de texto viva en el serial. Si Plasma falla, plasma-start hace exec /bin/sh ⇒ shell serie.
|
|
python3 - "$MERGED/ente/seed.card.json" <<'PY'
|
|
import json, sys, os
|
|
p = sys.argv[1]
|
|
c = json.load(open(p))
|
|
for g in c.get("genesis", []):
|
|
if g.get("label") == "console-getty":
|
|
argv = g["payload"]["Native"]["argv"]
|
|
argv[:] = [("/usr/bin/plasma-start" if a == "/bin/sh" else a) for a in argv]
|
|
print(" console-getty argv →", argv)
|
|
# escribir a temp y reemplazar la ENTRADA de directorio: rompe el hardlink (el seed en MERGED es un
|
|
# hardlink read-only a work/metal-rootfs) sin tocar el inode fuente ni chocar con los permisos.
|
|
tmp = p + ".new"
|
|
with open(tmp, "w") as f:
|
|
json.dump(c, f, indent=2)
|
|
os.chmod(tmp, 0o644)
|
|
os.replace(tmp, p)
|
|
PY
|
|
|
|
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"
|
|
|
|
export ROOTFS="$MERGED" IMG ROOT_SIZE
|
|
export ESP_MB="${ESP_MB:-128}" STORE_SIZE="${STORE_SIZE:-512}" STATE_SIZE="${STATE_SIZE:-768}"
|
|
exec ./scripts/install-image-efi.sh
|