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>
251 lines
14 KiB
Bash
Executable File
251 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# qemu-desktop-image.sh — imagen de disco EFI GNOME para QEMU (virtio-gpu + software GL), auto-lanza
|
|
# gnome-shell. Hermano de scripts/kde/qemu-desktop-image.sh; comparte con él la base metal, la
|
|
# inyección de mesa/LLVM/musl y el truco del console-getty, y sólo diverge en lo que GNOME necesita.
|
|
#
|
|
# work/metal-rootfs base: arje-zero PID1 + busybox + kernel(EFI-stub) + firmware
|
|
# work/gnome-rootfs cierre de gnome-shell (lo arma scripts/gnome/hydrate-gnome.sh, 108/108)
|
|
# + store/*-mesa-llvmpipe GBM/EGL de software (el rootfs GNOME sólo trae iris_dri, que es Intel real)
|
|
# + store/*-llvm18 libLLVM que llvmpipe JIT-linkea
|
|
# + store/*-dejavu-fonts el rootfs GNOME NO trae NINGUNA fuente
|
|
# → work/gnome-qemu-rootfs (hardlinks) → work/hammer-gnome-qemu.img
|
|
#
|
|
# Uso: scripts/gnome/qemu-desktop-image.sh
|
|
set -euo pipefail
|
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
|
|
|
|
BASE="${BASE:-work/metal-rootfs}"
|
|
GNOME="${GNOME:-work/gnome-rootfs}"
|
|
MESA="${MESA:-$(ls -dt store/*-mesa-llvmpipe 2>/dev/null | head -1)}"
|
|
LLVM="${LLVM:-$(ls -dt store/*-llvm18 2>/dev/null | head -1)}"
|
|
FONTS="${FONTS:-$(ls -dt store/*-dejavu-fonts 2>/dev/null | head -1)}"
|
|
MERGED="${MERGED:-work/gnome-qemu-rootfs}"
|
|
IMG="${IMG:-work/hammer-gnome-qemu.img}"
|
|
|
|
[ -d "$BASE" ] || { echo "falta $BASE" >&2; exit 1; }
|
|
[ -d "$GNOME" ] || { echo "falta $GNOME — corré scripts/gnome/hydrate-gnome.sh" >&2; exit 1; }
|
|
[ -n "$MESA" ] && [ -d "$MESA" ] || { echo "falta mesa-llvmpipe en store/" >&2; exit 1; }
|
|
|
|
echo "==> fundiendo rootfs por hardlinks (base metal + cierre GNOME)"
|
|
rm -rf "$MERGED"
|
|
cp -al "$BASE" "$MERGED"
|
|
cp -al --remove-destination "$GNOME"/. "$MERGED"/
|
|
|
|
echo "==> parcheando mesa software desde $MESA"
|
|
# El cierre GNOME trae mesa con SÓLO iris_dri.so (Intel real). En QEMU no hay GPU: hace falta
|
|
# kms_swrast. Se copian los .so de mesa-llvmpipe ENCIMA (--remove-destination rompe el hardlink).
|
|
mkdir -p "$MERGED"/usr/lib/dri
|
|
cp -a --remove-destination "$MESA"/usr/lib/dri/. "$MERGED"/usr/lib/dri/ 2>/dev/null || true
|
|
for so in libglapi libgbm libEGL libGLESv2 libGL libgallium; do
|
|
cp -a --remove-destination "$MESA"/usr/lib/$so* "$MERGED"/usr/lib/ 2>/dev/null || true
|
|
done
|
|
ls "$MERGED"/usr/lib/dri/*swrast* >/dev/null 2>&1 && echo " ✓ kms_swrast instalado" || { echo " ✗ swrast no entró" >&2; exit 1; }
|
|
|
|
echo "==> inyectando libLLVM.18 (llvmpipe lo JIT-linkea)"
|
|
[ -n "$LLVM" ] && [ -d "$LLVM" ] || { echo " ✗ falta llvm18 en store/ (set LLVM=)" >&2; exit 1; }
|
|
cp -a --remove-destination "$LLVM"/usr/lib/libLLVM.so.18.1 "$MERGED"/usr/lib/
|
|
ln -sf libLLVM.so.18.1 "$MERGED"/usr/lib/libLLVM.so.18
|
|
ln -sf libLLVM.so.18.1 "$MERGED"/usr/lib/libLLVM.so
|
|
|
|
echo "==> inyectando el runtime C++ de Alpine (deuda CONOCIDA, no accidente)"
|
|
# libLLVM y mesa-llvmpipe se construyen con gcc ⇒ NEEDean libstdc++/libgcc_s. Y además —esto es
|
|
# propio de GNOME y quedó documentado al auditar el cierre— **libmozjs-128.so también los NEEDea**,
|
|
# o sea que la cadena gnome-shell → libgjs → libmozjs arrastra el runtime C++ de Alpine. Es la
|
|
# "última milla" de matar-gcc que se cerró para cmake con -static-libstdc++ y que spidermonkey no
|
|
# cubrió. Mientras eso no se re-selle (radio medido: 2), la imagen los inyecta o el shell no carga.
|
|
for lib in libgcc_s.so.1 libstdc++.so.6; do
|
|
src=".dev-fs/alpine/usr/lib/$lib"
|
|
if [ -r "$src" ]; then cp -a --remove-destination "$src"* "$MERGED"/usr/lib/ 2>/dev/null || true
|
|
else echo " ⚠ sin $lib ($src)"; fi
|
|
done
|
|
|
|
echo "==> inyectando musl — y los DOS sonames"
|
|
# La base metal es 100% estática (arje-zero+busybox) y el cierre GNOME no trae libc. En musl el
|
|
# loader ES libc (mismo fichero). Van dos nombres porque conviven dos toolchains: zig-cc emite
|
|
# NEEDED `libc.so` (359 consumidores) y el gcc/clang de Alpine emite `libc.musl-x86_64.so.1`
|
|
# (17: todo nss + spidermonkey). Es la MISMA libc; sin el segundo symlink esos 17 no cargan.
|
|
MUSL="${MUSL:-/usr/lib/musl/lib/libc.so}"
|
|
[ -r "$MUSL" ] || { echo "no encuentro musl libc ($MUSL) — set MUSL=" >&2; exit 1; }
|
|
mkdir -p "$MERGED/usr/lib/musl/lib" "$MERGED/lib"
|
|
cp -a --remove-destination "$MUSL" "$MERGED/usr/lib/musl/lib/libc.so"
|
|
ln -sf /usr/lib/musl/lib/libc.so "$MERGED/lib/ld-musl-x86_64.so.1"
|
|
ln -sf /usr/lib/musl/lib/libc.so "$MERGED/usr/lib/libc.so"
|
|
ln -sf /usr/lib/musl/lib/libc.so "$MERGED/usr/lib/libc.musl-x86_64.so.1"
|
|
echo " ✓ libc.so + libc.musl-x86_64.so.1"
|
|
|
|
if [ -n "$FONTS" ] && [ -d "$FONTS" ]; then
|
|
echo "==> inyectando fuentes ($(basename "$FONTS"))"
|
|
cp -a --remove-destination "$FONTS"/usr/share/fonts/. "$MERGED"/usr/share/fonts/ 2>/dev/null || \
|
|
{ mkdir -p "$MERGED"/usr/share/fonts && cp -a "$FONTS"/usr/share/fonts/. "$MERGED"/usr/share/fonts/; }
|
|
else
|
|
echo " ⚠ sin dejavu-fonts en el store: el escritorio saldrá con tofu"
|
|
fi
|
|
|
|
echo "==> inyectando arje-logind-compat (el login1 del fractal)"
|
|
# EL ESLABÓN QUE FALTABA, descubierto arrancando la imagen: mutter levanta como display server y
|
|
# muere en `Failed to setup: Failed to find any matching session`. Ese chequeo es de logind — mutter
|
|
# pregunta por su sesión vía la C-ABI sd-login, que acá provee libelogind (arje-sdlogin-compat)
|
|
# LEYENDO /run/systemd/{sessions,users,seats}. Pero alguien tiene que ESCRIBIR esos ficheros, y ese
|
|
# alguien es arje-logind-compat, el daemon D-Bus que se hace pasar por org.freedesktop.login1.
|
|
# libelogind entra solo (es dep de mutter); el daemon no, porque nadie lo declara como dep de build.
|
|
# El artefacto se elige por `hammer hash` sobre la receta —el que corresponde a la receta de HOY—, no
|
|
# por el más reciente del store: con dos artefactos del mismo paquete conviviendo, la fecha no dice
|
|
# cuál es el vigente. ALC_BIN permite inyectar un binario RECIÉN COMPILADO en vez del artefacto sellado. Es para probar
|
|
# un cambio en arje-compat antes de commitear/pushear tawasuyu y re-sellar la receta (la receta pina
|
|
# un commit, así que sin push no hay artefacto nuevo). Lo que se pruebe así NO es reproducible: es
|
|
# andamio de iteración, y el camino de producción sigue siendo el artefacto del store.
|
|
if [ -n "${ALC_BIN:-}" ] && [ -x "$ALC_BIN" ]; then
|
|
install -Dm755 "$ALC_BIN" "$MERGED"/usr/bin/arje-logind-compat
|
|
echo " ⚠ binario LOCAL (no sellado): $ALC_BIN"
|
|
elif ALC="${ALC:-store/$(./target/release/hammer --store store hash recipes/arje-logind-compat.toml 2>/dev/null | tail -1 | sed 's/^b3://')-arje-logind-compat}"; [ -d "$ALC" ]; then
|
|
install -Dm755 "$ALC"/usr/bin/arje-logind-compat "$MERGED"/usr/bin/arje-logind-compat
|
|
echo " ✓ $(basename "$ALC" | cut -c1-12)"
|
|
else
|
|
echo " ✗ falta arje-logind-compat en store/ — mutter no hallará sesión" >&2; exit 1
|
|
fi
|
|
|
|
echo "==> política D-Bus para org.freedesktop.login1"
|
|
# Segundo muro del mismo eslabón: con el bus ya visible, dbus RECHAZA que el daemon se adueñe del
|
|
# nombre — "not allowed to own the service org.freedesktop.login1 due to security policies". El
|
|
# system.conf de dbus trae `<deny own="*"/>` por defecto: cada servicio de sistema aporta su propio
|
|
# fichero de política, y systemd normalmente instala el de login1. Acá no hay systemd, y el artefacto
|
|
# de arje-logind-compat es sólo el binario ⇒ nadie pone la política. La escribimos: root puede
|
|
# adueñarse del nombre, y cualquiera puede hablarle (mutter consulta login1 sin ser root).
|
|
# TODO: esto pertenece al artefacto de arje-logind-compat, no a la imagen; acá está de forma
|
|
# explícita para que se vea qué falta empaquetar del lado de arje.
|
|
install -d "$MERGED/usr/share/dbus-1/system.d"
|
|
cat > "$MERGED/usr/share/dbus-1/system.d/org.freedesktop.login1.conf" <<'XML'
|
|
<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN"
|
|
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
|
<busconfig>
|
|
<policy user="root">
|
|
<allow own="org.freedesktop.login1"/>
|
|
</policy>
|
|
<policy context="default">
|
|
<allow send_destination="org.freedesktop.login1"/>
|
|
<allow receive_sender="org.freedesktop.login1"/>
|
|
</policy>
|
|
</busconfig>
|
|
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#*:}"
|
|
if ! grep -q '^messagebus:' "$MERGED/etc/$file" 2>/dev/null; then
|
|
tmp="$MERGED/etc/$file.new"
|
|
{ cat "$MERGED/etc/$file"; echo "$line"; } > "$tmp"
|
|
chmod 644 "$tmp"; mv "$tmp" "$MERGED/etc/$file"
|
|
fi
|
|
done
|
|
|
|
# El launch-helper del bus de SISTEMA tiene que ser setuid root. Es el que dbus-daemon ejecuta para
|
|
# activar un servicio bajo demanda, y comprueba SUS PROPIOS permisos antes de hacer nada: si no es
|
|
# setuid root, se niega, y el cliente recibe
|
|
# Error calling StartServiceByName for <servicio>: The permission of the setuid helper is not correct
|
|
# que es exactamente lo que reportaba colord al arrancar la sesión. Sin esto **ninguna** activación
|
|
# por bus de sistema funciona; con esto, cualquier servicio con su `.service` instalado arranca solo y
|
|
# no hace falta lanzarlo a mano desde `gnome-start`.
|
|
for h in usr/libexec/dbus-daemon-launch-helper usr/lib/dbus-1.0/dbus-daemon-launch-helper; do
|
|
if [ -f "$MERGED/$h" ]; then
|
|
chown 0:81 "$MERGED/$h" 2>/dev/null || true # root:messagebus
|
|
chmod 4750 "$MERGED/$h"
|
|
echo " ✓ setuid en /$h"
|
|
fi
|
|
done
|
|
|
|
# GNOME_MODE se hornea como fichero: el getty ejecuta gnome-start sin ambiente heredable.
|
|
printf 'GNOME_MODE=%s\n' "${GNOME_MODE:-drm}" > "$MERGED/etc/gnome-mode"
|
|
echo "==> modo de arranque: ${GNOME_MODE:-drm}"
|
|
# KMS_MODE viaja por el mismo camino, y sólo si se pide: `gnome-start` lo traduce a
|
|
# MUTTER_DEBUG_FORCE_KMS_MODE. Vacío = atomic (el default de mutter). `simple` = el modeset legacy
|
|
# que se heredó de KDE y dejaba la pantalla en negro.
|
|
if [ -n "${KMS_MODE:-}" ]; then
|
|
printf 'KMS_MODE=%s\n' "$KMS_MODE" >> "$MERGED/etc/gnome-mode"
|
|
echo "==> KMS forzado a: $KMS_MODE"
|
|
fi
|
|
|
|
echo "==> instalando /usr/bin/gnome-start"
|
|
install -Dm755 scripts/gnome/gnome-start-qemu.sh "$MERGED/usr/bin/gnome-start"
|
|
|
|
echo "==> auto-lanzar GNOME: console-getty ejecuta gnome-start (no /bin/sh)"
|
|
python3 - "$MERGED/ente/seed.card.json" <<'PY'
|
|
import json, sys, os
|
|
p = sys.argv[1]
|
|
c = json.load(open(p))
|
|
for g in c.get("genesis", []):
|
|
if g.get("label") == "console-getty":
|
|
argv = g["payload"]["Native"]["argv"]
|
|
argv[:] = [("/usr/bin/gnome-start" if a == "/bin/sh" else a) for a in argv]
|
|
print(" console-getty argv →", argv)
|
|
tmp = p + ".new"
|
|
with open(tmp, "w") as f:
|
|
json.dump(c, f, indent=2)
|
|
os.chmod(tmp, 0o644)
|
|
os.replace(tmp, p)
|
|
PY
|
|
|
|
# /var/run → /run: arje-logind-compat busca el bus de sistema en la ruta LEGACY
|
|
# (/var/run/dbus/system_bus_socket) y dbus-daemon escucha en /run/dbus/system_bus_socket. En casi
|
|
# toda distro /var/run es un symlink a /run desde hace 15 años; esta base metal es tan mínima que ni
|
|
# siquiera tiene /var/run. Sin el enlace el daemon reporta `bus_present=false` y se va a "modo idle",
|
|
# o sea que nunca registra login1 ni escribe /run/systemd/sessions ⇒ mutter no halla sesión.
|
|
ln -sfn /run "$MERGED/var/run"
|
|
|
|
mkdir -p "$MERGED/root/.config" "$MERGED/root/.cache" "$MERGED/run/user/0" "$MERGED/tmp"
|
|
chmod 1777 "$MERGED/tmp"
|
|
|
|
echo "==> rootfs fundido: $(du -sh "$MERGED" | cut -f1)"
|
|
need_mb=$(du -sm "$MERGED" | cut -f1)
|
|
ROOT_SIZE="${ROOT_SIZE:-$(( need_mb + 1536 ))}"
|
|
echo "==> ROOT_SIZE=${ROOT_SIZE} MiB"
|
|
|
|
export ROOTFS="$MERGED" IMG ROOT_SIZE
|
|
export ESP_MB="${ESP_MB:-128}" STORE_SIZE="${STORE_SIZE:-512}" STATE_SIZE="${STATE_SIZE:-768}"
|
|
exec ./scripts/install-image-efi.sh
|