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 188Rust 55Shell 30Markdown 281211 Definitions

⎇ 8730aad34e868068796f9c85ca4e4c89cfb43fa6 scripts/cosmic/qemu-desktop-image.sh Shell · 202 lines · 10.9 KB History raw

takana etapa 3b: las 49 invocaciones pasan a ./target/release/takana 8730aad34 · Sergio ·

1#!/usr/bin/env bash
2# qemu-desktop-image.sh — imagen de disco EFI COSMIC para QEMU (virtio-gpu + GL por software), que
3# auto-lanza cosmic-session. Hermano de scripts/gnome/qemu-desktop-image.sh y scripts/kde/…: comparte
4# la base metal, la inyección de mesa-llvmpipe/LLVM/musl y el truco del console-getty.
5#
6#   work/metal-rootfs      base: arje-zero PID1 + busybox + kernel(EFI-stub) + firmware
7#   work/cosmic-rootfs     cierre de COSMIC (lo arma scripts/cosmic/hydrate-cosmic.sh)
8#   + store/*-mesa-llvmpipe   GBM/EGL de software (el cierre trae mesa con iris_dri, que es Intel real)
9#   + store/*-llvm18          libLLVM que llvmpipe JIT-linkea
10#   → work/cosmic-qemu-rootfs (hardlinks) → work/hammer-cosmic-qemu.img
11#
12# Uso:  scripts/cosmic/qemu-desktop-image.sh
13set -euo pipefail
14ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT"
15
16BASE="${BASE:-work/metal-rootfs}"
17COSMIC="${COSMIC:-work/cosmic-rootfs}"
18MESA="${MESA:-$(ls -dt store/*-mesa-llvmpipe 2>/dev/null | head -1)}"
19LLVM="${LLVM:-$(ls -dt store/*-llvm18 2>/dev/null | head -1)}"
20MERGED="${MERGED:-work/cosmic-qemu-rootfs}"
21IMG="${IMG:-work/hammer-cosmic-qemu.img}"
22
23[ -d "$BASE" ]   || { echo "falta $BASE" >&2; exit 1; }
24[ -d "$COSMIC" ] || { echo "falta $COSMIC — corré scripts/cosmic/hydrate-cosmic.sh" >&2; exit 1; }
25[ -n "$MESA" ] && [ -d "$MESA" ] || { echo "falta mesa-llvmpipe en store/" >&2; exit 1; }
26
27echo "==> fundiendo rootfs por hardlinks (base metal + cierre COSMIC)"
28rm -rf "$MERGED"
29cp -al "$BASE" "$MERGED"
30cp -al --remove-destination "$COSMIC"/. "$MERGED"/
31
32. "$ROOT/scripts/lib/pid1-desde-store.sh"
33inyectar_pid1 "$MERGED" "$BASE"
34
35echo "==> parcheando mesa software desde $(basename "$MESA")"
36# El cierre trae mesa con SÓLO iris_dri.so (Intel real). En QEMU no hay GPU: hace falta kms_swrast.
37mkdir -p "$MERGED"/usr/lib/dri
38cp -a --remove-destination "$MESA"/usr/lib/dri/. "$MERGED"/usr/lib/dri/ 2>/dev/null || true
39for so in libglapi libgbm libEGL libGLESv2 libGL libgallium; do
40  cp -a --remove-destination "$MESA"/usr/lib/$so* "$MERGED"/usr/lib/ 2>/dev/null || true
41done
42ls "$MERGED"/usr/lib/dri/*swrast* >/dev/null 2>&1 && echo "   ✓ kms_swrast instalado" || { echo "   ✗ swrast no entró" >&2; exit 1; }
43
44echo "==> inyectando libLLVM.18 (llvmpipe lo JIT-linkea)"
45[ -n "$LLVM" ] && [ -d "$LLVM" ] || { echo "   ✗ falta llvm18 en store/ (set LLVM=)" >&2; exit 1; }
46cp -a --remove-destination "$LLVM"/usr/lib/libLLVM.so.18.1 "$MERGED"/usr/lib/
47ln -sf libLLVM.so.18.1 "$MERGED"/usr/lib/libLLVM.so.18
48ln -sf libLLVM.so.18.1 "$MERGED"/usr/lib/libLLVM.so
49
50echo "==> inyectando el runtime C++ de Alpine (deuda CONOCIDA, no accidente)"
51# libLLVM y mesa-llvmpipe se construyen con gcc ⇒ NEEDean libstdc++/libgcc_s. Nada de COSMIC los pide
52# —el compositor cierra con ocho NEEDED y ninguno es de gcc—: esto es deuda de la cadena de mesa.
53for lib in libgcc_s.so.1 libstdc++.so.6; do
54  src=".dev-fs/alpine/usr/lib/$lib"
55  if [ -r "$src" ]; then cp -a --remove-destination "$src"* "$MERGED"/usr/lib/ 2>/dev/null || true
56  else echo "   ⚠ sin $lib ($src)"; fi
57done
58
59echo "==> inyectando musl — y los DOS sonames"
60# La base metal es 100% estática (arje-zero+busybox). En musl el loader ES libc (mismo fichero). Van
61# dos nombres porque conviven dos toolchains: zig-cc emite NEEDED `libc.so` y el gcc/clang de Alpine
62# emite `libc.musl-x86_64.so.1`. Es la MISMA libc; sin el segundo symlink lo de gcc no carga.
63MUSL="${MUSL:-/usr/lib/musl/lib/libc.so}"
64[ -r "$MUSL" ] || { echo "no encuentro musl libc ($MUSL) — set MUSL=" >&2; exit 1; }
65mkdir -p "$MERGED/usr/lib/musl/lib" "$MERGED/lib"
66cp -a --remove-destination "$MUSL" "$MERGED/usr/lib/musl/lib/libc.so"
67ln -sf /usr/lib/musl/lib/libc.so "$MERGED/lib/ld-musl-x86_64.so.1"
68ln -sf /usr/lib/musl/lib/libc.so "$MERGED/usr/lib/libc.so"
69ln -sf /usr/lib/musl/lib/libc.so "$MERGED/usr/lib/libc.musl-x86_64.so.1"
70echo "   ✓ libc.so + libc.musl-x86_64.so.1"
71
72echo "==> inyectando arje-logind-compat (el login1 del fractal) + su política"
73# cosmic-comp pregunta por su sesión igual que mutter y kwin. El daemon no entra por el cierre porque
74# nadie lo declara como dep de build. Se elige por `hammer hash` —la receta de HOY—, no por el más
75# reciente del store. Su política D-Bus viaja DENTRO del artefacto desde 2026-07-30.
76if ALC="${ALC:-store/$(./target/release/takana --store store hash recipes/arje-logind-compat.toml 2>/dev/null | tail -1 | sed 's/^b3://')-arje-logind-compat}"; [ -d "$ALC" ]; then
77  install -Dm755 "$ALC"/usr/bin/arje-logind-compat "$MERGED"/usr/bin/arje-logind-compat
78  install -d "$MERGED/usr/share/dbus-1/system.d"
79  cp -a --remove-destination "$ALC"/usr/share/dbus-1/system.d/. "$MERGED"/usr/share/dbus-1/system.d/ 2>/dev/null || true
80  echo "   ✓ $(basename "$ALC" | cut -c1-12)"
81else
82  echo "   ✗ falta arje-logind-compat en store/ — cosmic-comp no hallará sesión" >&2; exit 1
83fi
84# Comprobación, que es lo que reemplaza a escribir la política acá: mover algo a un artefacto sólo es
85# una mejora si se NOTA cuando falta.
86[ -f "$MERGED/usr/share/dbus-1/system.d/org.freedesktop.login1.conf" ] \
87  && echo "   ✓ org.freedesktop.login1.conf (del artefacto)" \
88  || { echo "   ✗ falta org.freedesktop.login1.conf — re-sellá arje-logind-compat" >&2; exit 1; }
89
90echo "==> grupo video (seatd lo exige por NOMBRE)"
91# `seatd -g video` sale con «Could not find group by name 'video'» y no arranca. No es cosmético:
92# sin seatd, libseat cae al backend `builtin`, que anda sólo porque acá todo corre como root — o sea
93# que el escritorio funcionaría por un accidente de privilegios y no por el camino real. La base metal
94# es tan mínima que no trae ni los grupos estándar.
95if ! grep -q '^video:' "$MERGED/etc/group" 2>/dev/null; then
96  tmp="$MERGED/etc/group.new"
97  { cat "$MERGED/etc/group"; echo "video:x:44:"; } > "$tmp"
98  chmod 644 "$tmp"; mv "$tmp" "$MERGED/etc/group"
99  echo "   ✓ video:x:44"
100fi
101
102echo "==> usuario messagebus (el system bus hace setuid a él)"
103for f in passwd:messagebus:x:81:81:messagebus:/dev/null:/sbin/nologin group:messagebus:x:81:; do
104  file="${f%%:*}"; line="${f#*:}"
105  if ! grep -q '^messagebus:' "$MERGED/etc/$file" 2>/dev/null; then
106    tmp="$MERGED/etc/$file.new"
107    { cat "$MERGED/etc/$file"; echo "$line"; } > "$tmp"
108    chmod 644 "$tmp"; mv "$tmp" "$MERGED/etc/$file"
109  fi
110done
111for h in usr/libexec/dbus-daemon-launch-helper usr/lib/dbus-1.0/dbus-daemon-launch-helper; do
112  if [ -f "$MERGED/$h" ]; then
113    chown 0:81 "$MERGED/$h" 2>/dev/null || true
114    chmod 4750 "$MERGED/$h"
115    echo "   ✓ setuid en /$h"
116  fi
117done
118
119# COSMIC_MODE se hornea como fichero: el getty no hereda ambiente. `bare` (default) lanza el
120# compositor solo y los clientes a mano; `session` usa cosmic-session, que EXIGE
121# cosmic-settings-daemon o panickea. Ver el runbook.
122printf 'COSMIC_MODE=%s\n' "${COSMIC_MODE:-bare}" > "$MERGED/etc/cosmic-mode"
123echo "==> modo de arranque: ${COSMIC_MODE:-bare}"
124
125echo "==> instalando /usr/bin/cosmic-start"
126install -Dm755 scripts/cosmic/cosmic-start.sh "$MERGED/usr/bin/cosmic-start"
127
128echo "==> auto-lanzar COSMIC: console-getty ejecuta cosmic-start (no /bin/sh)"
129python3 - "$MERGED/ente/seed.card.json" <<'PY'
130import json, sys, os
131p = sys.argv[1]
132c = json.load(open(p))
133for g in c.get("genesis", []):
134    if g.get("label") == "console-getty":
135        argv = g["payload"]["Native"]["argv"]
136        argv[:] = [("/usr/bin/cosmic-start" if a == "/bin/sh" else a) for a in argv]
137        print("  console-getty argv →", argv)
138tmp = p + ".new"
139with open(tmp, "w") as f:
140    json.dump(c, f, indent=2)
141os.chmod(tmp, 0o644)
142os.replace(tmp, p)
143PY
144
145# /var/run → /run: arje-logind-compat busca el bus de sistema en la ruta LEGACY y dbus-daemon escucha
146# en /run. Esta base metal es tan mínima que ni siquiera tiene /var/run; sin el enlace el daemon se va
147# a modo idle y nunca registra login1.
148ln -sfn /run "$MERGED/var/run"
149
150mkdir -p "$MERGED/root/.config" "$MERGED/root/.cache" "$MERGED/run/user/0" "$MERGED/tmp"
151chmod 1777 "$MERGED/tmp"
152
153# ── CATÁLOGO APPSTREAM, o por qué cosmic-store salía VACÍO ─────────────────────────────────────
154# El backend `pkgar` de la tienda NO lee `/usr/share/metainfo/*.metainfo.xml` —los ficheros que cada
155# receta instala, uno por aplicación—. Lee el **CATÁLOGO**: los ficheros de
156# `{/usr/share,/var/lib,/var/cache}/{swcatalog,app-info}/{xml,yaml}/`, que en una distro normal
157# genera `appstreamcli compose` agregando todos los metainfo en un solo documento
158# `<components version=… origin=…>`. Sin ese fichero la tienda abre perfecta y no lista NADA
159# —«No results for …»— porque su catálogo está vacío, no porque le falte nada del sistema.
160#
161# hammer no tiene appstreamcli (sería AppStream + glib, o sea la torre de C que esta campaña evita),
162# pero el formato es XML plano y la agregación es exactamente eso: envolver los `<component>` de cada
163# metainfo en una raíz. Doce líneas de python valen la librería entera.
164echo "==> catálogo AppStream para cosmic-store (agrega los metainfo del rootfs)"
165#
166# ⚠ Y hay que SACAR el bloque `<provides>`. Los metainfo que genera `xdgen` —los de cosmic-edit,
167# files, settings, store y term— lo escriben con los envoltorios `<mimetypes>`/`<binaries>`, y el
168# parser de la tienda (el crate `appstream`) espera los hijos PLANOS del esquema de catálogo
169# (`<mediatype>`, `<binary>`). Con el envoltorio muere con
170#     failed to parse … in "/usr/share/swcatalog/xml/hammer.xml": The tag provide doesn't have a value
171# y —esto es lo caro— **descarta el componente entero, callado para el usuario**: la tienda abría
172# perfecta y listaba sólo 2 de 7, justamente los dos que NO tienen `<provides>` (App Library y
173# Launcher). Se descubrió corriendo `RUST_LOG=info cosmic-store` desde cosmic-term DENTRO de la VM;
174# desde el lanzador la salida del proceso no llega al log de la sesión.
175python3 - "$MERGED" <<'PY'
176import glob, os, re, sys
177merged = sys.argv[1]
178comps = []
179for p in sorted(glob.glob(os.path.join(merged, "usr/share/metainfo/*.xml"))):
180    x = open(p, encoding="utf-8").read()
181    # Quedarse con el elemento raíz <component …>…</component>, sin cabecera XML ni comentarios.
182    m = re.search(r"<component[\s>].*</component>", x, re.S)
183    if m:
184        comps.append(re.sub(r"\s*<provides>.*?</provides>", "", m.group(0), flags=re.S))
185out = os.path.join(merged, "usr/share/swcatalog/xml")
186os.makedirs(out, exist_ok=True)
187with open(os.path.join(out, "hammer.xml"), "w", encoding="utf-8") as f:
188    f.write('<?xml version="1.0" encoding="UTF-8"?>\n')
189    f.write('<components version="0.14" origin="hammer" architecture="x86_64">\n')
190    f.write("\n".join(comps))
191    f.write("\n</components>\n")
192print("   %d componentes → /usr/share/swcatalog/xml/hammer.xml" % len(comps))
193PY
194
195echo "==> rootfs fundido: $(du -sh "$MERGED" | cut -f1)"
196need_mb=$(du -sm "$MERGED" | cut -f1)
197ROOT_SIZE="${ROOT_SIZE:-$(( need_mb + 1536 ))}"
198echo "==> ROOT_SIZE=${ROOT_SIZE} MiB"
199
200export ROOTFS="$MERGED" IMG ROOT_SIZE
201export ESP_MB="${ESP_MB:-128}" STORE_SIZE="${STORE_SIZE:-512}" STATE_SIZE="${STATE_SIZE:-768}"
202exec ./scripts/install-image-efi.sh