diff --git a/docs/evidencia/gnome-shell-qemu-2026-07-29.png b/docs/evidencia/gnome-shell-qemu-2026-07-29.png index 5aab2d2e..00b5043e 100644 Binary files a/docs/evidencia/gnome-shell-qemu-2026-07-29.png and b/docs/evidencia/gnome-shell-qemu-2026-07-29.png differ diff --git a/docs/runbooks/gnome-qemu-desktop.md b/docs/runbooks/gnome-qemu-desktop.md index 6dc1cc34..7ceecbab 100644 --- a/docs/runbooks/gnome-qemu-desktop.md +++ b/docs/runbooks/gnome-qemu-desktop.md @@ -122,19 +122,57 @@ reportaba colord. Sin eso **ninguna** activación por bus de sistema funciona. L `root:messagebus 4750`, y el síntoma de colord cambió de «permiso incorrecto» a «timed out»: la activación ya se intenta de verdad, y lo que queda es del demonio, no del bus. +### El bus de sistema, completo (cerrado): faltaba polkit + +``` +== gnome-qemu :: bus: org.freedesktop.login1 OK +== gnome-qemu :: bus: org.freedesktop.PolicyKit1 OK +== gnome-qemu :: bus: org.freedesktop.Accounts OK +== gnome-qemu :: bus: org.freedesktop.UPower OK +``` + +`accounts-daemon` y `upowerd` arrancaban, seguían vivos, y **nunca adquirían su nombre**: los dos se +bloquean en `polkit_authority_get_sync()` al iniciar, y `org.freedesktop.PolicyKit1` no lo servía +nadie —nuestra receta `polkit` es `libs-only` a propósito, porque el demonio lo pone arje—. gnome-shell +esperaba después 25 s por cada uno. + +**El dato que lo resolvió no salió del log de los daemons sino de la LISTA DE NOMBRES DEL BUS.** Ahí +estaban como conexiones anónimas `:1.0`, `:1.1` — sin nombre bien conocido. Eso distingue tres cosas +que en el log del cliente se ven igual: «no arrancó», «arrancó y no llegó a pedir el nombre» y «lo +pidió y se lo negaron». Era la segunda. `gnome-start` ahora **espera cada nombre** con +`NameHasOwner` y, si no aparece, vuelca el log del daemon y la lista de nombres. + +Lo sirve `recipes/arje-polkit-compat.toml` (b3:03e0a86f), el tercer shim de este tipo tras +arje-logind-compat y arje-sdlogin-compat. **⚠ Autoriza TODO** — es la postura de sistema confiado que +arje ya tenía tomada, no algo que la receta introduzca; está escrito en su comentario. + +Dos gotchas del empaquetado, los dos medidos: +- **La política de polkit ya existe y NO alcanza**: permite `own` sólo al usuario `polkitd`, y el shim + corre como root. Se agrega un `zz-arje-polkit-compat.conf` en vez de reescribir la de upstream — + dbus lee `system.d` en orden alfabético y las reglas posteriores ganan. +- **Los ficheros del rootfs fundido son hardlinks de SÓLO LECTURA del store** (0444). Un `cat >` + encima falla con `Permission denied` y rompió un build. Regla de este script: nunca sobrescribir un + fichero que venga de un artefacto; agregar al lado, o `rm -f` primero — y entonces se está pisando + algo sellado, que es una decisión, no un descuido. + ### Lo que queda, y ninguno impide el escritorio -- **UPower**: `gnome-start` lanza `upowerd` y **verifica que siga vivo** (lo hace: «upowerd sigue - vivo»), pero el nombre `org.freedesktop.UPower` no aparece en el bus y el shell espera sus 25 s. - Su política D-Bus está bien (`allow own` para root, verificado en el artefacto), así que el proximo - paso es mirar el log del propio upowerd —probablemente se queda enumerando por gudev sobre - libudev-zero—. Es el indicador de batería en una VM sin batería. -- **colord**: activación por bus ya permitida, el demonio no llega a arrancar. Es gestión de color. +- **colord** sigue sin arrancar (gestión de color de pantalla). Su activación por bus ya está + permitida; falta ver por qué el demonio no sube. +- **PulseAudio**: `Gvc-WARNING: Failed to connect context: Connection refused` — el control de volumen + del shell no tiene servidor de sonido. Coherente con que `pulseaudio` se construyó **sólo cliente** + (`-Ddaemon=false`): el demonio de sonido de la distro sigue sin decidirse (PulseAudio vs PipeWire). - `Missing required core component Settings` — es gnome-control-center, que no está en el corpus. -**Lección de las dos anteriores, que ya costó una iteración: un pid no es un servicio.** La primera -versión del lanzamiento de upowerd decía «lanzado» y el shell seguía esperando; reportar el arranque -de un daemon sin comprobar que adquirió su nombre es reportar una intención, no un hecho. +### Dos lecciones de método que costaron una iteración cada una + +1. **Un pid no es un servicio.** La primera versión del lanzamiento de upowerd decía «lanzado» y el + shell seguía esperando. Reportar el arranque de un daemon sin comprobar que adquirió su NOMBRE es + reportar una intención, no un hecho. +2. **El log serial persiste, y la imagen tarda más que el polling.** Tres veces leí un marcador del + arranque anterior y saqué conclusiones de una VM muerta. El orden correcto: esperar a que la imagen + esté hecha, *después* esperar el marcador. Truncar no alcanza si la truncada ocurre a mitad de la + cadena. ## CÓMO SE CERRÓ LA CAPA JS (2026-07-29) diff --git a/recipes/arje-polkit-compat.toml b/recipes/arje-polkit-compat.toml new file mode 100644 index 00000000..4a72768d --- /dev/null +++ b/recipes/arje-polkit-compat.toml @@ -0,0 +1,51 @@ +# arje-polkit-compat — el shim de `org.freedesktop.PolicyKit1.Authority` del fractal. +# +# POR QUÉ HACE FALTA, y es un diagnóstico que su propio autor ya había escrito: el comentario del +# crate dice literalmente «apps que usan polkit bloquean en `CheckAuthorization` si no responde +# nadie». Eso es exactamente lo que pasaba al arrancar la sesión GNOME: +# +# !! bus: org.freedesktop.Accounts NO apareció en 10s +# !! bus: org.freedesktop.UPower NO apareció en 10s +# nombres en el bus: org.freedesktop.DBus, org.freedesktop.login1, :1.0, :1.1, :1.2 +# +# Los dos daemons estaban CONECTADOS al bus (`:1.x`) pero sin adquirir su nombre: se quedaban en +# `polkit_authority_get_sync()` esperando a un `org.freedesktop.PolicyKit1` que no existía. Y como no +# adquirían el nombre, gnome-shell esperaba 25 s a cada uno. +# +# **Ese dato no se sacó del log del daemon sino de la LISTA DE NOMBRES DEL BUS**: una conexión anónima +# sin nombre bien conocido dice «arrancó y no llegó a pedirlo», que es distinto de «no arrancó» y de +# «lo pidió y se lo negaron». Vale para cualquier servicio D-Bus que parezca colgado. +# +# POR QUÉ ESTE SHIM Y NO EL polkitd DE VERDAD: nuestra receta `polkit` va con `-Dlibs-only=true` —el +# bloque `if not libs_only` es justo el que pide expat y duktape— y la decisión estaba tomada desde +# entonces: **el demonio lo pone arje**. La autorización real del fractal no es polkit sino SO_PEERCRED +# + capability grants sobre el bus interno; polkit es sólo la interfaz que el escritorio espera +# encontrar. +# +# ⚠ POSTURA DE SEGURIDAD, explícita porque no es un detalle: este shim responde +# `is_authorized = true` a TODO, y deja traza en el log de qué se pidió. Es la decisión que arje ya +# había tomado —el fractal es un sistema confiado y la puerta está en otro lado—, no algo que esta +# receta introduzca. Su propio comentario marca el camino de producción: que `CheckAuthorization` pida +# un token al grafo y devuelva el resultado. Mientras eso no exista, **cualquier cliente de polkit en +# esta distro está autorizado**, y quien lea esta receta tiene que saberlo. +# +# HUB-ONLY, como toda receta con fuente tawasuyu: el clone va a `gitea@git.tawasuyu.net` por el puerto +# 22 y la gitea escucha en :2345; el worker efímero es secretless por diseño y recibe +# `Connection refused`. Se muele en el hub. Ver [[rootfs-laptop-worker-divergen]]. +name = "arje-polkit-compat" +version = "0.0.1" + +[source] +# Mismo commit y misma rama que arje-logind-compat: `selfhost/arje-zero-attest-lockfile` es la única +# que versiona el `Cargo.lock` de la raíz, sin el cual el vendoreo `--locked` no es reproducible. +repo = "gitea@git.tawasuyu.net:tawasuyu/tawasuyu.git" +commit = "98db584fd28d9ff2bf34c9cf7d7db2e1de0b3fd0" +# El monorepo commitea su propio `vendor/` (la copia parcheada de smithay que mirada necesita) y el +# `cargo vendor` de hammer lo pisaría. Ver [[hammer-cargo-vendor-clobbers-project-vendor]]. +cargo_vendor_dir = ".hammer-cargo-vendor" + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +link = "static" +flags = ["-p", "arje-compat", "--bin", "arje-polkit-compat"] diff --git a/scripts/gnome/gnome-start-qemu.sh b/scripts/gnome/gnome-start-qemu.sh index 086d3d58..485de982 100755 --- a/scripts/gnome/gnome-start-qemu.sh +++ b/scripts/gnome/gnome-start-qemu.sh @@ -214,6 +214,18 @@ 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ú. # @@ -254,6 +266,36 @@ 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 +} +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 diff --git a/scripts/gnome/qemu-desktop-image.sh b/scripts/gnome/qemu-desktop-image.sh index 81bbba9f..0f169d37 100755 --- a/scripts/gnome/qemu-desktop-image.sh +++ b/scripts/gnome/qemu-desktop-image.sh @@ -130,6 +130,50 @@ XML chmod 644 "$MERGED/usr/share/dbus-1/system.d/org.freedesktop.login1.conf" echo " ✓ org.freedesktop.login1.conf" +echo "==> inyectando arje-polkit-compat (el PolicyKit1 del fractal) + su política" +# EL TERCER ESLABÓN DEL MISMO TIPO, y lo destapó la lista de nombres del bus: accounts-daemon y +# upowerd quedaban CONECTADOS (`:1.0`, `:1.1`) sin adquirir su nombre, porque los dos se bloquean en +# `polkit_authority_get_sync()` esperando a `org.freedesktop.PolicyKit1`. Nuestra receta `polkit` es +# libs-only a propósito (el demonio lo pone arje), así que nadie servía ese nombre y gnome-shell +# esperaba 25 s por cada uno. +# ⚠ El shim autoriza TODO (postura de sistema confiado ya tomada por arje) — ver su receta. +if APC="${APC:-store/$(./target/release/hammer --store store hash recipes/arje-polkit-compat.toml 2>/dev/null | tail -1 | sed 's/^b3://')-arje-polkit-compat}"; [ -d "$APC" ]; then + install -Dm755 "$APC"/usr/bin/arje-polkit-compat "$MERGED"/usr/bin/arje-polkit-compat + echo " ✓ $(basename "$APC" | cut -c1-12)" +else + echo " ✗ falta arje-polkit-compat en store/ — accounts-daemon y upowerd no adquirirán su nombre" >&2 + exit 1 +fi +# A DIFERENCIA de login1, acá el nombre YA tiene política: la trae el artefacto de `polkit`. Pero +# permite `own` sólo al usuario **polkitd**, y nuestro shim corre como root ⇒ dbus se lo niega. +# +# Se agrega un fichero APARTE en vez de reescribir el de polkit, por dos razones: +# 1. dbus lee todo `system.d` en orden alfabético y las reglas posteriores ganan, así que un +# `zz-…` añade el permiso sin descartar el resto de la política de upstream (que además trae las +# reglas de `AuthenticationAgent`). +# 2. **el fichero de polkit es un HARDLINK DE SÓLO LECTURA del store** — el rootfs fundido se arma +# con hardlinks a los artefactos sellados, que están a 0444. Un `cat >` encima falla con +# `Permission denied`, y eso ya rompió un build de esta imagen. Regla para este script: nunca +# sobrescribir un fichero que venga de un artefacto; agregar al lado, o `rm -f` primero para +# romper el enlace (y entonces se está pisando algo sellado, que es una decisión, no un descuido). +cat > "$MERGED/usr/share/dbus-1/system.d/zz-arje-polkit-compat.conf" <<'XML' + + + + + + + + + + + +XML +chmod 644 "$MERGED/usr/share/dbus-1/system.d/zz-arje-polkit-compat.conf" +echo " ✓ zz-arje-polkit-compat.conf (añade own= para root sobre la política de polkit)" + echo "==> usuario messagebus (el system bus hace setuid a él)" for f in passwd:messagebus:x:81:81:messagebus:/dev/null:/sbin/nologin group:messagebus:x:81:; do file="${f%%:*}"; line="${f#*:}"