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>
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
2 archivos · +195 −0
diff --git a/scripts/kde/plasma-start-qemu.sh b/scripts/kde/plasma-start-qemu.sh
new file mode 100644
index 00000000..e052135c
--- /dev/null
+++ b/scripts/kde/plasma-start-qemu.sh
@@ -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
diff --git a/scripts/kde/qemu-desktop-image.sh b/scripts/kde/qemu-desktop-image.sh
new file mode 100644
index 00000000..d4241441
--- /dev/null
+++ b/scripts/kde/qemu-desktop-image.sh
@@ -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