Files
takana/scripts/gnome/gnome-start-qemu.sh
T
sergioandClaude Opus 5 f56bab72f5 gnome: el compositor SUBE ENTERO en headless — el muro DRM es libinput, y aparece accountsservice
Experimento decisivo: `gnome-shell --headless --virtual-monitor 1280x800`. El
backend headless crea el seat con META_SEAT_NATIVE_FLAG_NO_LIBINPUT, o sea que
saltea init_libinput() — justo el último paso del hilo de input antes de señalar
`input_thread_initialized` (meta-seat-impl.c:3098). Resultado:

  libmutter-Message: Added virtual monitor Meta-0
  libmutter-Message: Using Wayland display name 'wayland-0'
  == compositor OK (wayland-0) — el shell ES el display server

⇒ **CONFIRMADO: el bloqueo del camino DRM está en libinput/udev, no en el resto
del arranque.** Todo lo demás de mutter funciona. Primer sospechoso libudev-zero.

Y con el compositor arriba aparece el muro siguiente, que es de otra naturaleza:

  Gjs-CRITICAL: JS ERROR: Requiring AccountsService, version 1.0:
                Typelib file for namespace 'AccountsService' not found

LA LECCIÓN DE MÉTODO: gnome-shell selló con su cierre de build COMPLETO (108/108)
y aun así la sesión muere pidiendo este typelib. **El cierre de build no es el
cierre de runtime**: todo lo que el shell carga por `imports.gi.*` desde
JavaScript es invisible al grafo de deps. Se encuentra ARRANCANDO, no compilando.

Se autoró recipes/incoming-gnome/accountsservice.toml. El configure pasa entero
—tres seds verificados contra el build real: generate-version.sh (deriva la
versión del nombre del DIRECTORIO, que en el sandbox es /src, y la rama git usa
`date`, o sea no-determinista), la aserción de wtmp (musl no define WTMPX_FILENAME
ni _PATH_WTMPX) y subdir('tests') (arrastra mocklibc, que llama fgetgrent, ausente
en musl)—. Ojo: -Dsystemdsystemunitdir tiene que ser literalmente `no`; con la
cadena vacía el meson interpreta "averiguá el directorio" y aserta pidiendo
systemd.pc.

NO SELLA todavía, y la frontera está contada símbolo por símbolo, no estimada:

  · libelogind exporta 14 símbolos (los que pedía mutter) y accountsservice usa
    OCHO que faltan: sd_get_sessions, sd_seat_can_multi_session,
    sd_session_get_display y los cinco de sd_login_monitor_*. Estos últimos son
    la parte con enjundia: no son getters sino una API de NOTIFICACIÓN (un fd
    poll-able). Sobre el diseño actual el camino natural es inotify sobre
    /run/systemd/{sessions,seats,users}.
  · fgetspent_r no existe en musl (extensión glibc de /etc/shadow, usada en
    src/daemon.c:265): necesita shim o parche a la variante no-reentrante.

Ninguno de los dos es de accountsservice: son huecos de NUESTRA capa de compat.

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

252 lines
14 KiB
Bash
Executable File

