kde: pipeline escritorio QEMU (qemu-desktop-image + plasma-start-qemu)

Imagen QEMU booteable con Plasma auto-lanzado: merge base+kde-metal-rootfs, inyección
del loader musl (la imagen metal nunca pudo correr KDE dinámico — base estática + KDE
sin libc), parche swrast, seed patcheado (console-getty ejecuta plasma-start), PATH/dbus/
fuentes/iconos. HITO: kwin toma DRM master de virtio-gpu y expone wayland-0 (lo que el
anidado no podía). Muro pendiente: GBM/EGL de software — mesa es softpipe, falta llvmpipe.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-19 14:21:37 -04:00
co-authored by Claude Opus 4.8
parent 7964e45bfa
commit dafd97be79
2 changed files with 195 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
#!/bin/sh
# plasma-start-qemu.sh — arranca Plasma en una VM QEMU con render por SOFTWARE (kms_swrast/llvmpipe)
# sobre el DRM de virtio-gpu (/dev/dri/card0). Derivado de plasma-start-metal.sh pero SIN hardware iris:
# en QEMU no hay GPU real, así que kwin presenta por el DRM de virtio con EGL/GBM de software.
#
# Se instala como /usr/bin/plasma-start en la imagen QEMU y lo AUTO-LANZA /etc/profile (console-getty
# autologin). Trae los arreglos de la campaña KDE 2026-07: QT_QUICK_BACKEND=software (plasmashell no crea
# contexto GL sin él), QT_QPA_FONTDIR (mata los tofu; qtbase se construyó sin fontconfig), kdeglobals +
# QT_QPA_PLATFORMTHEME=kde (iconos + estilo Breeze), kactivitymanagerd (plasmashell aborta sin él).
#
# Logs: /tmp/{kwin,kamd,plasmashell}.log + tee a /dev/console. Salir: matar kwin.
set -u
# getty ejecuta este script DIRECTO (no vía login shell) ⇒ PATH viene vacío: mkdir/tr/cat/dbus-daemon
# no se hallan y kwin nunca levanta. Seteamos PATH explícito (busybox + userland KDE) ANTES de todo.
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
# tee a la consola gráfica (tty0) Y al serial (ttyS0) — el host captura ttyS0 con -serial file:
say() { echo "== plasma-qemu :: $*" | tee -a /dev/console /dev/ttyS0 2>/dev/null; }
dump() { echo "---- $1 ----" > /dev/ttyS0 2>/dev/null; cat "$1" > /dev/ttyS0 2>/dev/null; }
export LD_LIBRARY_PATH=/usr/lib64:/usr/lib/qt6:/usr/lib:/lib
export QT_PLUGIN_PATH=/usr/lib64/qt6/plugins:/usr/lib/qt6/plugins
export QML2_IMPORT_PATH=/usr/lib64/qt6/qml:/usr/lib/qt6/qml
export QML_IMPORT_PATH=$QML2_IMPORT_PATH
export XDG_DATA_DIRS=/usr/share
export XDG_RUNTIME_DIR=/run/user/0
export XDG_CONFIG_HOME=/root/.config
export XDG_CACHE_HOME=/root/.cache
export HOME=/root
# --- render por software sobre virtio-gpu ---
export LIBGL_ALWAYS_SOFTWARE=1
export GALLIUM_DRIVER=llvmpipe
# kms_swrast (NO swrast): kwin abre card0 como dispositivo KMS y crea un gbm device sobre él ⇒ necesita
# el driver de software para KMS (dumb buffers). Con 'swrast' a secas gbm_create_device falla ("Failed
# to create gbm device"). kms_swrast_dri.so está en /usr/lib/dri.
export MESA_LOADER_DRIVER_OVERRIDE=kms_swrast
export LIBGL_DRIVERS_PATH=/usr/lib/dri
export QT_QUICK_BACKEND=software
# KWIN_COMPOSE=Q ⇒ compositor QPainter (software puro, SIN OpenGL/EGL): antes kwin exponía wayland-0 y
# MORÍA al montar la salida GL sobre softpipe/virtio (EGL de software inestable ⇒ pantalla negra). El
# backend QPainter pinta con CPU directo al framebuffer DRM, sin contexto GL — el camino para VM sin GPU.
export KWIN_COMPOSE=Q
export QT_QPA_PLATFORM=wayland
export QT_QPA_PLATFORMTHEME=kde
export QT_QPA_FONTDIR=/usr/share/fonts/dejavu
mkdir -p "$XDG_RUNTIME_DIR" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" /run/dbus /root
chmod 700 "$XDG_RUNTIME_DIR"
[ -s /etc/machine-id ] || cat /proc/sys/kernel/random/uuid 2>/dev/null | tr -d - > /etc/machine-id
# kdeglobals: iconos + estilo Breeze (sin esto Qt cae a hicolor vacío = escritorio pelado)
cat > "$XDG_CONFIG_HOME/kdeglobals" <<EOF
[Icons]
Theme=breeze
[General]
ColorScheme=BreezeLight
widgetStyle=Breeze
[KDE]
widgetStyle=Breeze
EOF
say "GPU DRM: $(ls /dev/dri/ 2>/dev/null | tr '\n' ' ')"
[ -e /dev/dri/card0 ] || say "!! no hay /dev/dri/card0 — ¿virtio-gpu no cargó? (dmesg | grep -i drm)"
# D-Bus de sesión
if [ -z "${DBUS_SESSION_BUS_ADDRESS:-}" ]; then
DBUS_SESSION_BUS_ADDRESS=$(dbus-daemon --session --fork --print-address 2>/dev/null)
export DBUS_SESSION_BUS_ADDRESS
fi
say "dbus: ${DBUS_SESSION_BUS_ADDRESS:-NO ARRANCO}"
# Compositor sobre el DRM de virtio (software EGL/GBM vía kms_swrast). SIN --virtual: presenta al display.
say "lanzando kwin_wayland (DRM virtio, software GL)..."
/usr/bin/kwin_wayland --drm >/tmp/kwin.log 2>&1 &
KWIN=$!
i=0
while [ $i -lt 60 ]; do
[ -S "$XDG_RUNTIME_DIR/wayland-0" ] && break
kill -0 $KWIN 2>/dev/null || { say "!! kwin murio:"; dump /tmp/kwin.log; exec /bin/sh; }
i=$((i+1)); sleep 0.5
done
if [ -S "$XDG_RUNTIME_DIR/wayland-0" ]; then
say "compositor OK (wayland-0)"
else
say "!! kwin no expuso wayland-0:"; dump /tmp/kwin.log; exec /bin/sh
fi
export WAYLAND_DISPLAY=wayland-0
# kactivitymanagerd: plasmashell ABORTA sin el
KAMD=$(ls /usr/lib*/libexec/kactivitymanagerd 2>/dev/null | head -1)
[ -n "$KAMD" ] && { "$KAMD" >/tmp/kamd.log 2>&1 & sleep 2; say "kactivitymanagerd lanzado"; }
say "lanzando plasmashell..."
/usr/bin/plasmashell >/tmp/plasmashell.log 2>&1 &
sleep 8
say "procesos: $(ps axo comm 2>/dev/null | grep -E 'kwin|plasma' | sort -u | tr '\n' ' ')"
say "escritorio arriba (o mira /tmp/plasmashell.log). Ctrl-C mata la sesion."
dump /tmp/plasmashell.log
wait $KWIN
# kwin salió ⇒ volcá su log para saber por qué (backend QPainter, DRM, modeset…)
say "kwin salió (código $?) — volcando kwin.log:"
dump /tmp/kwin.log
+91
View File
@@ -0,0 +1,91 @@
#!/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}"
SWRAST="${SWRAST:-$(ls -dt store/*-mesa-swrast 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 "$SWRAST" ] && [ -d "$SWRAST" ] || { echo "falta artefacto mesa-swrast en store/" >&2; exit 1; }
echo "==> fundiendo rootfs por hardlinks (base + KDE)"
rm -rf "$MERGED"
cp -al "$BASE" "$MERGED"
cp -al --remove-destination "$KDE"/. "$MERGED"/
echo "==> parcheando swrast (software GL) desde $SWRAST"
# 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 "$SWRAST"/usr/lib/dri/. "$MERGED"/usr/lib/dri/ 2>/dev/null || true
for so in libglapi libgbm libEGL libGLESv2; do
cp -a "$SWRAST"/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; }
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 "==> 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