gnome: el bus de sistema COMPLETO — faltaba polkit, y lo dijo la lista de nombres

bus: org.freedesktop.login1      OK
  bus: org.freedesktop.PolicyKit1  OK
  bus: org.freedesktop.Accounts    OK
  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 25s 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.

Receta nueva `arje-polkit-compat` (b3:03e0a86f), tercer shim de este tipo tras
arje-logind-compat y arje-sdlogin-compat. Su propio autor ya había escrito el diagnóstico
en el crate: «apps que usan polkit bloquean en CheckAuthorization si no responde nadie».
⚠ AUTORIZA TODO — es la postura de sistema confiado que arje ya tenía tomada, no algo que
esta receta introduzca; queda explícito en su comentario.

Dos gotchas del empaquetado:
- **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 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: nunca sobrescribir
  un fichero que venga de un artefacto; agregar al lado.

Queda escrito en el runbook lo que sigue faltando (colord, y que el shell no tiene servidor
de sonido porque pulseaudio se construyó sólo-cliente: el demonio de audio de la distro
sigue sin decidirse) y las dos lecciones de método que costaron una iteración cada una.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-29 19:19:23 -04:00
co-authored by Claude Opus 5
parent 32926dc6df
commit 3d01d5cb04
5 changed files with 184 additions and 9 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

+47 -9
View File
@@ -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)
+51
View File
@@ -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"]
+42
View File
@@ -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
+44
View File
@@ -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'
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<!-- Añade a la política de polkit el permiso que el shim del fractal necesita: la de upstream sólo
deja adueñarse del nombre al usuario `polkitd`, y arje-polkit-compat corre como root. -->
<busconfig>
<policy user="root">
<allow own="org.freedesktop.PolicyKit1"/>
</policy>
<policy context="default">
<allow send_destination="org.freedesktop.PolicyKit1"/>
<allow receive_sender="org.freedesktop.PolicyKit1"/>
</policy>
</busconfig>
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#*:}"