fix(medio): el medio arrancaba a una consola de texto, con el escritorio adentro
Medido el 2026-10-15 sobre el arbol compuesto y la semilla del piso: `instalador-variantes.sh` NO tocaba la seed, y la del piso trae `hammerd`, `console-getty` y `sshd` — nada mas. O sea que el medio de mirada arrancaba **sin que nada levantara el escritorio**, con mirada-compositor, mirada-greeter, el riel y las fuentes adentro. Y la herramienta del repo lo venia denunciando sola, `targets.py --services escritorio-mirada`: AVISO mirada: `mirada-compositor` esta en la imagen y TRAE este servicio, pero el perfil no lo arranca AVISO seatd: idem · shuma-daemon: idem · shuma-gateway: idem (el perfil no arranca ningun servicio) Es la figura de `foot` una vuelta mas arriba, y el propio targets.py la tiene escrita: «una imagen puede salir 121/121 sellada, con el demonio adentro, y sin que nada lo levante nunca». Son dos mitades y faltaban las dos: 1. HABILITAR en el perfil. Un servicio se declara en la receta ([[service]]) y se habilita en el perfil (`servicios = [...]`); con la primera sola, `--service-paths` devuelve vacio y no se genera ninguna Card. `base` habilita `uevent-relay` (es de la maquina: sin el, lo que se enchufa despues de arrancar no aparece nunca) y `escritorio-mirada` habilita `seatd`, `mirada`, `shuma-daemon` y `shuma-gateway`. El ORDEN importa: seatd antes que mirada, porque sin el seat el compositor no obtiene el DRM y muere con el dispositivo ocupado. 2. COMPONER la seed al armar. El armador pide las rutas con `targets.py --service-paths` de cada perfil que compone, traduce con `takana service-cards` —la UNICA traduccion [[service]] → Card— y compone con `scripts/gnome/inyectar-cards.py`, que es idempotente por label y usa os.replace porque la seed es un hardlink al store. Es el mismo par que usan servidor-image.sh y los armadores de gnome/kde/cosmic: mirada era el unico que no lo hacia. Si no hay servicios habilitados, lo DICE; si no puede componer, FALLA el armado en vez de entregar una imagen que arranca a una consola. Certificado sobre una copia de la semilla real del piso: pasa de [hammerd, console-getty, sshd] a [... uevent-relay, seatd, mirada, shuma-daemon, shuma-gateway], en ese orden, y `arje-zero --attest-check` la valida — que es el unico juez que cuenta, porque una seed ilegible no da un error en pantalla, da una maquina que no arranca. Y scripts/probar-medio-armado.sh: 25 comprobaciones sobre el arbol compuesto (sin root y sin montar nada) con el POR QUE de cada una al lado, porque cada una fue un defecto real de estos dos dias — fuentes, foot, xkb, firmware de wifi/amdgpu/ethernet, uevent-relay, los bits de doas y unix_chkpwd, que bin/su no sea un symlink a busybox, que el instalador llame a cuenta_aplicar, y que algo arranque el escritorio. Distingue los dos caminos de arranque (Card en la seed vs /sbin/init del medio live) y los dos instaladores: un verificador que marca en rojo lo que no corresponde al tipo de medio se desactiva solo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
3 archivos · +174 −0
- docs/state/targets.toml +14 −0
- scripts/instalador-variantes.sh +31 −0
- scripts/probar-medio-armado.sh +129 −0
diff --git a/docs/state/targets.toml b/docs/state/targets.toml
index 7241ce2d..cedfd9a7 100644
--- a/docs/state/targets.toml
+++ b/docs/state/targets.toml
@@ -112,6 +112,10 @@ paquetes = [
descripcion = "userland foundational C: la distro arranca y se usa (shell, VCS, privilegios, red, disco, TLS, proceso, editor, docs)"
# Lift verbatim de BASE_SYSTEM_PKGS (scripts/product-userland-from-repo.sh). De-Alpinizado y firmado,
# tandas base-system-1..3.
+# El reenviador de uevents es de la MÁQUINA, no de un escritorio: sin él, lo que se
+# enchufa después de arrancar no aparece nunca (libudev-zero escucha un grupo netlink al
+# que, sin udevd, nadie emite). Va en `base` por el mismo argumento que `dhcpcd`.
+servicios = ["uevent-relay"]
paquetes = [
# `gcc-libs` (2026-09-14): `libgcc_s.so.1` y `libstdc++.so.6`. Estaba sellado y declarado sólo en
# los CUATRO perfiles de escritorio — y lo necesita cualquier binario ajeno que no sea estático
@@ -414,6 +418,16 @@ descripcion = "rootfs del USB mirada: compositor Wayland propio sobre mesa softw
# con la medición de por qué la cara de Vello no sirve acá). Declararla acá deja el hueco VISIBLE en
# `cierres.json` y en `build-state` —«declara sin receta: wanted/churay»— en vez de en un comentario
# que nadie lee. El día que se commitee, la marca pasa a hash sola.
+# ── LO QUE ARRANCA EL ESCRITORIO (2026-10-15) ────────────────────────────────────────────
+# Hasta hoy este perfil no habilitaba NINGÚN servicio, y `targets.py --services` lo venía
+# denunciando: «`mirada-compositor` está en la imagen y TRAE este servicio, pero el perfil no
+# lo arranca». La semilla del piso trae `hammerd`, `console-getty` y `sshd` y nada más, así
+# que la imagen arrancaba a una consola de texto con el escritorio adentro y nadie
+# llamándolo — la misma figura que `foot`, una vuelta más arriba.
+#
+# El ORDEN importa: `seatd` antes que `mirada`, porque sin el seat el compositor no obtiene
+# el DRM y muere con el dispositivo ocupado.
+servicios = ["seatd", "mirada", "shuma-daemon", "shuma-gateway"]
paquetes = [
"expat-shared", "zlib-shared", "libffi-shared",
"mesa-swrast", "wayland", "libdrm", "seatd", "libinput", "libudev-zero",
diff --git a/scripts/instalador-variantes.sh b/scripts/instalador-variantes.sh
index 9ab885c3..de894d21 100755
--- a/scripts/instalador-variantes.sh
+++ b/scripts/instalador-variantes.sh
@@ -186,6 +186,37 @@ except Exception:
# el árbol pide privilegio. No se hace `chown`: `cp -a` como root conserva dueño y modo, que es
# justo lo que la imagen tiene que llevar. Aparece en cuanto entra un escritorio (sway, cosmic,
# kde); las variantes chicas nunca lo tocaron. Medido el 2026-09-23.
+ # ── LA SEMILLA DEL MEDIO: quién arranca el escritorio ─────────────────────────────────────
+ # Medido el 2026-10-15: la semilla del piso trae `hammerd`, `console-getty` y `sshd` y nada más,
+ # y este armador no la tocaba ⇒ **el medio arrancaba a una consola de texto, con el escritorio
+ # adentro y nadie llamándolo**. Es la figura de `foot` una vuelta más arriba, y el propio
+ # `targets.py --services` la venía denunciando: «`mirada-compositor` está en la imagen y TRAE
+ # este servicio, pero el perfil no lo arranca».
+ #
+ # Las Cards NO se escriben acá: `service-cards` es la ÚNICA traducción `[[service]]` → Card y
+ # `inyectar-cards.py` la única que compone la seed (idempotente por label, con `os.replace`
+ # porque la seed es un hardlink al store). Es el mismo par que usan `servidor-image.sh` y los
+ # armadores de gnome/kde/cosmic; mirada era el único que no lo hacía.
+ _svc=""
+ for p in $(perfiles_de "$_cv" "$_cm"); do
+ _svc="$_svc $(python3 scripts/targets.py --service-paths "$p" 2>/dev/null | tr '\n' ' ')"
+ done
+ _svc=$(echo $_svc)
+ if [ -z "$_svc" ]; then
+ echo " ⚠ ningún servicio habilitado en los perfiles: NADA arrancaría el escritorio" >&2
+ elif [ ! -r "$C/ente/seed.card.json" ]; then
+ echo " ⚠ el árbol no tiene semilla: no hay dónde componer las Cards" >&2
+ else
+ # shellcheck disable=SC2086
+ if ./target/release/takana --store "$STORE" service-cards $_svc > "work/.cards-$_cv-$_cm.json" 2>/dev/null \
+ && python3 scripts/gnome/inyectar-cards.py "$C/ente/seed.card.json" \
+ "work/.cards-$_cv-$_cm.json" "$C/etc/arje/cards.d" >/dev/null; then
+ echo " semilla: $(python3 -c 'import json,sys; print(len(json.load(open(sys.argv[1]))))' "work/.cards-$_cv-$_cm.json") card(s) compuestas · Entes: $(python3 -c 'import json,sys; print(", ".join(e.get("label","?") for e in json.load(open(sys.argv[1])).get("genesis",[])))' "$C/ente/seed.card.json")"
+ else
+ echo " ✗ no pude componer la semilla: esta imagen arrancaría sin escritorio" >&2
+ exit 1
+ fi
+ fi
L="$LOCAL/$_cv-$_cm"
rm -rf "$L" 2>/dev/null || sudo rm -rf "$L"
if ! cp -a "$C" "$L" 2>/dev/null; then
diff --git a/scripts/probar-medio-armado.sh b/scripts/probar-medio-armado.sh
new file mode 100644
index 00000000..1d1ae457
--- /dev/null
+++ b/scripts/probar-medio-armado.sh
@@ -0,0 +1,129 @@
+#!/bin/sh
+# probar-medio-armado.sh — ¿el medio que acabo de armar SIRVE?
+#
+# «Armado» y «sirve» no son lo mismo, y la diferencia se paga en un arranque: cada cosa de esta
+# lista fue un defecto real medido en metal entre el 14 y el 15 de octubre de 2026, con su síntoma
+# al lado. Corre sobre el ÁRBOL COMPUESTO (lo que va al `.img`), así que no hace falta montar nada
+# ni tener root.
+#
+# sh scripts/probar-medio-armado.sh [/store/.perfiles/mirada-usb]
+set -eu
+
+C="${1:-}"
+if [ -z "$C" ]; then
+ for c in /store/.perfiles/mirada-usb /store/.perfiles/mirada-run; do
+ [ -d "$c" ] && { C="$c"; break; }
+ done
+fi
+[ -n "$C" ] && [ -d "$C" ] || { echo "no encuentro el árbol compuesto (pasalo como argumento)"; exit 2; }
+echo "══ sobre $C ════════════════════════════════════════════════════════════════"
+
+mal=0
+# ok <qué> <condición-ya-evaluada> <por qué importa>
+hay() { # hay <ruta> <por qué>
+ if [ -e "$C/$1" ]; then printf ' ✓ %-40s\n' "$1"
+ else printf ' ✗ %-40s FALTA — %s\n' "$1" "$2"; mal=$((mal + 1)); fi
+}
+modo() { # modo <ruta> <modo esperado> <por qué>
+ if [ ! -e "$C/$1" ]; then printf ' ✗ %-40s FALTA — %s\n' "$1" "$3"; mal=$((mal + 1)); return; fi
+ m=$(stat -c%a "$C/$1" 2>/dev/null || echo 0)
+ if [ "$m" = "$2" ]; then printf ' ✓ %-40s %s\n' "$1" "$m"
+ else printf ' ✗ %-40s es %s y debería ser %s — %s\n' "$1" "$m" "$2" "$3"; mal=$((mal + 1)); fi
+}
+nolink() { # nolink <ruta> <por qué>
+ if [ -L "$C/$1" ]; then
+ printf ' ✗ %-40s sigue siendo un symlink a %s — %s\n' "$1" "$(readlink "$C/$1")" "$2"
+ mal=$((mal + 1))
+ elif [ -f "$C/$1" ]; then printf ' ✓ %-40s binario real\n' "$1"
+ else printf ' ✗ %-40s FALTA — %s\n' "$1" "$2"; mal=$((mal + 1)); fi
+}
+
+echo "── el escritorio pinta y tiene con qué trabajar ──"
+# Sin un .ttf el compositor no pinta un solo rótulo, y se CALLA (memoria del 14-oct).
+if [ -n "$(find "$C/usr/share/fonts" -name '*.ttf' 2>/dev/null | head -1)" ]; then
+ printf ' ✓ %-40s %s fuentes\n' "usr/share/fonts/**.ttf" "$(find "$C/usr/share/fonts" -name '*.ttf' 2>/dev/null | wc -l)"
+else printf ' ✗ %-40s FALTA — el compositor no pinta ningún rótulo y no lo dice\n' "una .ttf"; mal=$((mal + 1)); fi
+hay usr/bin/foot "ninguna terminal en la imagen"
+hay usr/bin/mirada-compositor "no hay escritorio"
+hay usr/bin/mirada-greeter "nadie puede iniciar sesión"
+hay usr/bin/pata-llimphi "el riel de la barra"
+hay usr/share/X11/xkb "XKB_CONFIG_ROOT apunta acá; vacío = el teclado no responde"
+hay usr/share/terminfo "dentro de foot, nada sabe pintar"
+hay etc/fonts/fonts.conf "fontconfig; sin esto el riel muere al cargar"
+
+echo "── la máquina tiene red ──"
+hay lib/firmware/intel/iwlwifi "wifi Intel: el caso más común de todos"
+hay lib/firmware/amdgpu "sin su firmware amdgpu no inicializa y la pantalla queda muerta"
+hay lib/firmware/rtl_nic "el ethernet de media gama"
+hay lib/firmware/regulatory.db "sin la base regulatoria el supplicant no elige canal"
+hay usr/sbin/wpa_supplicant "WPA2 no se asocia sin supplicant"
+hay usr/bin/netup "DHCP + ruta + resolv.conf"
+
+echo "── los accesorios en caliente ──"
+hay usr/bin/uevent-relay "sin él, lo que se enchufa después de arrancar no aparece NUNCA"
+hay usr/lib/libudev.so.1 "libudev-zero: sin él libinput descarta todo por falta de ID_INPUT"
+
+echo "── se puede ser root, y entrar ──"
+modo usr/bin/doas 4755 "sin el bit, doas no escala ni con la contraseña correcta"
+# `unix_chkpwd` es con lo que PAM verifica una contraseña cuando el que pregunta NO es root: sin su
+# bit el greeter rechaza a todo el mundo diciendo que la contraseña está mal.
+modo sbin/unix_chkpwd 4755 "el greeter no podría autenticar a NADIE"
+# Y éstos son el arreglo del 15-oct: el symlink a busybox del piso los tapaba.
+nolink bin/su "el symlink del piso tapa al de shadow y queda sin bit"
+nolink usr/bin/passwd "ídem: nadie podría cambiar su contraseña"
+modo bin/su 4755 "su sin bit dice «must be suid to work properly»"
+modo usr/bin/passwd 4755 "passwd sin bit no puede escribir /etc/shadow"
+modo usr/bin/ping 4755 "ping sin bit sale 1 y se lee como «no hay red»"
+
+echo "── el instalador pregunta por una cuenta ──"
+# ⚠ Hay DOS instaladores y cada medio lleva el suyo: el ISO inyecta `/usr/bin/takana-install`
+# (`iso-image.sh`), y el USB de variantes arranca `takana-install-partition.sh` desde el initramfs
+# del instalador, que viaja aparte. Exigir el del ISO en un medio que usa el otro es ruido con forma
+# de defecto, y un verificador que marca en rojo lo que no corresponde se desactiva solo.
+_inst=""
+for _c in usr/bin/takana-install instalador/takana-install-partition.sh; do
+ [ -r "$C/$_c" ] && { _inst="$_c"; break; }
+done
+if [ -z "$_inst" ]; then
+ printf ' · %-40s (este árbol no lleva instalador: lo trae el initramfs del medio, aparte)\n' "instalador"
+else
+ printf ' ✓ %-40s\n' "$_inst"
+ if grep -q "cuenta_aplicar" "$C/$_inst" 2>/dev/null; then
+ printf ' ✓ %-40s llama a cuenta_aplicar\n' "$(basename "$_inst")"
+ else
+ printf ' ✗ %-40s NO llama a cuenta_aplicar — instalaría sin cuenta\n' "$(basename "$_inst")"; mal=$((mal + 1))
+ fi
+ for _l in cuenta.sh firmware-initramfs.sh; do
+ if [ -r "$C/usr/lib/takana/$_l" ] || [ -r "$C/instalador/lib/$_l" ]; then
+ printf ' ✓ %-40s\n' "lib/$_l"
+ else
+ printf ' ✗ %-40s FALTA — el instalador la sourcea y sin ella no hace su parte\n' "lib/$_l"; mal=$((mal + 1))
+ fi
+ done
+fi
+
+echo "── lo que arranca solo ──"
+# Dos formas de arrancar el escritorio, y el medio usa UNA: por Card en la semilla (lo que hace un
+# sistema instalado) o por un `/sbin/init` que lo lanza (lo que hace el medio live, `mirada-usb.sh`).
+# Se mira la que este árbol usa de verdad.
+if [ -r "$C/sbin/init" ] && grep -q "mirada-compositor" "$C/sbin/init" 2>/dev/null; then
+ printf ' ✓ %-40s el lanzador del medio live arranca el compositor\n' "sbin/init"
+ grep -q "uevent-relay" "$C/sbin/init" 2>/dev/null \
+ && printf ' ✓ %-40s y también el reenviador de uevents\n' "sbin/init" \
+ || printf ' ⚠ %-40s el lanzador NO arranca uevent-relay: en el MEDIO el hotplug no andaría\n' "sbin/init"
+elif [ -r "$C/ente/seed.card.json" ]; then
+ for e in mirada seatd uevent-relay; do
+ if grep -q "\"$e\"" "$C/ente/seed.card.json" 2>/dev/null; then printf ' ✓ %-40s en la semilla\n' "Ente $e"
+ else printf ' ✗ %-40s NO está en la semilla\n' "Ente $e"; mal=$((mal + 1)); fi
+ done
+else
+ printf ' ✗ %-40s ni /sbin/init ni semilla: nada arrancaría el escritorio\n' "arranque"; mal=$((mal + 1))
+fi
+
+echo "═════════════════════════════════════════════════════════════════════════════"
+if [ "$mal" = 0 ]; then
+ echo "✓✓ el medio sirve: pinta, tiene red, ve lo que se enchufa, deja ser root y pregunta por una cuenta"
+else
+ echo "✗ $mal comprobación(es) en rojo — ese medio va a fallar en metal, y arriba está en qué"
+ exit 1
+fi