1# Runbook — arrancar gnome-shell en QEMU (y dónde está el muro hoy)
2
3Estado al 2026-07-29: **el compositor SUBE ENTERO sobre DRM real.** gnome-shell toma el DRM master
4y los tres dispositivos de input por logind, realiza el stage `MetaStageNative`, asigna los planos
5primario y de cursor, y expone `wayland-0`:
6
7```
8BACKEND: Opening and taking control of device file '/dev/input/event0' ← y event2, y event1
9BACKEND: Realizing stage 'MetaStageNative'
10KMS: Plane 33 … primary for CRTC 37 · Plane 34 … cursor
11Using Wayland display name 'wayland-0'
12== gnome-qemu :: compositor OK (wayland-0) — el shell ES el display server
13```
14
15Lo único que queda para tener escritorio es la **capa JS**: el shell sale con código 1 en
16`JS ERROR: Requiring AccountsService, version 1.0: Typelib file … not found`. Ya no hay ningún
17cuelgue ni ningún SIGSEGV: los tres muros anteriores están cerrados, cada uno por medición y no por
18conjetura.
19
20Hermano de [`kde-qemu-desktop.md`](kde-qemu-desktop.md), del que reusa el andamiaje: la base
21`work/metal-rootfs`, la inyección de mesa-llvmpipe/LLVM/musl, el truco del console-getty y el propio
22`scripts/kde/run-qemu-desktop.sh`.
23
24## Ciclo completo
25
26```sh
27cd ~/hammer
28bash scripts/gnome/hydrate-gnome.sh # cierre de gnome-shell → work/gnome-rootfs (108/108)
29bash scripts/gnome/qemu-desktop-image.sh # funde base+gnome, parchea, → work/hammer-gnome-qemu.img
30IMG=work/hammer-gnome-qemu.img SERIAL=work/gnome-qemu-serial.log MON=work/gnome-monitor.sock \
31 TIMEOUT=150 DISP=none bash scripts/kde/run-qemu-desktop.sh
32grep -a "gnome-qemu ::" work/gnome-qemu-serial.log | awk '!seen[$0]++'
33```
34
35`DISP=gtk` para ver la ventana (necesita `DISPLAY=:0 GDK_BACKEND=x11`). **Regla heredada de KDE:
36validar escritorios con PANTALLA, nunca sólo por serial** — pero hoy no hace falta, porque no llega
37a pintar.
38
39## Sacar el core y el backtrace
40
41El `gnome-start` deja el core en la raíz ext4 y hace `sync`, así que sobrevive al SIGKILL de QEMU.
42
43```sh
44sfdisk -J work/hammer-gnome-qemu.img # localizar hammer-root (part 2)
45dd if=work/hammer-gnome-qemu.img of=/tmp/root.img bs=1M skip=129 count=5113 status=none
46debugfs -R "dump /core.gnome-shell.<pid> /tmp/core.gnome-shell" /tmp/root.img
47gdb -batch -q -ex "set sysroot $PWD/work/gnome-qemu-rootfs" \
48 -ex "core-file /tmp/core.gnome-shell" -ex "bt 25" work/gnome-qemu-rootfs/usr/bin/gnome-shell
49```
50
51## EL MURO DE HOY — el typelib de AccountsService
52
53```
54Gjs-CRITICAL: JS ERROR: Error: Requiring AccountsService, version 1.0:
55 Typelib file for namespace 'AccountsService', version '1.0' not found
56Execution of main.js threw exception → gnome-shell sale con código 1
57```
58
59No es un cuelgue: es una dep de RUNTIME que falta. La receta está escrita y medida en
60`recipes/incoming-gnome/accountsservice.toml` y **no sella todavía** por dos huecos de NUESTRA capa
61de compatibilidad, contados símbolo por símbolo en el comentario de esa receta: ocho símbolos
62`sd-*` que `libelogind` no exporta (los cinco `sd_login_monitor_*` son una API de notificación, no
63getters) y `fgetspent_r`, que es extensión de glibc y musl no tiene.
64
65## CÓMO CAYÓ EL MURO ANTERIOR — la hebra de input (cerrado 2026-07-29)
66
67Vale la pena dejarlo escrito porque **el diagnóstico que había acá era falso** y la forma de
68refutarlo es reusable.
69
70Decía: «el hilo principal espera en un condvar a que la Mutter Input Thread avise que terminó de
71inicializarse; nunca avisa» —eso era correcto— «hipótesis principal: `libudev-zero`, que
72`libinput_udev_assign_seat()` enumera por udev y el rootfs no trae udev de verdad». Eso era la
73conjetura, apoyada en un episodio parecido de otro frente ([[mirada-usb-nvidia]]).
74
75**La sonda que la refutó**: `libinput list-devices`, que hace exactamente lo que hace
76`init_libinput()` de mutter —`udev_new` + `libinput_udev_create_context` +
77`libinput_udev_assign_seat("seat0")`—. Volvió con `rc=0` y los tres dispositivos listados. La
78enumeración por /sys con libudev-zero funciona perfecto. Está horneada en `gnome-start` y corre en
79cada arranque, antes del shell.
80
81**El dato que dio la causa**: `tid 201 [Mutter Input Th] wchan=evdev_read syscall=0`. El hilo estaba
82dormido en un `read()` de evdev DENTRO DEL KERNEL. Ese volcado por hilo ya estaba en el script pero
83corría *después* del `kill -ABRT`, sobre un `/proc/<pid>/task` que ya no existía: salía vacío y no
84decía nada. El core tampoco servía — gdb no desenrolla a través de musl y devuelve `?? ()` para los
8511 hilos que no son el principal. **Acá `/proc` le gana al core.**
86
87**La causa**: `arje-logind-compat` abría el device de `TakeDevice` sin `O_NONBLOCK`. El compositor
88no elige los flags —recibe el fd ya abierto por el bus—, y libinput asume no-bloqueante para poder
89leer en bucle hasta `EAGAIN`; lo dice donde abre (`libinput/src/evdev.c:2401`) y le pasa
90`O_RDWR|O_NONBLOCK|O_CLOEXEC` a `open_restricted()`, pero mutter, cuando hay logind, ignora esos
91flags y delega la apertura. systemd-logind abre con `O_NONBLOCK`, así que la dependencia no se nota
92hasta que la implementa otro. Arreglado en tawasuyu `3dd88f582`, con test.
93
94Y por qué la sonda no lo veía: `libinput list-devices` abre el devnode **él mismo**, con
95`O_NONBLOCK`. Sólo el camino por logind pasaba por el fd malo. Esa asimetría es justamente lo que la
96convirtió en un experimento y no en una repetición.
97
98Tres perillas de diagnóstico que quedaron y valen para el próximo muro:
99`MUTTER_DEBUG` es una **lista de tópicos** (`g_parse_debug_string` sobre `meta_debug_keys`,
100`src/core/util.c:42`), no un booleano — con `1` no encendía nada; con `backend` imprime
101«Opening and taking control of device file '<path>'» por cada device y dice en cuál se para. El ping
102D-Bus a login1 separa «daemon tildado» de «daemon vivo que ya contestó». Y los logs de `/tmp` (tmpfs)
103se copian a la raíz ext4 antes de abortar, para sacarlos con `debugfs` junto al core.
104
105## LO QUE YA SE ARREGLÓ ANTES (dos bugs reales en arje-logind-compat)
106
107Ambos encontrados arrancando esta imagen, ambos con test, ambos ya en tawasuyu y sellados:
108
1091. **La sesión se creaba PEREZOSAMENTE.** `ensure_user` sólo corría dentro de un método del Manager,
110 y `write_login_state` vive adentro. Pero mutter no empieza por D-Bus: lo primero que hace es
111 `sd_pid_get_session()`, que arje-sdlogin-compat resuelve LEYENDO `/run/systemd/sessions`. Huevo y
112 gallina — el estado en disco sólo se escribía después de un pedido que sólo ocurre si el estado
113 ya existe. Ahora la sesión se crea al arrancar (`eager_session`, apagable con
114 `ARJE_LOGIN_EAGER=0`, uid por `ARJE_LOGIN_UID`).
1152. **El object path de la Session estaba mal escapado.** Era `/session/_1`; la convención de systemd
116 (`bus_label_escape`) codifica como `_<hex>` todo lo que no sea `[A-Za-z0-9]` **y también el primer
117 carácter si es dígito**, así que el id `"1"` da **`_31`**. Importa porque el cliente calcula el
118 path por su cuenta y no pregunta: mutter reimplementa la misma regla en
119 `meta-dbus-utils.c:escape_dbus_component`. Con `_1` no había nadie sirviendo ahí, la propiedad
120 `Seat` volvía NULL y mutter —que no chequea— moría de **SIGSEGV en `get_seat_proxy`**. Los objetos
121 `User` NO usan este escapado (systemd hardcodea `_<uid>`), así que `user_path()` queda igual.
122
123De paso, un arreglo de portabilidad musl preexistente: `libc::ioctl` toma `c_ulong` en glibc y
124`c_int` en musl, así que `DRM_IOCTL_SET_MASTER` se guarda pelado y se convierte en el sitio de uso.
125
126**Y `ARJE_LOGIN_STATE=1` ya existía**: el comentario del daemon dice literalmente «en arje (sin
127systemd) el launcher de sesión lo prende. Default off» — y el launcher es `gnome-start`. El puente
128que escribía `/run/systemd/` a mano fue reinventar esa perilla; queda como fallback inerte.
129
130El muro gráfico que este runbook listaba como «el que viene después» **nunca apareció**: los fixes
131que KDE dejó pagos y precargados en `gnome-start-qemu.sh` (`GBM_ALWAYS_SOFTWARE`, `kms_swrast`,
132`LP_NUM_THREADS=1`, `MUTTER_DEBUG_SEND_KMS_MODIFIERS=0`) alcanzaron: mutter crea el renderer gbm y
133elige `/dev/dri/card0` como primaria sin quejarse. Es el rédito directo de la campaña anterior.
134
135## Lo que falta, en orden
136
1371. **`AccountsService`** (el muro de hoy): cerrar los ocho símbolos `sd-*` en `arje-sdlogin-compat`
138 y `fgetspent_r`, sellar `accountsservice`, y hidratar su typelib. Ojo al costo: re-sellar
139 `libelogind` re-hashea todo lo que lo declara (mutter, gnome-shell, polkit…) ⇒ pasar
140 `yupana radio libelogind` ANTES de tocarlo.
1412. **Empaquetar la política D-Bus de login1** dentro del artefacto de arje-logind-compat (hoy la
142 escribe el script de imagen).
1433. Arreglar el `identity mismatch` del `Announce` al bus del fractal (hoy sólo un WARN).
1444. Con la capa JS viva: validar CON PANTALLA (`DISP=gtk`), nunca sólo por serial — regla heredada de
145 KDE ([[metal-dual-desktop-en-curso]]).
gnome-qemu-desktop.md
Runbook — arrancar gnome-shell en QEMU (y dónde está el muro hoy)
Estado al 2026-07-29: el compositor SUBE ENTERO sobre DRM real. gnome-shell toma el DRM master
y los tres dispositivos de input por logind, realiza el stage MetaStageNative, asigna los planos
primario y de cursor, y expone wayland-0:
BACKEND: Opening and taking control of device file '/dev/input/event0' ← y event2, y event1
BACKEND: Realizing stage 'MetaStageNative'
KMS: Plane 33 … primary for CRTC 37 · Plane 34 … cursor
Using Wayland display name 'wayland-0'
== gnome-qemu :: compositor OK (wayland-0) — el shell ES el display server
Lo único que queda para tener escritorio es la capa JS: el shell sale con código 1 en
JS ERROR: Requiring AccountsService, version 1.0: Typelib file … not found. Ya no hay ningún
cuelgue ni ningún SIGSEGV: los tres muros anteriores están cerrados, cada uno por medición y no por
conjetura.
Hermano de kde-qemu-desktop.md, del que reusa el andamiaje: la base
work/metal-rootfs, la inyección de mesa-llvmpipe/LLVM/musl, el truco del console-getty y el propio
scripts/kde/run-qemu-desktop.sh.
Ciclo completo
cd ~/hammer
bash scripts/gnome/hydrate-gnome.sh # cierre de gnome-shell → work/gnome-rootfs (108/108)
bash scripts/gnome/qemu-desktop-image.sh # funde base+gnome, parchea, → work/hammer-gnome-qemu.img
IMG=work/hammer-gnome-qemu.img SERIAL=work/gnome-qemu-serial.log MON=work/gnome-monitor.sock \
TIMEOUT=150 DISP=none bash scripts/kde/run-qemu-desktop.sh
grep -a "gnome-qemu ::" work/gnome-qemu-serial.log | awk '!seen[$0]++'
DISP=gtk para ver la ventana (necesita DISPLAY=:0 GDK_BACKEND=x11). Regla heredada de KDE:
validar escritorios con PANTALLA, nunca sólo por serial — pero hoy no hace falta, porque no llega
a pintar.
Sacar el core y el backtrace
El gnome-start deja el core en la raíz ext4 y hace sync, así que sobrevive al SIGKILL de QEMU.
sfdisk -J work/hammer-gnome-qemu.img # localizar hammer-root (part 2)
dd if=work/hammer-gnome-qemu.img of=/tmp/root.img bs=1M skip=129 count=5113 status=none
debugfs -R "dump /core.gnome-shell.<pid> /tmp/core.gnome-shell" /tmp/root.img
gdb -batch -q -ex "set sysroot $PWD/work/gnome-qemu-rootfs" \
-ex "core-file /tmp/core.gnome-shell" -ex "bt 25" work/gnome-qemu-rootfs/usr/bin/gnome-shell
EL MURO DE HOY — el typelib de AccountsService
Gjs-CRITICAL: JS ERROR: Error: Requiring AccountsService, version 1.0:
Typelib file for namespace 'AccountsService', version '1.0' not found
Execution of main.js threw exception → gnome-shell sale con código 1
No es un cuelgue: es una dep de RUNTIME que falta. La receta está escrita y medida en
recipes/incoming-gnome/accountsservice.toml y no sella todavía por dos huecos de NUESTRA capa
de compatibilidad, contados símbolo por símbolo en el comentario de esa receta: ocho símbolos
sd-* que libelogind no exporta (los cinco sd_login_monitor_* son una API de notificación, no
getters) y fgetspent_r, que es extensión de glibc y musl no tiene.
CÓMO CAYÓ EL MURO ANTERIOR — la hebra de input (cerrado 2026-07-29)
Vale la pena dejarlo escrito porque el diagnóstico que había acá era falso y la forma de
refutarlo es reusable.
Decía: «el hilo principal espera en un condvar a que la Mutter Input Thread avise que terminó de
inicializarse; nunca avisa» —eso era correcto— «hipótesis principal: libudev-zero, que
libinput_udev_assign_seat() enumera por udev y el rootfs no trae udev de verdad». Eso era la
conjetura, apoyada en un episodio parecido de otro frente ([[mirada-usb-nvidia]]).
La sonda que la refutó: libinput list-devices, que hace exactamente lo que hace
init_libinput() de mutter —udev_new + libinput_udev_create_context +
libinput_udev_assign_seat("seat0")—. Volvió con rc=0 y los tres dispositivos listados. La
enumeración por /sys con libudev-zero funciona perfecto. Está horneada en gnome-start y corre en
cada arranque, antes del shell.
El dato que dio la causa: tid 201 [Mutter Input Th] wchan=evdev_read syscall=0. El hilo estaba
dormido en un read() de evdev DENTRO DEL KERNEL. Ese volcado por hilo ya estaba en el script pero
corría después del kill -ABRT, sobre un /proc/<pid>/task que ya no existía: salía vacío y no
decía nada. El core tampoco servía — gdb no desenrolla a través de musl y devuelve ?? () para los
11 hilos que no son el principal. Acá /proc le gana al core.
La causa: arje-logind-compat abría el device de TakeDevice sin O_NONBLOCK. El compositor
no elige los flags —recibe el fd ya abierto por el bus—, y libinput asume no-bloqueante para poder
leer en bucle hasta EAGAIN; lo dice donde abre (libinput/src/evdev.c:2401) y le pasa
O_RDWR|O_NONBLOCK|O_CLOEXEC a open_restricted(), pero mutter, cuando hay logind, ignora esos
flags y delega la apertura. systemd-logind abre con O_NONBLOCK, así que la dependencia no se nota
hasta que la implementa otro. Arreglado en tawasuyu 3dd88f582, con test.
Y por qué la sonda no lo veía: libinput list-devices abre el devnode él mismo, con
O_NONBLOCK. Sólo el camino por logind pasaba por el fd malo. Esa asimetría es justamente lo que la
convirtió en un experimento y no en una repetición.
Tres perillas de diagnóstico que quedaron y valen para el próximo muro:
MUTTER_DEBUG es una lista de tópicos (g_parse_debug_string sobre meta_debug_keys,
src/core/util.c:42), no un booleano — con 1 no encendía nada; con backend imprime
«Opening and taking control of device file '<path>'» por cada device y dice en cuál se para. El ping
D-Bus a login1 separa «daemon tildado» de «daemon vivo que ya contestó». Y los logs de /tmp (tmpfs)
se copian a la raíz ext4 antes de abortar, para sacarlos con debugfs junto al core.
LO QUE YA SE ARREGLÓ ANTES (dos bugs reales en arje-logind-compat)
Ambos encontrados arrancando esta imagen, ambos con test, ambos ya en tawasuyu y sellados:
- La sesión se creaba PEREZOSAMENTE.
ensure_user sólo corría dentro de un método del Manager,
y write_login_state vive adentro. Pero mutter no empieza por D-Bus: lo primero que hace es
sd_pid_get_session(), que arje-sdlogin-compat resuelve LEYENDO /run/systemd/sessions. Huevo y
gallina — el estado en disco sólo se escribía después de un pedido que sólo ocurre si el estado
ya existe. Ahora la sesión se crea al arrancar (eager_session, apagable con
ARJE_LOGIN_EAGER=0, uid por ARJE_LOGIN_UID).
- El object path de la Session estaba mal escapado. Era
/session/_1; la convención de systemd
(bus_label_escape) codifica como _<hex> todo lo que no sea [A-Za-z0-9] y también el primer
carácter si es dígito, así que el id "1" da _31. Importa porque el cliente calcula el
path por su cuenta y no pregunta: mutter reimplementa la misma regla en
meta-dbus-utils.c:escape_dbus_component. Con _1 no había nadie sirviendo ahí, la propiedad
Seat volvía NULL y mutter —que no chequea— moría de SIGSEGV en get_seat_proxy. Los objetos
User NO usan este escapado (systemd hardcodea _<uid>), así que user_path() queda igual.
De paso, un arreglo de portabilidad musl preexistente: libc::ioctl toma c_ulong en glibc y
c_int en musl, así que DRM_IOCTL_SET_MASTER se guarda pelado y se convierte en el sitio de uso.
Y ARJE_LOGIN_STATE=1 ya existía: el comentario del daemon dice literalmente «en arje (sin
systemd) el launcher de sesión lo prende. Default off» — y el launcher es gnome-start. El puente
que escribía /run/systemd/ a mano fue reinventar esa perilla; queda como fallback inerte.
El muro gráfico que este runbook listaba como «el que viene después» nunca apareció: los fixes
que KDE dejó pagos y precargados en gnome-start-qemu.sh (GBM_ALWAYS_SOFTWARE, kms_swrast,
LP_NUM_THREADS=1, MUTTER_DEBUG_SEND_KMS_MODIFIERS=0) alcanzaron: mutter crea el renderer gbm y
elige /dev/dri/card0 como primaria sin quejarse. Es el rédito directo de la campaña anterior.
Lo que falta, en orden
AccountsService (el muro de hoy): cerrar los ocho símbolos sd-* en arje-sdlogin-compat
y fgetspent_r, sellar accountsservice, y hidratar su typelib. Ojo al costo: re-sellar
libelogind re-hashea todo lo que lo declara (mutter, gnome-shell, polkit…) ⇒ pasar
yupana radio libelogind ANTES de tocarlo.
- Empaquetar la política D-Bus de login1 dentro del artefacto de arje-logind-compat (hoy la
escribe el script de imagen).
- Arreglar el
identity mismatch del Announce al bus del fractal (hoy sólo un WARN).
- Con la capa JS viva: validar CON PANTALLA (
DISP=gtk), nunca sólo por serial — regla heredada de
KDE ([[metal-dual-desktop-en-curso]]).