Files
takana/scripts/gnome/gnome-start-qemu.sh
T
sergioandClaude Opus 5 5faa259603 audio: PipeWire es el servidor de la distro (decisión del usuario) — gvc conecta
Cierra una pregunta que estaba explícitamente abierta en varias recetas, y que es la
razón por la que `pulseaudio` se había construido con `-Ddaemon=false`: sólo el cliente,
para que gvc hablara el protocolo sin cerrar la decisión de prestado.

**Los dos conviven y por eso la decisión no rompe nada**: pipewire va con
`-Dlibpulse=enabled` ⇒ `pipewire-pulse`, un servidor que habla el protocolo de
PulseAudio. gnome-shell usa libpulse sin enterarse de qué hay del otro lado. No hubo que
tocar una línea del cliente.

  == gnome-qemu :: socket pipewire-0 OK
  == gnome-qemu :: socket pulse/native OK — gvc va a poder conectar
  Gvc-DEBUG: Updating client: index=32 name='pipewire'
  Gvc-DEBUG: Updating sink: index=33 name='auto_null' description='Dummy Output'

El `Failed to connect context: Connection refused` desapareció. El sink es auto_null, que
es la respuesta honesta: la VM no tiene tarjeta de sonido.

**⚠ Falta wireplumber** (gestor de sesión, Lua + su cierre). Sin él PipeWire acepta
clientes pero NO enumera ni enruta dispositivos. «El shell conecta» ≠ «hay sonido», y el
log de arriba es justo el caso donde confundirlos sería fácil. Queda escrito en la receta
y en el runbook.

GOTCHA MEDIDO: ya existía incoming-kde/pipewire.toml sellada y NO se puede reusar
hidratándola acá — los dos cierres traen glib y **no son la misma glib**:
incoming-kde/glib-shared y incoming-gnome/glib producen libglib-2.0.so.0.8800.1 con bytes
distintos (verificado con cmp) y comparten 370 rutas. Proyectar las dos deja que una gane
por orden de proyección ⇒ dos registros de GType en un proceso, el cuadro que costó el
episodio de colord. De ahí la copia en la cola GNOME (glib-shared→glib, dbus→dbus-shared,
pcre2-shared→pcre2), que sí re-construye. Lo que FUE gratis: alsa-lib, cuya única dep es
pkgconf y resuelve al catálogo PADRE ⇒ hash idéntico y cache hit (b3:93cae411).

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

