tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1518Shell 194Rust 108Markdown 983677 Definiciones

⎇ 7190cc48a36d768c9abe4b84f7cf856f33b8995d scripts/gnome/qemu-desktop-image.sh Shell · 247 líneas · 14.8 KB Historia crudo

estado: cosecha granja 2026-07-30T23:20:59Z — avance del árbol KDE 7190cc48a · sergio ·

1#!/usr/bin/env bash
2# qemu-desktop-image.sh — imagen de disco EFI GNOME para QEMU (virtio-gpu + software GL), auto-lanza
3# gnome-shell. Hermano de scripts/kde/qemu-desktop-image.sh; comparte con él la base metal, la
4# inyección de mesa/LLVM/musl y el truco del console-getty, y sólo diverge en lo que GNOME necesita.
5#
6#   work/metal-rootfs      base: arje-zero PID1 + busybox + kernel(EFI-stub) + firmware
7#   work/gnome-rootfs      cierre de gnome-shell (lo arma scripts/gnome/hydrate-gnome.sh, 108/108)
8#   + store/*-mesa-llvmpipe   GBM/EGL de software (el rootfs GNOME sólo trae iris_dri, que es Intel real)
9#   + store/*-llvm18          libLLVM que llvmpipe JIT-linkea
10#   + store/*-dejavu-fonts    el rootfs GNOME NO trae NINGUNA fuente
11#   → work/gnome-qemu-rootfs (hardlinks) → work/hammer-gnome-qemu.img
12#
13# Uso:  scripts/gnome/qemu-desktop-image.sh
14set -euo pipefail
15ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
16
17BASE="${BASE:-work/metal-rootfs}"
18GNOME="${GNOME:-work/gnome-rootfs}"
19MESA="${MESA:-$(ls -dt store/*-mesa-llvmpipe 2>/dev/null | head -1)}"
20LLVM="${LLVM:-$(ls -dt store/*-llvm18 2>/dev/null | head -1)}"
21FONTS="${FONTS:-$(ls -dt store/*-dejavu-fonts 2>/dev/null | head -1)}"
22MERGED="${MERGED:-work/gnome-qemu-rootfs}"
23IMG="${IMG:-work/hammer-gnome-qemu.img}"
24
25[ -d "$BASE" ]  || { echo "falta $BASE" >&2; exit 1; }
26[ -d "$GNOME" ] || { echo "falta $GNOME — corré scripts/gnome/hydrate-gnome.sh" >&2; exit 1; }
27[ -n "$MESA" ] && [ -d "$MESA" ] || { echo "falta mesa-llvmpipe en store/" >&2; exit 1; }
28
29echo "==> fundiendo rootfs por hardlinks (base metal + cierre GNOME)"
30rm -rf "$MERGED"
31cp -al "$BASE" "$MERGED"
32cp -al --remove-destination "$GNOME"/. "$MERGED"/
33
34# ── PID1 DESDE EL STORE, no desde el directorio base ────────────────────────────────────────────
35# `work/metal-rootfs` se armó a mano en la campaña de metal y quedó CONGELADO: su `arje-zero` era de
36# 2026-06-19. Nadie lo re-generaba al re-sellar la receta, así que cada imagen de escritorio arrancaba
37# con un PID1 de hace mes y medio sin que nada lo dijera. Así se comió el fix del `identity mismatch`:
38# arje-zero se había arreglado río arriba y la VM seguía imprimiendo el mensaje viejo.
39#
40# El síntoma es engañoso porque el directorio base es LEGÍTIMO para todo lo demás (busybox, firmware,
41# el kernel EFI-stub): sólo la pieza que también es receta se queda atrás. Regla: **si algo del rootfs
42# tiene receta, la imagen lo toma del artefacto sellado, no de la copia congelada.**
43#
44# Se elige por `hammer hash` —el artefacto que corresponde a la receta de HOY—, mismo criterio que los
45# dos compat daemons de más abajo; el más reciente por fecha miente en cuanto conviven dos.
46echo "==> PID1: arje-zero desde el store (no desde $BASE)"
47AZ="${AZ:-store/$(./target/release/hammer --store store hash recipes/arje-zero.toml 2>/dev/null | tail -1 | sed 's/^b3://')-arje-zero}"
48if [ -d "$AZ" ] && [ -x "$AZ/usr/bin/arje-zero" ]; then
49  install -Dm755 "$AZ"/usr/bin/arje-zero "$MERGED"/usr/bin/arje-zero
50  echo "   ✓ $(basename "$AZ" | cut -c1-12)  (el de $BASE era de $(stat -c '%y' "$BASE"/usr/bin/arje-zero 2>/dev/null | cut -d' ' -f1))"
51else
52  echo "   ✗ falta arje-zero sellado en store/ — la imagen usaría el PID1 CONGELADO de $BASE" >&2
53  echo "     construílo:  ./target/release/hammer build recipes/arje-zero.toml --store store" >&2
54  exit 1
55fi
56
57echo "==> parcheando mesa software desde $MESA"
58# El cierre GNOME trae mesa con SÓLO iris_dri.so (Intel real). En QEMU no hay GPU: hace falta
59# kms_swrast. Se copian los .so de mesa-llvmpipe ENCIMA (--remove-destination rompe el hardlink).
60mkdir -p "$MERGED"/usr/lib/dri
61cp -a --remove-destination "$MESA"/usr/lib/dri/. "$MERGED"/usr/lib/dri/ 2>/dev/null || true
62for so in libglapi libgbm libEGL libGLESv2 libGL libgallium; do
63  cp -a --remove-destination "$MESA"/usr/lib/$so* "$MERGED"/usr/lib/ 2>/dev/null || true
64done
65ls "$MERGED"/usr/lib/dri/*swrast* >/dev/null 2>&1 && echo "   ✓ kms_swrast instalado" || { echo "   ✗ swrast no entró" >&2; exit 1; }
66
67echo "==> inyectando libLLVM.18 (llvmpipe lo JIT-linkea)"
68[ -n "$LLVM" ] && [ -d "$LLVM" ] || { echo "   ✗ falta llvm18 en store/ (set LLVM=)" >&2; exit 1; }
69cp -a --remove-destination "$LLVM"/usr/lib/libLLVM.so.18.1 "$MERGED"/usr/lib/
70ln -sf libLLVM.so.18.1 "$MERGED"/usr/lib/libLLVM.so.18
71ln -sf libLLVM.so.18.1 "$MERGED"/usr/lib/libLLVM.so
72
73echo "==> inyectando el runtime C++ de Alpine (deuda CONOCIDA, no accidente)"
74# libLLVM y mesa-llvmpipe se construyen con gcc ⇒ NEEDean libstdc++/libgcc_s. Y además —esto es
75# propio de GNOME y quedó documentado al auditar el cierre— **libmozjs-128.so también los NEEDea**,
76# o sea que la cadena gnome-shell → libgjs → libmozjs arrastra el runtime C++ de Alpine. Es la
77# "última milla" de matar-gcc que se cerró para cmake con -static-libstdc++ y que spidermonkey no
78# cubrió. Mientras eso no se re-selle (radio medido: 2), la imagen los inyecta o el shell no carga.
79for lib in libgcc_s.so.1 libstdc++.so.6; do
80  src=".dev-fs/alpine/usr/lib/$lib"
81  if [ -r "$src" ]; then cp -a --remove-destination "$src"* "$MERGED"/usr/lib/ 2>/dev/null || true
82  else echo "   ⚠ sin $lib ($src)"; fi
83done
84
85echo "==> inyectando musl — y los DOS sonames"
86# La base metal es 100% estática (arje-zero+busybox) y el cierre GNOME no trae libc. En musl el
87# loader ES libc (mismo fichero). Van dos nombres porque conviven dos toolchains: zig-cc emite
88# NEEDED `libc.so` (359 consumidores) y el gcc/clang de Alpine emite `libc.musl-x86_64.so.1`
89# (17: todo nss + spidermonkey). Es la MISMA libc; sin el segundo symlink esos 17 no cargan.
90MUSL="${MUSL:-/usr/lib/musl/lib/libc.so}"
91[ -r "$MUSL" ] || { echo "no encuentro musl libc ($MUSL) — set MUSL=" >&2; exit 1; }
92mkdir -p "$MERGED/usr/lib/musl/lib" "$MERGED/lib"
93cp -a --remove-destination "$MUSL" "$MERGED/usr/lib/musl/lib/libc.so"
94ln -sf /usr/lib/musl/lib/libc.so "$MERGED/lib/ld-musl-x86_64.so.1"
95ln -sf /usr/lib/musl/lib/libc.so "$MERGED/usr/lib/libc.so"
96ln -sf /usr/lib/musl/lib/libc.so "$MERGED/usr/lib/libc.musl-x86_64.so.1"
97echo "   ✓ libc.so + libc.musl-x86_64.so.1"
98
99if [ -n "$FONTS" ] && [ -d "$FONTS" ]; then
100  echo "==> inyectando fuentes ($(basename "$FONTS"))"
101  cp -a --remove-destination "$FONTS"/usr/share/fonts/. "$MERGED"/usr/share/fonts/ 2>/dev/null || \
102    { mkdir -p "$MERGED"/usr/share/fonts && cp -a "$FONTS"/usr/share/fonts/. "$MERGED"/usr/share/fonts/; }
103else
104  echo "   ⚠ sin dejavu-fonts en el store: el escritorio saldrá con tofu"
105fi
106
107echo "==> inyectando arje-logind-compat (el login1 del fractal)"
108# EL ESLABÓN QUE FALTABA, descubierto arrancando la imagen: mutter levanta como display server y
109# muere en `Failed to setup: Failed to find any matching session`. Ese chequeo es de logind — mutter
110# pregunta por su sesión vía la C-ABI sd-login, que acá provee libelogind (arje-sdlogin-compat)
111# LEYENDO /run/systemd/{sessions,users,seats}. Pero alguien tiene que ESCRIBIR esos ficheros, y ese
112# alguien es arje-logind-compat, el daemon D-Bus que se hace pasar por org.freedesktop.login1.
113# libelogind entra solo (es dep de mutter); el daemon no, porque nadie lo declara como dep de build.
114# El artefacto se elige por `hammer hash` sobre la receta —el que corresponde a la receta de HOY—, no
115# por el más reciente del store: con dos artefactos del mismo paquete conviviendo, la fecha no dice
116# cuál es el vigente. ALC_BIN permite inyectar un binario RECIÉN COMPILADO en vez del artefacto sellado. Es para probar
117# un cambio en arje-compat antes de commitear/pushear tawasuyu y re-sellar la receta (la receta pina
118# un commit, así que sin push no hay artefacto nuevo). Lo que se pruebe así NO es reproducible: es
119# andamio de iteración, y el camino de producción sigue siendo el artefacto del store.
120if [ -n "${ALC_BIN:-}" ] && [ -x "$ALC_BIN" ]; then
121  install -Dm755 "$ALC_BIN" "$MERGED"/usr/bin/arje-logind-compat
122  echo "   ⚠ binario LOCAL (no sellado): $ALC_BIN"
123elif 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
124  install -Dm755 "$ALC"/usr/bin/arje-logind-compat "$MERGED"/usr/bin/arje-logind-compat
125  # …y su POLÍTICA D-Bus, que desde 2026-07-30 viaja en el artefacto. Se copia, no se escribe: el
126  # dueño del fichero es el paquete. Ojo — inyectar «sólo el binario» era el bug que destapó la
127  # comprobación de abajo: el artefacto traía el .conf y la imagen lo dejaba afuera.
128  install -d "$MERGED/usr/share/dbus-1/system.d"
129  cp -a --remove-destination "$ALC"/usr/share/dbus-1/system.d/. "$MERGED"/usr/share/dbus-1/system.d/ 2>/dev/null || true
130  echo "   ✓ $(basename "$ALC" | cut -c1-12)"
131else
132  echo "   ✗ falta arje-logind-compat en store/ — mutter no hallará sesión" >&2; exit 1
133fi
134
135# La POLÍTICA D-Bus de login1 YA NO SE ESCRIBE ACÁ (2026-07-30): la trae el artefacto de
136# `arje-logind-compat`, que es su dueño. Este bloque tenía un TODO diciendo exactamente eso, y estaba
137# bien puesto: un servicio que no puede adueñarse de su nombre no es un servicio, así que su política
138# es parte de lo que el paquete promete. Teniéndola acá, cualquier OTRA imagen —metal, KDE, mirada—
139# se llevaba el daemon y no podía usarlo. Se comprueba abajo, junto con la de polkit.
140install -d "$MERGED/usr/share/dbus-1/system.d"
141
142echo "==> inyectando arje-polkit-compat (el PolicyKit1 del fractal) + su política"
143# EL TERCER ESLABÓN DEL MISMO TIPO, y lo destapó la lista de nombres del bus: accounts-daemon y
144# upowerd quedaban CONECTADOS (`:1.0`, `:1.1`) sin adquirir su nombre, porque los dos se bloquean en
145# `polkit_authority_get_sync()` esperando a `org.freedesktop.PolicyKit1`. Nuestra receta `polkit` es
146# libs-only a propósito (el demonio lo pone arje), así que nadie servía ese nombre y gnome-shell
147# esperaba 25 s por cada uno.
148# ⚠ El shim autoriza TODO (postura de sistema confiado ya tomada por arje) — ver su receta.
149if 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
150  install -Dm755 "$APC"/usr/bin/arje-polkit-compat "$MERGED"/usr/bin/arje-polkit-compat
151  install -d "$MERGED/usr/share/dbus-1/system.d"
152  cp -a --remove-destination "$APC"/usr/share/dbus-1/system.d/. "$MERGED"/usr/share/dbus-1/system.d/ 2>/dev/null || true
153  echo "   ✓ $(basename "$APC" | cut -c1-12)"
154else
155  echo "   ✗ falta arje-polkit-compat en store/ — accounts-daemon y upowerd no adquirirán su nombre" >&2
156  exit 1
157fi
158# Su política tampoco se escribe acá: viaja en el artefacto (ver recipes/arje-polkit-compat.toml, que
159# explica por qué el fichero se llama `zz-` y no reemplaza al de polkit).
160#
161# ── COMPROBACIÓN, que es lo que reemplaza a escribirlas ────────────────────────────────────────
162# Mover algo a un artefacto sólo es una mejora si se NOTA cuando falta. Sin esto, un artefacto viejo
163# sin su .conf daría el mismo fallo tardío y confuso de antes: el daemon arranca, no adquiere el
164# nombre, y el escritorio espera 25 s por servicio sin decir por qué.
165for pol in org.freedesktop.login1.conf zz-arje-polkit-compat.conf; do
166  if [ -f "$MERGED/usr/share/dbus-1/system.d/$pol" ]; then
167    echo "   ✓ $pol (del artefacto)"
168  else
169    echo "   ✗ falta $pol — el artefacto que lo trae es viejo; re-sellá su receta" >&2
170    exit 1
171  fi
172done
173
174echo "==> usuario messagebus (el system bus hace setuid a él)"
175for f in passwd:messagebus:x:81:81:messagebus:/dev/null:/sbin/nologin group:messagebus:x:81:; do
176  file="${f%%:*}"; line="${f#*:}"
177  if ! grep -q '^messagebus:' "$MERGED/etc/$file" 2>/dev/null; then
178    tmp="$MERGED/etc/$file.new"
179    { cat "$MERGED/etc/$file"; echo "$line"; } > "$tmp"
180    chmod 644 "$tmp"; mv "$tmp" "$MERGED/etc/$file"
181  fi
182done
183
184# El launch-helper del bus de SISTEMA tiene que ser setuid root. Es el que dbus-daemon ejecuta para
185# activar un servicio bajo demanda, y comprueba SUS PROPIOS permisos antes de hacer nada: si no es
186# setuid root, se niega, y el cliente recibe
187#   Error calling StartServiceByName for <servicio>: The permission of the setuid helper is not correct
188# que es exactamente lo que reportaba colord al arrancar la sesión. Sin esto **ninguna** activación
189# por bus de sistema funciona; con esto, cualquier servicio con su `.service` instalado arranca solo y
190# no hace falta lanzarlo a mano desde `gnome-start`.
191for h in usr/libexec/dbus-daemon-launch-helper usr/lib/dbus-1.0/dbus-daemon-launch-helper; do
192  if [ -f "$MERGED/$h" ]; then
193    chown 0:81 "$MERGED/$h" 2>/dev/null || true   # root:messagebus
194    chmod 4750 "$MERGED/$h"
195    echo "   ✓ setuid en /$h"
196  fi
197done
198
199# GNOME_MODE se hornea como fichero: el getty ejecuta gnome-start sin ambiente heredable.
200printf 'GNOME_MODE=%s\n' "${GNOME_MODE:-drm}" > "$MERGED/etc/gnome-mode"
201echo "==> modo de arranque: ${GNOME_MODE:-drm}"
202# KMS_MODE viaja por el mismo camino, y sólo si se pide: `gnome-start` lo traduce a
203# MUTTER_DEBUG_FORCE_KMS_MODE. Vacío = atomic (el default de mutter). `simple` = el modeset legacy
204# que se heredó de KDE y dejaba la pantalla en negro.
205if [ -n "${KMS_MODE:-}" ]; then
206  printf 'KMS_MODE=%s\n' "$KMS_MODE" >> "$MERGED/etc/gnome-mode"
207  echo "==> KMS forzado a: $KMS_MODE"
208fi
209
210echo "==> instalando /usr/bin/gnome-start"
211install -Dm755 scripts/gnome/gnome-start-qemu.sh "$MERGED/usr/bin/gnome-start"
212
213echo "==> auto-lanzar GNOME: console-getty ejecuta gnome-start (no /bin/sh)"
214python3 - "$MERGED/ente/seed.card.json" <<'PY'
215import json, sys, os
216p = sys.argv[1]
217c = json.load(open(p))
218for g in c.get("genesis", []):
219    if g.get("label") == "console-getty":
220        argv = g["payload"]["Native"]["argv"]
221        argv[:] = [("/usr/bin/gnome-start" if a == "/bin/sh" else a) for a in argv]
222        print("  console-getty argv →", argv)
223tmp = p + ".new"
224with open(tmp, "w") as f:
225    json.dump(c, f, indent=2)
226os.chmod(tmp, 0o644)
227os.replace(tmp, p)
228PY
229
230# /var/run → /run: arje-logind-compat busca el bus de sistema en la ruta LEGACY
231# (/var/run/dbus/system_bus_socket) y dbus-daemon escucha en /run/dbus/system_bus_socket. En casi
232# toda distro /var/run es un symlink a /run desde hace 15 años; esta base metal es tan mínima que ni
233# siquiera tiene /var/run. Sin el enlace el daemon reporta `bus_present=false` y se va a "modo idle",
234# o sea que nunca registra login1 ni escribe /run/systemd/sessions ⇒ mutter no halla sesión.
235ln -sfn /run "$MERGED/var/run"
236
237mkdir -p "$MERGED/root/.config" "$MERGED/root/.cache" "$MERGED/run/user/0" "$MERGED/tmp"
238chmod 1777 "$MERGED/tmp"
239
240echo "==> rootfs fundido: $(du -sh "$MERGED" | cut -f1)"
241need_mb=$(du -sm "$MERGED" | cut -f1)
242ROOT_SIZE="${ROOT_SIZE:-$(( need_mb + 1536 ))}"
243echo "==> ROOT_SIZE=${ROOT_SIZE} MiB"
244
245export ROOTFS="$MERGED" IMG ROOT_SIZE
246export ESP_MB="${ESP_MB:-128}" STORE_SIZE="${STORE_SIZE:-512}" STATE_SIZE="${STATE_SIZE:-768}"
247exec ./scripts/install-image-efi.sh