gioser

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 1572Shell 203Rust 109Markdown 993728 Definiciones

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>

b0a4d44799449fd0f98189f500378ad2b4b58980 · por sergio · (hace 6 d) · padre f7aedd6d7 · ver el árbol en este commit

Delta semántico

Ninguna definición cambió: el cambio es de texto, documentación o datos.

3 archivos · +174 −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