456 lines
26 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; }
# 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; }
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
# XCURSOR_PATH/XCURSOR_THEME: sin tema de cursor mutter avisa «No cursor theme available» y —con el
# cursor por software, que en virtio-gpu es obligatorio— el puntero se mueve INVISIBLE. Lo provee
# `adwaita-icon-theme`, y el nombre del tema tiene que coincidir con el directorio bajo
# /usr/share/icons. El `org.gnome.desktop.interface cursor-theme` de GSettings dice lo mismo, pero
# estas variables las lee también libXcursor/wayland-cursor sin pasar por GSettings.
export XCURSOR_PATH=/usr/share/icons
export XCURSOR_THEME=Adwaita
export XCURSOR_SIZE=24
# --- 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álogo mutter de un fix de kwin: cursor por software (el plano de cursor por HW no se presenta en
# virtio-gpu con render software ⇒ cursor invisible).
export MUTTER_DEBUG_DISABLE_HW_CURSORS=1
# MUTTER_DEBUG_FORCE_KMS_MODE — perilla EXPERIMENTAL, apagada por defecto desde 2026-07-29.
#
# Estuvo en `simple` (modeset legacy en vez de atomic), copiado de la campaña KDE. Pero ahí el que
# fallaba con atomic era KWIN, no mutter: se heredó por analogía, no por medición. Y el síntoma que
# dejó es exactamente el que el modo legacy explica — la sesión sube entera, `gnome-shell` corre
# cinco minutos sin un solo error, y la PANTALLA SIGUE NEGRA con la consola del kernel encima. En el
# log de KMS: `Plane has no advertised formats` para el plano primario de virtio-gpu (sin 3D no
# publica IN_FORMATS) y sólo `Queue mode set` — ningún page flip, ningún scanout.
#
# Con atomic, mutter negocia formatos y planos por la ruta moderna, que es la que sabe qué hacer
# cuando el driver no publica IN_FORMATS. Se deja la variable como perilla para volver a probar el
# camino legacy sin editar el script: `KMS_MODE=simple bash scripts/gnome/qemu-desktop-image.sh`,
# que lo hornea en /etc/gnome-mode (el getty no hereda ambiente, igual que GNOME_MODE).
[ -n "${KMS_MODE:-}" ] && export MUTTER_DEBUG_FORCE_KMS_MODE="$KMS_MODE"
# 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
# MUTTER_DEBUG es una LISTA DE TÓPICOS (g_parse_debug_string sobre meta_debug_keys, core/util.c:42),
# no un booleano: con `1` no matcheaba ninguna clave y no encendía nada. `backend` es el que
# imprime «Opening and taking control of device file '<path>'» por cada dispositivo que pasa por
# MetaDevicePool ⇒ dice EXACTAMENTE en cuál se queda parada la hebra de input.
export MUTTER_DEBUG=backend,input,kms
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; }
# SIEMPRE volcar el log, no sólo al fallar. `glib-compile-schemas` **sale con 0 aunque RECHACE un
# esquema**: imprime el motivo por stderr y sigue con los demás. Con el volcado condicionado al
# código de salida, un esquema descartado quedaba invisible detrás de un «esquemas OK» — y eso fue
# exactamente lo que escondió que los gschemas de g-s-d les faltaban los `<enum>`, mientras el
# shell moría con «schema ... not found» y el fichero estaba instalado. Perdí un ciclo por creerle
# al código de salida en vez de leer stderr.
if [ -s /tmp/schemas.log ]; then
say "!! glib-compile-schemas se QUEJÓ (aunque no falle, puede haber descartado esquemas):"
dump /tmp/schemas.log
fi
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
# arje-polkit-compat: el PolicyKit1 del fractal. **VA ANTES que accounts-daemon y upowerd**, y el
# orden no es estético: los dos se bloquean en `polkit_authority_get_sync()` al arrancar, así que si
# `org.freedesktop.PolicyKit1` no está en el bus se quedan conectados pero SIN adquirir su nombre —y
# gnome-shell después espera 25 s por cada uno—. Nuestra receta `polkit` es libs-only a propósito
# (el demonio lo pone arje). Ojo: el shim autoriza TODO; ver recipes/arje-polkit-compat.toml.
if [ -x /usr/bin/arje-polkit-compat ]; then
/usr/bin/arje-polkit-compat >/tmp/polkit-compat.log 2>&1 &
say "arje-polkit-compat lanzado (pid $!)"
else
say "!! sin arje-polkit-compat — accounts-daemon y upowerd se van a colgar pidiendo autorización"
fi
# accounts-daemon: el servicio D-Bus de cuentas (org.freedesktop.Accounts). El shell lo consulta
# desde JavaScript vía `imports.gi.AccountsService` para el nombre y el avatar del usuario del menú.
#
# Se lanza EXPLÍCITO aunque el artefacto instale su `.service` de activación: la activación por bus
# de sistema pasa por `dbus-daemon-launch-helper`, que en una distro normal es setuid root y acá no
# lo es. Corriendo todo como root la activación probablemente funcionaría igual, pero depender de eso
# es depender de un accidente; lanzarlo a mano es una línea y no deja ambigüedad. Si además llegara a
# activarse por bus, el segundo simplemente no adquiere el nombre y se va: no hay daño.
if [ -x /usr/libexec/accounts-daemon ]; then
/usr/libexec/accounts-daemon >/tmp/accounts-daemon.log 2>&1 &
say "accounts-daemon lanzado (pid $!)"
else
say "!! sin /usr/libexec/accounts-daemon — el menú de usuario del shell va a fallar"
fi
# upowerd: el servicio de energía. Mismo caso que accounts-daemon —el shell lo consulta por D-Bus
# para el indicador de batería— y el mismo motivo para lanzarlo a mano: sin esto la activación por bus
# se queda esperando y el log lo dice sin ambigüedad,
# Error calling StartServiceByName for org.freedesktop.UPower:
# Failed to activate service ... timed out (service_start_timeout=25000ms)
# o sea VEINTICINCO SEGUNDOS de arranque tirados esperando algo que nunca iba a venir.
# Los directorios de estado los deriva upower de --prefix (historydir/statedir vacíos ⇒
# $prefix/var/lib/upower); si no existen, arranca y se muere sin ruido.
mkdir -p /usr/var/lib/upower /var/lib/upower
if [ -x /usr/libexec/upowerd ]; then
/usr/libexec/upowerd >/tmp/upowerd.log 2>&1 &
UPOWERD_PID=$!
say "upowerd lanzado (pid $UPOWERD_PID)"
# Chequeo real, no «lo lancé y confío»: la primera versión reportaba «lanzado» y el shell seguía
# esperando 25s por org.freedesktop.UPower. Un pid no es un servicio.
sleep 1
if alive $UPOWERD_PID; then
say "upowerd sigue vivo"
else
say "!! upowerd MURIÓ al arrancar:"; dump /tmp/upowerd.log
fi
else
say "!! sin upowerd — el shell va a esperar 25s a su activación D-Bus y seguir sin batería"
fi
# ── ESPERAR EL NOMBRE, NO EL PROCESO ────────────────────────────────────────────────────────────
# El paso que faltaba, y es la otra mitad de «un pid no es un servicio»: comprobar que el daemon
# ADQUIRIÓ su nombre en el bus. Se le da tiempo y, si no aparece, se vuelca su log y la lista de
# nombres del bus — que es el dato que dice si el problema es «no arrancó», «arrancó y no pidió el
# nombre» o «lo pidió y se lo negaron».
esperar_nombre() {
n="$1"; log="$2"; i=0
while [ $i -lt 20 ]; do
if dbus-send --system --dest=org.freedesktop.DBus --print-reply \
/org/freedesktop/DBus org.freedesktop.DBus.NameHasOwner "string:$n" 2>/dev/null \
| grep -q "boolean true"; then
say "bus: $n OK"
return 0
fi
i=$((i+1)); sleep 0.5
done
say "!! bus: $n NO apareció en 10s"
[ -n "$log" ] && [ -s "$log" ] && dump "$log"
say "nombres en el bus de sistema:"
dbus-send --system --dest=org.freedesktop.DBus --print-reply \
/org/freedesktop/DBus org.freedesktop.DBus.ListNames 2>/dev/null \
| tr -d '" ' | grep -v '^\s*$' > /tmp/busnames.log
dump /tmp/busnames.log
return 1
}
# ── AUDIO: PipeWire (decisión del usuario, 2026-07-29) ──────────────────────────────────────────
# Van DOS procesos y el orden importa: `pipewire` es el servidor, y `pipewire-pulse` es el puente que
# habla el protocolo de PulseAudio contra él. gnome-shell usa libpulse (gvc), así que lo que necesita
# es el SEGUNDO; sin el primero, el segundo no tiene con quién hablar.
#
# Los dos son servicios de USUARIO: su socket vive en $XDG_RUNTIME_DIR, que ya está creado más arriba.
#
# ⚠ Sin gestor de sesión (wireplumber todavía no está autorado): esto hace que el shell CONECTE y deje
# de tirar `Gvc-WARNING: Failed to connect context: Connection refused`, pero NO enumera ni enruta
# dispositivos. «El shell conecta» no es «hay sonido», y conviene no confundirlos.
if [ -x /usr/bin/pipewire ]; then
/usr/bin/pipewire >/tmp/pipewire.log 2>&1 &
say "pipewire lanzado (pid $!)"
i=0; while [ $i -lt 20 ] && [ ! -S "$XDG_RUNTIME_DIR/pipewire-0" ]; do i=$((i+1)); sleep 0.25; done
if [ -S "$XDG_RUNTIME_DIR/pipewire-0" ]; then
say "socket pipewire-0 OK"
/usr/bin/pipewire-pulse >/tmp/pipewire-pulse.log 2>&1 &
say "pipewire-pulse lanzado (pid $!)"
i=0; while [ $i -lt 20 ] && [ ! -S "$XDG_RUNTIME_DIR/pulse/native" ]; do i=$((i+1)); sleep 0.25; done
if [ -S "$XDG_RUNTIME_DIR/pulse/native" ]; then
say "socket pulse/native OK — gvc va a poder conectar"
else
say "!! pipewire-pulse no creó pulse/native:"; dump /tmp/pipewire-pulse.log
fi
else
say "!! pipewire no creó su socket:"; dump /tmp/pipewire.log
fi
else
say "!! sin /usr/bin/pipewire — el shell va a avisar 'Failed to connect context'"
fi
esperar_nombre org.freedesktop.login1 /tmp/logind-compat.log
esperar_nombre org.freedesktop.PolicyKit1 /tmp/polkit-compat.log
esperar_nombre org.freedesktop.Accounts /tmp/accounts-daemon.log
esperar_nombre org.freedesktop.UPower /tmp/upowerd.log
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"
# ── SONDA DE INPUT — barata, y separa las dos hipótesis del muro ────────────────────────────────
# El muro es la hebra de input de mutter: arranca y nunca señala `input_thread_initialized`.
# `libinput list-devices` hace EXACTAMENTE lo que hace `init_libinput()` —udev_new +
# libinput_udev_create_context + libinput_udev_assign_seat("seat0")— pero con `open()/close()`
# pelados en vez del `TakeDevice` de logind. O sea que discrimina:
# · la sonda cuelga o no lista nada ⇒ el muro es libudev-zero/libinput (enumeración por /sys)
# · la sonda lista los dispositivos ⇒ libinput anda; el cuelgue está del lado de mutter
# (MetaDevicePool → TakeDevice, o algo anterior en el hilo: input settings, keymap)
# Ojo: `libinput` es un multiplexor de subcomandos y `list-devices` NO es interactivo, pero igual se
# le pone reloj — si el problema es un cuelgue, la sonda cuelga también, y hay que matarla.
say "sonda: /sys/class/input = $(ls /sys/class/input 2>/dev/null | tr '\n' ' ')"
say "sonda: /dev/input = $(ls /dev/input 2>/dev/null | tr '\n' ' ')"
if [ -x /usr/bin/libinput ]; then
( libinput list-devices >/tmp/libinput-devices.log 2>&1; echo "rc=$?" >>/tmp/libinput-devices.log ) &
LI_PID=$!
i=0
while [ $i -lt 24 ] && alive $LI_PID; do i=$((i+1)); sleep 0.5; done
if alive $LI_PID; then
say "!! SONDA COLGADA: 'libinput list-devices' no volvió en 12s ⇒ el muro ES libinput/libudev-zero"
say " wchan=$(cat /proc/$LI_PID/wchan 2>/dev/null) syscall=$(cat /proc/$LI_PID/syscall 2>/dev/null)"
kill -9 $LI_PID 2>/dev/null
else
say "sonda 'libinput list-devices' VOLVIÓ (no cuelga) — dispositivos:"
dump /tmp/libinput-devices.log
fi
else
say "sonda: no hay /usr/bin/libinput en la imagen"
fi
# 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 &
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)"
# POR HILO, y ANTES del SIGABRT. La primera versión hacía este bucle DESPUÉS de abortar, o sea
# sobre un /proc/<pid>/task que ya no existía: salía vacío y no dijo nada. Es el dato más barato
# que hay para ubicar la «Mutter Input Thread», porque el core no sirve: gdb no puede desenrollar
# a través de musl (sin CFI) y devuelve `?? ()` para los 11 hilos que no son el principal.
say "--- por hilo (comm / wchan / syscall) ---"
for t in $(ls /proc/$SHELL_PID/task 2>/dev/null); do
say " tid $t [$(cat /proc/$SHELL_PID/task/$t/comm 2>/dev/null)] wchan=$(cat /proc/$SHELL_PID/task/$t/wchan 2>/dev/null) syscall=$(cut -d' ' -f1 /proc/$SHELL_PID/task/$t/syscall 2>/dev/null)"
done
# ¿Sigue vivo y respondiendo el login1? Si el shell está esperando una respuesta D-Bus que no
# llega, esto lo separa en dos: daemon muerto/colgado vs daemon vivo que ya contestó.
say "arje-logind-compat: pids=$(pgrep -f arje-logind-compat 2>/dev/null | tr '\n' ' ')"
for p in $(pgrep -f arje-logind-compat 2>/dev/null); do
say " logind pid $p state=$(sed -n 's/^State:[[:space:]]*//p' /proc/$p/status 2>/dev/null) wchan=$(cat /proc/$p/wchan 2>/dev/null)"
done
( dbus-send --system --print-reply --dest=org.freedesktop.login1 \
/org/freedesktop/login1 org.freedesktop.DBus.Properties.Get \
string:org.freedesktop.login1.Manager string:NAutoVTs >/tmp/login1-ping.log 2>&1
echo "rc=$?" >>/tmp/login1-ping.log ) &
PING_PID=$!
i=0; while [ $i -lt 16 ] && alive $PING_PID; do i=$((i+1)); sleep 0.5; done
if alive $PING_PID; then
say "!! login1 NO responde (ping D-Bus colgado 8s) ⇒ el daemon se quedó tildado"
kill -9 $PING_PID 2>/dev/null
else
say "login1 responde al ping D-Bus:"; dump /tmp/login1-ping.log
fi
# /tmp es tmpfs ⇒ el log del daemon se pierde con la VM. Copiarlo a la raíz ext4, que sí se
# sincroniza y se puede sacar con debugfs junto al core.
cp /tmp/logind-compat.log /logind-compat.log 2>/dev/null || true
cp /tmp/gnome-shell.log /gnome-shell.log 2>/dev/null || true
# 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)"
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