#!/bin/sh
# gnome-start-qemu.sh — arranca gnome-shell (Wayland) en una VM QEMU con render por SOFTWARE
# (kms_swrast/llvmpipe) sobre el DRM de virtio-gpu. Análogo de plasma-start-qemu.sh, y hereda sus
# lecciones caras: el diagnóstico va POR SERIAL porque la ventana gtk se congela si mirada reinicia
# su Xwayland (QEMU sigue vivo por dentro). Ver [[kde-metal-qemu-desktop]].
#
# Se instala como /usr/bin/gnome-start y lo ejecuta DIRECTO el console-getty (no vía login shell).
# Salir: matar gnome-shell. Logs: /tmp/gnome-shell.log + tee al serial.
#
# DIFERENCIA CLAVE CON PLASMA: acá NO hay dos procesos (compositor + shell). gnome-shell ES el
# compositor — embebe mutter como librería (libmutter-16.so) y `--wayland` lo hace display server.
# Por eso no hay un equivalente de "kwin arriba pero plasmashell murió": si el shell cae, cae todo.
set -u
# getty ejecuta este script DIRECTO ⇒ PATH viene vacío. Primero eso, antes de invocar nada.
export PATH=/usr/bin:/bin:/usr/sbin:/sbin
# El modo lo hornea la imagen (GNOME_MODE=... en scripts/gnome/qemu-desktop-image.sh).
[ -r /etc/gnome-mode ] && . /etc/gnome-mode
say() { echo "== gnome-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/lib:/lib:/usr/lib/gnome-shell:/usr/lib/mutter-16
# GI_TYPELIB_PATH es EL env crítico de esta sesión, y el que no tiene análogo en KDE: gnome-shell es
# JavaScript sobre gjs y carga TODO por typelib. Los del sistema van en /usr/lib/girepository-1.0,
# pero St/Shell/Gvc/Shew se instalan APARTE en /usr/lib/gnome-shell (son privados del shell). Sin
# esta ruta, gjs no encuentra imports.gi.St y el shell muere en el primer require.
export GI_TYPELIB_PATH=/usr/lib/girepository-1.0:/usr/lib/gnome-shell:/usr/lib/gnome-shell/girepository-1.0
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 XDG_SESSION_TYPE=wayland
export HOME=/root
export GSETTINGS_SCHEMA_DIR=/usr/share/glib-2.0/schemas
export LANG=C.UTF-8
# --- render por software sobre virtio-gpu (los mismos fixes que costaron la campaña KDE) ---
export LIBGL_ALWAYS_SOFTWARE=1
export GALLIUM_DRIVER=llvmpipe
# GBM_ALWAYS_SOFTWARE=1 — virtio-gpu-pci SIN 3D expone un nodo KMS-only. Sin esto gbm carga kms_swrast
# pero deja gbm_dri->software=FALSE y dri2_initialize_drm entra al bloque `if(!software)` llamando
# queryCompatibleRenderOnlyDeviceFd(), NULL en el driver software ⇒ SIGSEGV. Fue el MURO 2 de KDE.
export GBM_ALWAYS_SOFTWARE=1
# kms_swrast y NO swrast: el compositor abre card0 como KMS y crea un gbm device encima ⇒ necesita el
# driver software PARA KMS (dumb buffers). Con 'swrast' pelado gbm_create_device falla.
export MESA_LOADER_DRIVER_OVERRIDE=kms_swrast
export LIBGL_DRIVERS_PATH=/usr/lib/dri
# llvmpipe SINCRÓNICO: su JIT multi-thread segfaultea al componer la 1ra superficie en esta VM.
export LP_NUM_THREADS=1
# Análogos mutter de dos fixes de kwin: cursor por software (el plano de cursor por HW no se presenta
# en virtio-gpu con render software ⇒ cursor invisible) y modeset simple en vez de atomic.
export MUTTER_DEBUG_DISABLE_HW_CURSORS=1
export MUTTER_DEBUG_FORCE_KMS_MODE=simple
# MUTTER_DEBUG_KMS_THREAD_TYPE=user: mutter corre su hebra KMS con prioridad de TIEMPO REAL por
# defecto. En esta VM no hay privilegios de RT scheduling, y el síntoma es exactamente el que dio el
# diagnóstico — hilo principal dormido en `futex_do_wait` con la "KMS thread" viva pero sin avanzar,
# el shell con /dev/dri/card0 ya abierto y ningún socket wayland creado nunca.
export MUTTER_DEBUG_KMS_THREAD_TYPE=user
# MUTTER_DEBUG_SEND_KMS_MODIFIERS=0: el equivalente en mutter del KWIN_DRM_USE_MODIFIERS=0 que costó
# la campaña KDE. virtio-gpu ANUNCIA soportar drmModeAddFB2WithModifiers pero RECHAZA el modifier
# explícito (aun LINEAR=0) con EINVAL ⇒ no se puede crear el framebuffer del compositor. Los nombres
# de estas variables están verificados contra los strings de libmutter-16.so.0, no de memoria.
export MUTTER_DEBUG_SEND_KMS_MODIFIERS=0
if [ "${DIAG:-1}" = 1 ]; then
export G_MESSAGES_DEBUG=all
export MUTTER_DEBUG=1
export EGL_LOG_LEVEL=debug
export LIBGL_DEBUG=verbose
fi
mkdir -p "$XDG_RUNTIME_DIR" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME" /run/dbus /var/lib/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
cp -f /etc/machine-id /var/lib/dbus/machine-id 2>/dev/null || true
# gschemas.compiled: meson NO lo genera cuando DESTDIR está seteado (lo dice en el log del build:
# "Skipping custom install script because DESTDIR is set"). Sin él, GSettings aborta al primer
# g_settings_new() y el shell no llega ni a abrir el DRM. Se compila acá, en el primer arranque.
if [ ! -f "$GSETTINGS_SCHEMA_DIR/gschemas.compiled" ]; then
say "compilando $(ls $GSETTINGS_SCHEMA_DIR/*.xml 2>/dev/null | wc -l) esquemas GSettings..."
glib-compile-schemas "$GSETTINGS_SCHEMA_DIR" 2>/tmp/schemas.log \
&& say "esquemas OK" || { say "!! glib-compile-schemas FALLÓ:"; dump /tmp/schemas.log; }
fi
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ó?"
# D-Bus de SISTEMA: mutter habla con logind por el bus de sistema. En esta distro login1 lo provee
# arje-logind-compat; si no está, mutter cae a su ruta sin sesión (que es justo lo que libelogind
# vino a cubrir del lado de la C-ABI). El bus tiene que existir igual.
if [ ! -S /run/dbus/system_bus_socket ]; then
if dbus-daemon --system --fork 2>/tmp/dbus-system.log; then
say "system bus ON"
else
say "!! system bus NO arrancó:"; dump /tmp/dbus-system.log
fi
fi
# D-Bus de sesión: gnome-shell EXIGE uno (registra org.gnome.Shell y org.gnome.Mutter.*).
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 sesión: ${DBUS_SESSION_BUS_ADDRESS:-NO ARRANCO}"
# arje-logind-compat: el login1 del fractal. Va DESPUÉS del bus de sistema (registra
# org.freedesktop.login1 ahí) y ANTES del shell. Mutter no sólo lo consulta por D-Bus: pregunta por
# SU sesión vía la C-ABI sd-login, que libelogind resuelve LEYENDO /run/systemd/sessions. Sin este
# daemon esos ficheros no existen y mutter muere en "Failed to find any matching session".
if [ -x /usr/bin/arje-logind-compat ]; then
# ARJE_LOGIN_STATE=1: la perilla que YA traía el daemon y que nadie prendía. Su comentario en
# arje-compat lo dice explícito — "en un host CON systemd real pisaría su estado; en arje (sin
# systemd) el launcher de sesión lo prende. Default off" — y el launcher de sesión es ESTE script.
# Con ella escribe /run/systemd/{sessions,seats,users}, que es lo que libelogind lee.
export ARJE_LOGIN_STATE=1
/usr/bin/arje-logind-compat >/tmp/logind-compat.log 2>&1 &
i=0
while [ $i -lt 40 ]; do
[ -d /run/systemd/sessions ] && [ -n "$(ls /run/systemd/sessions 2>/dev/null)" ] && break
i=$((i+1)); sleep 0.25
done
say "logind-compat: sesiones=$(ls /run/systemd/sessions 2>/dev/null | tr '\n' ' ') seats=$(ls /run/systemd/seats 2>/dev/null | tr '\n' ' ')"
[ -n "$(ls /run/systemd/sessions 2>/dev/null)" ] || dump /tmp/logind-compat.log
else
say "!! sin /usr/bin/arje-logind-compat — mutter no va a hallar sesión"
fi
# ── PUENTE EXPLÍCITO: el estado de sesión que arje-logind-compat todavía no escribe ─────────────
# ESTO ES ANDAMIO, NO LA SOLUCIÓN. arje-logind-compat adquiere org.freedesktop.login1 y sirve el
# Manager por D-Bus, pero NO deja nada en /run/systemd/{sessions,seats,users} — su handshake con el
# bus de arje falla ("Announce respuesta inesperada: identity mismatch"), así que nunca hay una
# sesión registrada por el fractal. Y mutter no pregunta por D-Bus: usa la C-ABI sd-login, que
# libelogind (arje-sdlogin-compat) resuelve LEYENDO esos ficheros. De ahí "Failed to find any
# matching session" aun con login1 arriba.
#
# Escribimos el estado a mano, en el formato que libelogind lee (claves sacadas del propio .so:
# LEADER/SEAT/CLASS/TYPE/ACTIVE/STATE/VTNR y CAN_GRAPHICAL del seat). Sirve para PROBAR que el resto
# de la pila funciona; el arreglo de verdad es que arje-logind-compat registre la sesión del fractal
# —o que el Announce deje de fallar— y escriba esto solo. Queda marcado para no confundir andamio
# con producto.
if [ -z "$(ls /run/systemd/sessions 2>/dev/null)" ]; then
say "puente: escribiendo estado de sesión a mano (arje-logind-compat no lo hizo)"
mkdir -p /run/systemd/sessions /run/systemd/seats /run/systemd/users
cat > /run/systemd/sessions/1 <<EOF
UID=0
NAME=root
LEADER=$$
SEAT=seat0
TYPE=wayland
CLASS=user
ACTIVE=1
STATE=active
VTNR=1
REMOTE=0
EOF
cat > /run/systemd/seats/seat0 <<EOF
CAN_GRAPHICAL=1
CAN_MULTI_SESSION=1
ACTIVE=1
ACTIVE_SESSIONS=1
SESSIONS=1
EOF
cat > /run/systemd/users/0 <<EOF
NAME=root
STATE=active
SESSIONS=1
ACTIVE_SESSIONS=1
EOF
fi
# XDG_SESSION_ID/SEAT/VTNR: libelogind los menciona entre sus símbolos, y son además el contrato
# estándar por el que un proceso sabe cuál es SU sesión sin consultar por PID.
export XDG_SESSION_ID=1
export XDG_SEAT=seat0
export XDG_VTNR=1
if [ "${DIAG:-1}" = 1 ]; then
ulimit -c unlimited 2>/dev/null || true
echo '/core.%e.%p' > /proc/sys/kernel/core_pattern 2>/dev/null || true
fi
say "typelibs visibles: $(ls /usr/lib/girepository-1.0/*.typelib 2>/dev/null | wc -l) sistema + $(ls /usr/lib/gnome-shell/*.typelib 2>/dev/null | wc -l) del shell"
# GNOME_MODE=headless — EXPERIMENTO DECISIVO, no un modo de producción. El backend headless de
# mutter crea el seat con META_SEAT_NATIVE_FLAG_NO_LIBINPUT, o sea que SALTEA `init_libinput()`,
# que es justo el último paso del hilo de input antes de señalar `input_thread_initialized`
# (meta-seat-impl.c:3098). Si en headless aparece wayland-0, el bloqueo está en libinput/udev y no
# en el resto del arranque; si tampoco aparece, el problema es anterior (keymap, input settings).
case "${GNOME_MODE:-drm}" in
headless) SHELL_ARGS="--headless --virtual-monitor 1280x800" ;;
*) SHELL_ARGS="--wayland" ;;
esac
say "lanzando gnome-shell $SHELL_ARGS (modo=${GNOME_MODE:-drm})..."
/usr/bin/gnome-shell $SHELL_ARGS >/tmp/gnome-shell.log 2>&1 &
SHELL_PID=$!
# stream EN VIVO al serial (busybox sed no soporta -u ⇒ tail -f directo, sin pipes).
( echo "==== gnome-shell.log (stream vivo) ===="; tail -f /tmp/gnome-shell.log ) > /dev/ttyS0 2>/dev/null &
# OJO con el test de vivo: `kill -0` TIENE ÉXITO sobre un ZOMBI (el hijo existe como PID hasta que
# el padre lo cosecha, y acá el padre es este script, que sólo hace wait al final). La primera
# versión usaba kill -0 y por eso reportó "sin wayland-0 tras 45s" cuando en realidad gnome-shell ya
# había muerto en el primer segundo: el diagnóstico decía "0 hilos" porque no quedaba proceso, sólo
# la entrada zombi. Se mira el State de /proc en su lugar.
alive() { s=$(sed -n 's/^State:[[:space:]]*//p' /proc/$1/status 2>/dev/null); case "$s" in ""|Z*) return 1;; *) return 0;; esac; }
i=0
while [ $i -lt 240 ]; do
[ -S "$XDG_RUNTIME_DIR/wayland-0" ] && break
alive $SHELL_PID || { wait $SHELL_PID; RC=$?; say "!! gnome-shell murió (código $RC) antes de exponer wayland-0:"; dump /tmp/gnome-shell.log; say "cores: $(ls /core.* 2>/dev/null | tr '\n' ' ' || echo NINGUNO)"; exec /bin/sh; }
i=$((i+1)); sleep 0.5
done
if [ -S "$XDG_RUNTIME_DIR/wayland-0" ]; then
say "compositor OK (wayland-0) — el shell ES el display server"
else
say "!! sin wayland-0 tras 120s:"; dump /tmp/gnome-shell.log
# El shell NO murió: quedó COLGADO. Sin gdb en la imagen, el diagnóstico barato es dónde está
# bloqueado el hilo principal (wchan) y contra qué socket, que es lo que distingue "esperando una
# respuesta D-Bus que no llega" de "spinning en el renderer".
say "--- diagnóstico del cuelgue (pid $SHELL_PID) ---"
say "XDG_RUNTIME_DIR ($XDG_RUNTIME_DIR): $(ls -a $XDG_RUNTIME_DIR 2>/dev/null | tr '\n' ' ')"
say "sockets wayland en el sistema: $(find / -name 'wayland-*' -maxdepth 6 2>/dev/null | tr '\n' ' ')"
say "wchan: $(cat /proc/$SHELL_PID/wchan 2>/dev/null)"
say "state: $(grep -a ^State /proc/$SHELL_PID/status 2>/dev/null)"
say "stack principal:"; cat /proc/$SHELL_PID/stack > /dev/ttyS0 2>/dev/null
say "syscall: $(cat /proc/$SHELL_PID/syscall 2>/dev/null)"
say "fds abiertos:"; ls -l /proc/$SHELL_PID/fd 2>/dev/null > /dev/ttyS0
say "hilos: $(ls /proc/$SHELL_PID/task 2>/dev/null | wc -l)"
# Sin gdb en la imagen, la única forma de ver DÓNDE está bloqueado es forzarle un core y leerlo
# con el gdb del host (`thread apply all bt`). SIGABRT lo produce y no lo maneja nadie.
say "forzando core del proceso colgado (SIGABRT)..."
kill -ABRT $SHELL_PID 2>/dev/null
sleep 12; sync; sync
say "cores: $(ls -la /core.* 2>/dev/null | tr '\n' ' ' || echo NINGUNO)"
for t in $(ls /proc/$SHELL_PID/task 2>/dev/null); do
say " tid $t wchan=$(cat /proc/$SHELL_PID/task/$t/wchan 2>/dev/null) comm=$(cat /proc/$SHELL_PID/task/$t/comm 2>/dev/null)"
done
exec /bin/sh
fi
export WAYLAND_DISPLAY=wayland-0
( n=0; while sleep 15; do
n=$((n+1))
say "STATUS +$((n*15))s: gnome-shell=$(pgrep gnome-shell 2>/dev/null | wc -l)"
done ) &
wait $SHELL_PID
RC=$?
say "gnome-shell salió (código $RC) — volcando log:"
dump /tmp/gnome-shell.log
cp /tmp/gnome-shell.log /gnome-shell.log 2>/dev/null || true
say "cores en /: $(ls -la /core.* 2>/dev/null | tr '\n' ' ' || echo NINGUNO)"
sync; sync
say "== disco SYNCED. serial CONGELADO. matá QEMU desde el host. =="
sleep 3600