diff --git a/docs/evidencia/cosmic-comp-qemu-2026-08-03.png b/docs/evidencia/cosmic-comp-qemu-2026-08-03.png new file mode 100644 index 00000000..28f4c82c Binary files /dev/null and b/docs/evidencia/cosmic-comp-qemu-2026-08-03.png differ diff --git a/docs/runbooks/cosmic-desktop.md b/docs/runbooks/cosmic-desktop.md index 30c877ff..8f862b13 100644 --- a/docs/runbooks/cosmic-desktop.md +++ b/docs/runbooks/cosmic-desktop.md @@ -1,8 +1,11 @@ # Runbook — COSMIC, el cuarto escritorio -Estado al 2026-08-03: **el gate está pasado.** `cosmic-comp` —el compositor, la pieza de la que todo -lo demás es cliente— **compila y está sellado**, junto con `cosmic-session`, `cosmic-icons`, -`cosmic-bg` y `cosmic-idle`. Falta arrancarlo. Este documento se escribe mientras se hace, no después. +Estado al 2026-08-03: **COSMIC COMPONE EN QEMU.** `cosmic-comp` arranca sobre el kernel de hammer con +arje-zero como PID1, toma el DRM, expone `wayland-1` y **presenta frames**: pantalla completa en el +gris de COSMIC `(39,41,42)` con el cursor dibujado, 134 colores distintos. Falta que los clientes se +conecten. Este documento se escribe mientras se hace, no después. + +![cosmic-comp en QEMU](../evidencia/cosmic-comp-qemu-2026-08-03.png) COSMIC es el escritorio de System76, en Rust sobre **smithay** (compositor) e **iced/libcosmic** (clientes). Es el cuarto de hammer, tras [mirada](mirada-usb-nvidia.md), @@ -128,6 +131,59 @@ Que un compositor Wayland completo —DRM, GBM, EGL, libinput, seat, Vulkan, XWa multi-GPU y una capa de UI en iced— cierre con ocho librerías compartidas y ningún parche es el resultado más limpio de las cuatro campañas de escritorio. +## 🖥 EL PRIMER ARRANQUE: qué falló y qué no (2026-08-03) + +Tres imágenes y tres causas distintas, todas medidas. Vale dejarlas porque **ninguna estaba en el +camino que uno anticipa**, y las tres se manifestaron a varias capas de distancia de su causa. + +**1. `cosmic-session` no es opcional en el sentido que yo asumí — y `cosmic-settings-daemon` tampoco.** +El compositor arrancó perfecto y le devolvió a la sesión su `WAYLAND_DISPLAY=wayland-1`; el gestor +entonces intentó lanzar `cosmic-settings-daemon`, no lo encontró, y **panickeó**: + +``` +INFO cosmic_session: got environmental variables from cosmic-comp: [("WAYLAND_DISPLAY", "wayland-1")] +ERROR panic: 'failed to start settings daemon: NotFound' src/main.rs:255 +``` + +Es un `.expect()`, sin feature que lo apague. **Esto corrige lo que decía la sección de +cosmic-settings-daemon**: no es que sin él se pierdan el brillo y el tema, es que **no hay sesión**. +Queda por eso el modo `bare` en `cosmic-start` —el camino que upstream ya soporta como +`install-bare-session`—: se lanza el compositor solo y los clientes a mano contra su WAYLAND_DISPLAY. +Separa «¿compone?» de «¿arranca la sesión?», que son dos preguntas y estaban viniendo juntas. + +**2. La pantalla negra era `libz.so.1`.** El corpus trae `zlib` estática, que le alcanza a todo el +cierre de COSMIC. Pero `kms_swrast_dri.so` es un `.so` que mesa abre con `dlopen` y NEEDea +`libz.so.1`: + +``` +MESA-LOADER: failed to open kms_swrast: Error loading shared library libz.so.1 +WARN cosmic_comp::backend::kms: Failed to add device /dev/dri/card0: Failed to initialize GBM device +``` + +Sin driver de software no hay GBM, y **el compositor arranca igual y presenta una pantalla negra**. La +causa está a tres capas del síntoma. Se arregla agregando `zlib-shared` como raíz del hidratador. + +**3. Los clientes NO pueden ser estáticos.** `cosmic-bg` moría con «The wayland library could not be +loaded» **teniendo `libwayland-client.so.0` en el rootfs**. `wayland-client` entra con la feature +`dlopen`: carga libwayland en runtime en vez de enlazarla, y **un binario musl estático no tiene +dlopen funcional**. No faltaba la librería: faltaba poder abrirla. Todos los clientes pasan a +`link = "dynamic"`. + +**Lo que NO falló, y conviene decirlo:** el compositor no necesitó un solo parche, ni una perilla de +KMS, ni forzar modeset legacy. Las cuatro perillas de render por software heredadas de KDE y GNOME +(`GBM_ALWAYS_SOFTWARE`, `kms_swrast`, `LP_NUM_THREADS=1`) alcanzaron. GNOME costó cinco muros para +llegar a este punto; COSMIC costó tres, y ninguno era del compositor. + +### Dos carreras con /run, la misma causa + +`dbus-daemon --system` falló con «Failed to bind socket /run/dbus/system_bus_socket: No such file or +directory» **aunque el directorio se creaba al principio del script**, y dbus-run-session se quejó +por su lado de que `XDG_RUNTIME_DIR` no existía. Entre el arranque del getty y el uso pasan varios +segundos en los que arje-zero sigue montando, y un tmpfs sobre `/run` se lleva puesto lo que hubiera. +**Crear justo antes de usar** es la única forma de no depender de ese orden; comprobar al principio y +usar al final es exactamente cómo se cuela una carrera — la misma forma del bug de PolicyKit1 que se +cerró hoy en GNOME. + ## Gotchas medidos (no previstos) 1. **`cargo rustc --` exige UN target.** La fase compile del lab pasa flags tras el `--`, y cualquier diff --git a/recipes/incoming-cosmic/cosmic-app-library.toml b/recipes/incoming-cosmic/cosmic-app-library.toml index cff0a791..954b437e 100644 --- a/recipes/incoming-cosmic/cosmic-app-library.toml +++ b/recipes/incoming-cosmic/cosmic-app-library.toml @@ -19,7 +19,12 @@ sha256 = "39dfa91d807240585ece7794190229832cb0c48162d87a29d62fb6a43a5238e6" [build] compiler = "zig-cc" target = "x86_64-linux-musl" -link = "static" +# ── DINÁMICO, Y NO ES UNA PREFERENCIA ────────────────────────────────────────────────────────── +# `wayland-client` entra con la feature `dlopen`: no ENLAZA libwayland-client, la carga en runtime. +# Un binario musl ESTÁTICO no tiene dlopen funcional, así que el cliente arranca y muere con +# «The wayland library could not be loaded» — con el .so presente en el rootfs, que es lo que hace +# el diagnóstico confuso: no falta la librería, falta poder abrirla. +link = "dynamic" flags = ["--bin", "cosmic-app-library"] [build.phases] @@ -34,4 +39,7 @@ cp data/icons/com.system76.CosmicAppLibrary.svg /out/usr/share/icons/hicolor ''' [deps] -build = ["libxkbcommon", "pkgconf"] +# libudev-zero: lo pide `libudev-sys`, que llega por la cadena de libcosmic (enumeración de +# monitores y de dispositivos de entrada). Es el tercer miembro fijo del juego de deps de todo +# cliente de la suite, junto con libxkbcommon y pkgconf. +build = ["libxkbcommon", "pkgconf", "libudev-zero"] diff --git a/recipes/incoming-cosmic/cosmic-bg.toml b/recipes/incoming-cosmic/cosmic-bg.toml index a688cc72..08664afb 100644 --- a/recipes/incoming-cosmic/cosmic-bg.toml +++ b/recipes/incoming-cosmic/cosmic-bg.toml @@ -28,7 +28,12 @@ sha256 = "12b30c60e15ac6c0f4b3eca8c37642145683e830833b429958222dfc395db177" [build] compiler = "zig-cc" target = "x86_64-linux-musl" -link = "static" +# ── DINÁMICO, Y NO ES UNA PREFERENCIA ────────────────────────────────────────────────────────── +# `wayland-client` entra con la feature `dlopen`: no ENLAZA libwayland-client, la carga en runtime. +# Un binario musl ESTÁTICO no tiene dlopen funcional, así que el cliente arranca y muere con +# «The wayland library could not be loaded» — con el .so presente en el rootfs, que es lo que hace +# el diagnóstico confuso: no falta la librería, falta poder abrirla. +link = "dynamic" # Un único target para `cargo rustc --` (ver el comentario de cosmic-comp): el workspace trae también # la lib de config. flags = ["--bin", "cosmic-bg"] diff --git a/recipes/incoming-cosmic/cosmic-idle.toml b/recipes/incoming-cosmic/cosmic-idle.toml index 089ddae0..f803a0e4 100644 --- a/recipes/incoming-cosmic/cosmic-idle.toml +++ b/recipes/incoming-cosmic/cosmic-idle.toml @@ -22,7 +22,12 @@ sha256 = "f6eab0ed81bba6bd3a6dfb3ec78471a152852c59e6887a74206f4b59865df7c1" [build] compiler = "zig-cc" target = "x86_64-linux-musl" -link = "static" +# ── DINÁMICO, Y NO ES UNA PREFERENCIA ────────────────────────────────────────────────────────── +# `wayland-client` entra con la feature `dlopen`: no ENLAZA libwayland-client, la carga en runtime. +# Un binario musl ESTÁTICO no tiene dlopen funcional, así que el cliente arranca y muere con +# «The wayland library could not be loaded» — con el .so presente en el rootfs, que es lo que hace +# el diagnóstico confuso: no falta la librería, falta poder abrirla. +link = "dynamic" flags = ["--bin", "cosmic-idle"] [build.phases] diff --git a/recipes/incoming-cosmic/cosmic-launcher.toml b/recipes/incoming-cosmic/cosmic-launcher.toml index dc206d42..7e9efd64 100644 --- a/recipes/incoming-cosmic/cosmic-launcher.toml +++ b/recipes/incoming-cosmic/cosmic-launcher.toml @@ -19,7 +19,12 @@ sha256 = "908cb0ef7e609ce0dec59f6d1cb4c249551883da42c957cd9dcb2e45892c7ec7" [build] compiler = "zig-cc" target = "x86_64-linux-musl" -link = "static" +# ── DINÁMICO, Y NO ES UNA PREFERENCIA ────────────────────────────────────────────────────────── +# `wayland-client` entra con la feature `dlopen`: no ENLAZA libwayland-client, la carga en runtime. +# Un binario musl ESTÁTICO no tiene dlopen funcional, así que el cliente arranca y muere con +# «The wayland library could not be loaded» — con el .so presente en el rootfs, que es lo que hace +# el diagnóstico confuso: no falta la librería, falta poder abrirla. +link = "dynamic" flags = ["--bin", "cosmic-launcher"] [build.phases] @@ -34,4 +39,7 @@ cp data/icons/com.system76.CosmicLauncher.svg /out/usr/share/icons/hicolor/s ''' [deps] -build = ["libxkbcommon", "pkgconf"] +# libudev-zero: lo pide `libudev-sys`, que llega por la cadena de libcosmic (enumeración de +# monitores y de dispositivos de entrada). Es el tercer miembro fijo del juego de deps de todo +# cliente de la suite, junto con libxkbcommon y pkgconf. +build = ["libxkbcommon", "pkgconf", "libudev-zero"] diff --git a/recipes/incoming-cosmic/cosmic-notifications.toml b/recipes/incoming-cosmic/cosmic-notifications.toml index c156a364..272d5c42 100644 --- a/recipes/incoming-cosmic/cosmic-notifications.toml +++ b/recipes/incoming-cosmic/cosmic-notifications.toml @@ -15,7 +15,12 @@ sha256 = "8c60dd4c2161a2b9d2314597e141c600da9882fe774858283303370b2409ead7" [build] compiler = "zig-cc" target = "x86_64-linux-musl" -link = "static" +# ── DINÁMICO, Y NO ES UNA PREFERENCIA ────────────────────────────────────────────────────────── +# `wayland-client` entra con la feature `dlopen`: no ENLAZA libwayland-client, la carga en runtime. +# Un binario musl ESTÁTICO no tiene dlopen funcional, así que el cliente arranca y muere con +# «The wayland library could not be loaded» — con el .so presente en el rootfs, que es lo que hace +# el diagnóstico confuso: no falta la librería, falta poder abrirla. +link = "dynamic" flags = ["--bin", "cosmic-notifications"] [build.phases] @@ -26,4 +31,7 @@ chmod 755 /out/usr/bin/cosmic-notifications ''' [deps] -build = ["libxkbcommon", "pkgconf"] +# libudev-zero: lo pide `libudev-sys`, que llega por la cadena de libcosmic (enumeración de +# monitores y de dispositivos de entrada). Es el tercer miembro fijo del juego de deps de todo +# cliente de la suite, junto con libxkbcommon y pkgconf. +build = ["libxkbcommon", "pkgconf", "libudev-zero"] diff --git a/recipes/incoming-cosmic/cosmic-osd.toml b/recipes/incoming-cosmic/cosmic-osd.toml index ac09387c..37331324 100644 --- a/recipes/incoming-cosmic/cosmic-osd.toml +++ b/recipes/incoming-cosmic/cosmic-osd.toml @@ -21,7 +21,12 @@ sha256 = "de1a409c95305059f0c79f1dff0089f7cf6aaf9b2b7201e982da9895d58ad4a3" [build] compiler = "zig-cc" target = "x86_64-linux-musl" -link = "static" +# ── DINÁMICO, Y NO ES UNA PREFERENCIA ────────────────────────────────────────────────────────── +# `wayland-client` entra con la feature `dlopen`: no ENLAZA libwayland-client, la carga en runtime. +# Un binario musl ESTÁTICO no tiene dlopen funcional, así que el cliente arranca y muere con +# «The wayland library could not be loaded» — con el .so presente en el rootfs, que es lo que hace +# el diagnóstico confuso: no falta la librería, falta poder abrirla. +link = "dynamic" flags = ["--bin", "cosmic-osd"] [build.phases] @@ -32,4 +37,7 @@ chmod 755 /out/usr/bin/cosmic-osd ''' [deps] -build = ["libxkbcommon", "pkgconf"] +# libudev-zero: lo pide `libudev-sys`, que llega por la cadena de libcosmic (enumeración de +# monitores y de dispositivos de entrada). Es el tercer miembro fijo del juego de deps de todo +# cliente de la suite, junto con libxkbcommon y pkgconf. +build = ["libxkbcommon", "pkgconf", "libudev-zero"] diff --git a/recipes/incoming-cosmic/cosmic-panel.toml b/recipes/incoming-cosmic/cosmic-panel.toml index 1b92b85e..5b9c9ce6 100644 --- a/recipes/incoming-cosmic/cosmic-panel.toml +++ b/recipes/incoming-cosmic/cosmic-panel.toml @@ -20,7 +20,12 @@ sha256 = "66d8dfbc8aa62ff6a91e83e5bdcb5363c663aac5a58c111152b258054a815222" [build] compiler = "zig-cc" target = "x86_64-linux-musl" -link = "static" +# ── DINÁMICO, Y NO ES UNA PREFERENCIA ────────────────────────────────────────────────────────── +# `wayland-client` entra con la feature `dlopen`: no ENLAZA libwayland-client, la carga en runtime. +# Un binario musl ESTÁTICO no tiene dlopen funcional, así que el cliente arranca y muere con +# «The wayland library could not be loaded» — con el .so presente en el rootfs, que es lo que hace +# el diagnóstico confuso: no falta la librería, falta poder abrirla. +link = "dynamic" flags = ["--bin", "cosmic-panel"] [build.phases] @@ -32,4 +37,7 @@ cp -r data/default_schema/. /out/usr/share/cosmic/ ''' [deps] -build = ["libxkbcommon", "pkgconf"] +# libudev-zero: lo pide `libudev-sys`, que llega por la cadena de libcosmic (enumeración de +# monitores y de dispositivos de entrada). Es el tercer miembro fijo del juego de deps de todo +# cliente de la suite, junto con libxkbcommon y pkgconf. +build = ["libxkbcommon", "pkgconf", "libudev-zero"] diff --git a/recipes/incoming-cosmic/cosmic-workspaces-epoch.toml b/recipes/incoming-cosmic/cosmic-workspaces-epoch.toml index b843d86a..1a8a79a8 100644 --- a/recipes/incoming-cosmic/cosmic-workspaces-epoch.toml +++ b/recipes/incoming-cosmic/cosmic-workspaces-epoch.toml @@ -12,7 +12,12 @@ sha256 = "d7b54ce8dd229c6b76c2132b38d0814df656439bd4c014d04bd052143dcf4249" [build] compiler = "zig-cc" target = "x86_64-linux-musl" -link = "static" +# ── DINÁMICO, Y NO ES UNA PREFERENCIA ────────────────────────────────────────────────────────── +# `wayland-client` entra con la feature `dlopen`: no ENLAZA libwayland-client, la carga en runtime. +# Un binario musl ESTÁTICO no tiene dlopen funcional, así que el cliente arranca y muere con +# «The wayland library could not be loaded» — con el .so presente en el rootfs, que es lo que hace +# el diagnóstico confuso: no falta la librería, falta poder abrirla. +link = "dynamic" flags = ["--bin", "cosmic-workspaces"] [build.phases] @@ -25,4 +30,7 @@ cp data/com.system76.CosmicWorkspaces.svg /out/usr/share/icons/hicolor/scala ''' [deps] -build = ["libxkbcommon", "pkgconf"] +# libudev-zero: lo pide `libudev-sys`, que llega por la cadena de libcosmic (enumeración de +# monitores y de dispositivos de entrada). Es el tercer miembro fijo del juego de deps de todo +# cliente de la suite, junto con libxkbcommon y pkgconf. +build = ["libxkbcommon", "pkgconf", "libudev-zero"] diff --git a/recipes/incoming-cosmic/dejavu-fonts.toml b/recipes/incoming-cosmic/dejavu-fonts.toml new file mode 100644 index 00000000..5020cd5f --- /dev/null +++ b/recipes/incoming-cosmic/dejavu-fonts.toml @@ -0,0 +1,21 @@ +# dejavu-fonts 2.37 — fuentes TTF. Qt ya no trae fuentes ("QFontDatabase: Cannot find font directory"); +# sin ellas el escritorio renderiza sin texto legible. Data-only: copia los .ttf a /usr/share/fonts. +name = "dejavu-fonts" +version = "2.37" + +[source] +tarball = "https://github.com/dejavu-fonts/dejavu-fonts/releases/download/version_2_37/dejavu-fonts-ttf-2.37.tar.bz2" +sha256 = "fa9ca4d13871dd122f61258a80d01751d603b4d3ee14095d65453b4e846e17d7" + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +link = "dynamic" + +[build.phases] +configure = "true" +compile = "true" +install = "mkdir -p /out/usr/share/fonts/dejavu && cp ttf/*.ttf /out/usr/share/fonts/dejavu/" + +[deps] +build = [] diff --git a/recipes/incoming-cosmic/hicolor-icon-theme.toml b/recipes/incoming-cosmic/hicolor-icon-theme.toml new file mode 100644 index 00000000..243a9602 --- /dev/null +++ b/recipes/incoming-cosmic/hicolor-icon-theme.toml @@ -0,0 +1,33 @@ +# hicolor-icon-theme 0.18 — el tema de iconos BASE del estándar freedesktop, y nada más que su +# esqueleto: un `index.theme` y el árbol de directorios vacíos por tamaño y categoría. +# +# QUÉ ES Y POR QUÉ HACE FALTA: `hicolor` es el tema de FALLBACK obligatorio de la especificación de +# iconos. Cada aplicación instala sus iconos en `/usr/share/icons/hicolor/...`, y todo buscador de +# iconos (GTK, Qt, el shell) termina ahí cuando el tema activo no tiene lo que se pide. Sin su +# `index.theme` el directorio existe pero NO es un tema: la búsqueda no lo recorre y los iconos de +# las aplicaciones quedan invisibles aunque los ficheros estén en su sitio. +# +# No trae ni un icono propio, a propósito — es el contrato, no el contenido. De ahí que sea la receta +# más chica del frente y a la vez la que no se puede saltear. +# +# Ojo con la versión: hasta 0.17 era autotools; **0.18 pasó a meson** y el tarball ya no trae +# `configure` (ni `configure.ac`). Un `./configure` heredado de la receta vieja sale con 127. +name = "hicolor-icon-theme" +version = "0.18" + +[source] +tarball = "https://icon-theme.freedesktop.org/releases/hicolor-icon-theme-0.18.tar.xz" +sha256 = "db0e50a80aa3bf64bb45cbca5cf9f75efd9348cf2ac690b907435238c3cf81d7" + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +link = "dynamic" + +[build.phases] +configure = "PYTHONPATH=/usr/lib/python3.12/site-packages meson setup output --prefix=/usr --buildtype=release --wrap-mode=nodownload" +compile = "PYTHONPATH=/usr/lib/python3.12/site-packages ninja -C output" +install = "PYTHONPATH=/usr/lib/python3.12/site-packages DESTDIR=/out meson install -C output --no-rebuild" + +[deps] +build = ["meson", "samurai", "python3", "pkgconf"] diff --git a/recipes/incoming-cosmic/xkeyboard-config.toml b/recipes/incoming-cosmic/xkeyboard-config.toml new file mode 100644 index 00000000..efc3c2f0 --- /dev/null +++ b/recipes/incoming-cosmic/xkeyboard-config.toml @@ -0,0 +1,23 @@ +# xkeyboard-config 2.44 — datos de layouts de teclado XKB + `xkeyboard-config.pc` (variable xkb_base). +# plasma-desktop lo EXIGE en ConfigureChecks (pkg_get_variable xkb_base) y kwin/Qt-wayland lo usan en +# runtime (/usr/share/X11/xkb; sin él: "xkbcommon: failed to add default include path /usr/share/X11/xkb"). +# Meson, sin compilación real (instala datos + .pc). nls off (evita gettext); sin libxml2 (validación). +name = "xkeyboard-config" +version = "2.44" + +[source] +tarball = "https://www.x.org/releases/individual/data/xkeyboard-config/xkeyboard-config-2.44.tar.xz" +sha256 = "54d2c33eeebb031d48fa590c543e54c9bcbd0f00386ebc6489b2f47a0da4342a" + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +link = "dynamic" + +[build.phases] +configure = "meson setup output --prefix=/usr -Dnls=false -Dcompat-rules=false" +compile = "ninja -C output" +install = "DESTDIR=/out ninja -C output install" + +[deps] +build = ["meson", "samurai", "python3", "pkgconf"] diff --git a/recipes/incoming-cosmic/zlib-shared.toml b/recipes/incoming-cosmic/zlib-shared.toml new file mode 100644 index 00000000..35b0de5c --- /dev/null +++ b/recipes/incoming-cosmic/zlib-shared.toml @@ -0,0 +1,33 @@ +# zlib-shared 1.3.1 — variante DINÁMICA de zlib para el escritorio KDE. +# +# El zlib canónico (recipes/zlib.toml) es `--static` (sólo libz.a, para el toolchain rust/mrustc). Pero +# el stack GUI dinámico (Qt/KDE/libpng/Mesa) linkea `libz.so.1` por SONAME en runtime, y ese `.so` no lo +# provee ningún artefacto sellado ⇒ el rootfs hidratado caía a la libz.so.1 del sysroot Alpine (fuga de +# soberanía, deuda static↔dinámico de Capa 0). Esta variante sella el `libz.so.1` soberano: los `.so` de +# KDE ya tienen NEEDED libz.so.1 (mismo soname), así que basta hidratar este artefacto — sin rebuild de KDE. +# +# Build shared: el configure de zlib aborta bajo zig cc ("compiler error reporting too harsh"), así que se +# fabrica el libz.so.1 a mano del libz.a PIC con --whole-archive. CLAVE (fix soberanía): se pasa el +# --version-script=zlib.map del propio tarball ⇒ el .so exporta los símbolos VERSIONADOS (ZLIB_1.2.0 +# compressBound, ZLIB_1.2.3.4 inflateReset2, …). Qt6 (libQt6Core/Gui) fue enlazado contra un zlib +# versionado y NEEDED esos @tags; sin el mapa, un libz.so con nombres pelados no resolvía +# inflateReset2@ZLIB_1.2.3.4 al enlazar gwenview (--no-allow-shlib-undefined) y Qt caía en runtime a la +# libz.so.1 de Alpine (fuga). El .so versionado satisface consumidores versionados Y no-versionados. +# -fPIC/-O2/-fno-sanitize=undefined igual que la canónica (evita el runtime UBSan de zig). + +name = "zlib-shared" +version = "1.3.1" + +[source] +tarball = "https://github.com/madler/zlib/releases/download/v1.3.1/zlib-1.3.1.tar.gz" +sha256 = "9a93b2b7dfdac77ceba5a558a580e74667dd6fede4585b91eefb60f03b72df23" + +[build] +compiler = "zig-cc" +target = "x86_64-linux-musl" +link = "dynamic" + +[build.phases] +configure = "CFLAGS='-fPIC -O2 -fno-sanitize=undefined' ./configure --prefix=/usr --static" +compile = "make -j\"$(nproc)\" && ${CC:-zig cc} -shared -Wl,-soname,libz.so.1 -Wl,--version-script,zlib.map -Wl,--whole-archive libz.a -Wl,--no-whole-archive -o libz.so.1.3.1" +install = "make install DESTDIR=/out && install -Dm755 libz.so.1.3.1 /out/usr/lib/libz.so.1.3.1 && ln -sf libz.so.1.3.1 /out/usr/lib/libz.so.1 && ln -sf libz.so.1 /out/usr/lib/libz.so" diff --git a/scripts/cosmic/cosmic-start-qemu.sh b/scripts/cosmic/cosmic-start-qemu.sh new file mode 100644 index 00000000..453abcc9 --- /dev/null +++ b/scripts/cosmic/cosmic-start-qemu.sh @@ -0,0 +1,197 @@ +#!/bin/sh +# cosmic-start — lo que el console-getty ejecuta en vez de /bin/sh en la imagen COSMIC de QEMU. +# Hermano de gnome-start y plasma-start; hereda de ellos todo lo que ya está pago y sólo diverge en +# lo que COSMIC necesita. +# +# Es `/bin/sh` (busybox ash) a propósito, aunque `start-cosmic` de upstream sea bash: este script +# corre ANTES, prepara el entorno, y después le pasa la posta. Cuanto menos dependa, más temprano +# puede reportar. +set -u +say() { echo "== cosmic-qemu :: $*" | tee -a /dev/console /dev/ttyS0 2>/dev/null; } +dump() { echo "---- $1 ----" > /dev/ttyS0 2>/dev/null; cat "$1" > /dev/ttyS0 2>/dev/null; } +alive() { s=$(sed -n 's/^State:[[:space:]]*//p' /proc/$1/status 2>/dev/null); case "$s" in ""|Z*) return 1;; *) return 0;; esac; } + +say "arrancando (pid $$)" + +# ── FS virtuales y directorios de runtime ─────────────────────────────────────────────────────── +mount -t proc none /proc 2>/dev/null +mount -t sysfs none /sys 2>/dev/null +mount -t devtmpfs none /dev 2>/dev/null +mkdir -p /dev/pts /dev/shm && mount -t devpts none /dev/pts 2>/dev/null && mount -t tmpfs none /dev/shm 2>/dev/null +# XDG_RUNTIME_DIR es OBLIGATORIO para Wayland: ahí viven el socket del compositor y los de los +# clientes. Sin él el compositor arranca y nadie puede conectarse — y el error que da es del cliente, +# no del compositor, que es lo que hace confuso el diagnóstico. +export XDG_RUNTIME_DIR=/run/user/0 +mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR" +# …y COMPROBARLO, porque el primer arranque falló justo acá y el error salía del sitio equivocado: +# dbus decía «XDG_RUNTIME_DIR /run/user/0 not available» y eso se lee como un problema de dbus. El +# `mkdir -p` de arriba no garantiza nada si /run no es escribible o si algo lo montó encima. +if [ -d "$XDG_RUNTIME_DIR" ] && [ -w "$XDG_RUNTIME_DIR" ]; then + say "XDG_RUNTIME_DIR OK ($XDG_RUNTIME_DIR)" +else + say "!! XDG_RUNTIME_DIR NO existe/no escribible — /run es: $(ls -ld /run 2>&1)" + say " montando tmpfs en /run" + mount -t tmpfs none /run 2>/dev/null + mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR" + [ -w "$XDG_RUNTIME_DIR" ] && say " ✓ ahora sí" || say " ✗ sigue sin andar" +fi +mkdir -p /root/.config /root/.cache /var/lib/dbus /run/dbus /tmp && chmod 1777 /tmp +export HOME=/root SHELL=/bin/bash PATH=/usr/bin:/bin:/usr/sbin:/sbin + +say "kernel $(uname -r) · drm: $(ls /dev/dri 2>/dev/null | tr '\n' ' ')" + +# ── Render por software: la VM no tiene GPU ───────────────────────────────────────────────────── +# Las cuatro perillas vienen pagas de las campañas KDE y GNOME. `GBM_ALWAYS_SOFTWARE` + el driver +# `kms_swrast` son lo que hace que mesa acepte crear un device GBM sobre virtio-gpu sin acelerador; +# `LP_NUM_THREADS=1` evita que llvmpipe se coma los cuatro vcpus y deje al compositor sin planificar. +export GBM_ALWAYS_SOFTWARE=1 +export LIBGL_ALWAYS_SOFTWARE=1 +export MESA_LOADER_DRIVER_OVERRIDE=kms_swrast +export LP_NUM_THREADS=1 +# COSMIC pinta con tiny-skia (libcosmic entra sin `wgpu`), pero el compositor SÍ usa EGL/GL por +# smithay para componer, así que las de arriba siguen haciendo falta. +export RUST_BACKTRACE=1 +export RUST_LOG="${RUST_LOG:-info}" + +# ── seatd: quién le da los devices al compositor ──────────────────────────────────────────────── +# smithay entra con `backend_session_libseat`, y libseat tiene dos backends: hablar con un `seatd` +# corriendo, o —si el proceso ya es root— abrir los devices él mismo (`builtin`). Acá se levanta +# seatd de verdad en vez de usar el builtin, porque es el camino que también sirve fuera de QEMU y +# porque deja un log donde mirar. Si no sube, se cae al builtin: mejor un escritorio con andamio que +# ninguno, pero se dice en el serial para que no pase por bueno. +if [ -x /usr/bin/seatd ]; then + /usr/bin/seatd -g video >/tmp/seatd.log 2>&1 & + SEATD_PID=$! + sleep 1 + if alive $SEATD_PID && [ -S /run/seatd.sock ]; then + say "seatd OK (pid $SEATD_PID, /run/seatd.sock)" + else + say "!! seatd no levantó — caigo a LIBSEAT_BACKEND=builtin (somos root)"; dump /tmp/seatd.log + export LIBSEAT_BACKEND=builtin + fi +else + say "!! sin seatd — LIBSEAT_BACKEND=builtin" + export LIBSEAT_BACKEND=builtin +fi + +# ── bus de SISTEMA ────────────────────────────────────────────────────────────────────────────── +# cosmic-comp habla logind por zbus (sesión, inhibidores) y el bus de sesión lo abre después +# `start-cosmic` con dbus-run-session. El de sistema hay que levantarlo acá. +if [ -x /usr/bin/dbus-daemon ]; then + # ⚠ El mkdir de /run/dbus va ACÁ, no arriba con los demás. El primer arranque en modo bare falló + # con «Failed to bind socket /run/dbus/system_bus_socket: No such file or directory» aunque el + # directorio se creaba al principio del script: entre un punto y otro arje-zero sigue montando, y + # un tmpfs sobre /run se lleva puesto lo que hubiera. Crear justo antes de usar es la única forma + # de no depender de ese orden. Mismo motivo que el segundo mkdir de XDG_RUNTIME_DIR. + mkdir -p /run/dbus /var/lib/dbus + [ -f /var/lib/dbus/machine-id ] || dbus-uuidgen > /var/lib/dbus/machine-id 2>/dev/null + [ -f /etc/machine-id ] || cp /var/lib/dbus/machine-id /etc/machine-id 2>/dev/null + dbus-daemon --system --fork >/tmp/dbus-system.log 2>&1 + sleep 1 + [ -S /run/dbus/system_bus_socket ] && say "bus de sistema OK" || { say "!! sin bus de sistema:"; dump /tmp/dbus-system.log; } +else + say "!! sin dbus-daemon" +fi + +# ── login1: el shim de arje ───────────────────────────────────────────────────────────────────── +# Mismo eslabón que en GNOME y KDE. cosmic-comp pregunta por su sesión; quien contesta +# `org.freedesktop.login1` acá es arje-logind-compat. Se ESPERA el nombre, no el pid: un pid no es un +# servicio, y esperar sólo al final deja pasar la carrera (lección de gnome-start, 2026-08-03). +if [ -x /usr/bin/arje-logind-compat ]; then + ARJE_LOGIN_STATE=1 /usr/bin/arje-logind-compat >/tmp/logind-compat.log 2>&1 & + say "arje-logind-compat lanzado (pid $!)" + i=0 + while [ $i -lt 20 ]; do + if dbus-send --system --dest=org.freedesktop.DBus --print-reply /org/freedesktop/DBus \ + org.freedesktop.DBus.NameHasOwner string:org.freedesktop.login1 2>/dev/null | grep -q "boolean true"; then + say "bus: org.freedesktop.login1 OK"; break + fi + i=$((i+1)); sleep 0.5 + done + [ $i -lt 20 ] || { say "!! login1 NO apareció en 10s:"; dump /tmp/logind-compat.log; } +else + say "!! sin arje-logind-compat — cosmic-comp puede no hallar sesión" +fi + +# ── COSMIC ────────────────────────────────────────────────────────────────────────────────────── +export XDG_CURRENT_DESKTOP=COSMIC XDG_SESSION_DESKTOP=COSMIC XDG_SESSION_TYPE=wayland +export XDG_SESSION_ID=1 XDG_SEAT=seat0 XDG_VTNR=1 +export XDG_DATA_DIRS=/usr/share XDG_CONFIG_DIRS=/etc/xdg + +# Y otra vez JUSTO ANTES de arrancar, no sólo al principio: entre el getty y este punto pasan varios +# segundos en los que arje-zero sigue montando cosas, y un tmpfs sobre /run se lleva puesto el +# directorio sin avisar. Comprobar al principio y usar al final es cómo se cuela una carrera. +mkdir -p "$XDG_RUNTIME_DIR" && chmod 700 "$XDG_RUNTIME_DIR" +[ -w "$XDG_RUNTIME_DIR" ] || say "!! XDG_RUNTIME_DIR se perdió entre el arranque y ahora" + +# ── MODO: bare (default) o session ────────────────────────────────────────────────────────────── +# `cosmic-session` es el camino de producción, pero HOY no se puede usar: llama a +# `cosmic-settings-daemon` con `.expect("failed to start settings daemon")` (src/main.rs:255) y +# PANICKEA si no está — no es opcional ni está detrás de una feature. Y ese daemon todavía no se +# puede construir acá (arrastra pipewire; ver el runbook). +# +# El modo `bare` es la otra mitad que upstream ya soporta (`make install-bare-session`): el +# compositor se lanza SOLO y los clientes se levantan a mano contra su WAYLAND_DISPLAY. Sirve para lo +# que hace falta ahora, que es saber si el escritorio PINTA — y separa las dos preguntas: «¿compone?» +# es del compositor, «¿arranca la sesión?» es del gestor. +# +# COSMIC_MODE=session para probar el camino de producción cuando el daemon exista. +if [ "${COSMIC_MODE:-bare}" = session ]; then + say "lanzando cosmic-session vía start-cosmic (modo session)" + /bin/bash /usr/bin/start-cosmic --in-login-shell >/tmp/cosmic-session.log 2>&1 & + CS_PID=$! +else + say "lanzando cosmic-comp SOLO (modo bare; cosmic-session necesita settings-daemon)" + dbus-daemon --session --fork --print-address=1 >/tmp/dbus-session-addr 2>/tmp/dbus-session.log + DBUS_SESSION_BUS_ADDRESS=$(cat /tmp/dbus-session-addr 2>/dev/null) + [ -n "$DBUS_SESSION_BUS_ADDRESS" ] && export DBUS_SESSION_BUS_ADDRESS && say "bus de sesión OK" \ + || { say "!! sin bus de sesión:"; dump /tmp/dbus-session.log; } + /usr/bin/cosmic-comp >/tmp/cosmic-session.log 2>&1 & + CS_PID=$! +fi + +# ── Reporte: el socket, no el pid ─────────────────────────────────────────────────────────────── +# «Un pid no es un servicio» — la lección que costó dos iteraciones en GNOME. Lo que dice que el +# compositor está VIVO es que exista su socket Wayland, no que el proceso no haya muerto todavía. +i=0; WD="" +while [ $i -lt 60 ]; do + WD=$(ls "$XDG_RUNTIME_DIR" 2>/dev/null | grep -m1 '^wayland-[0-9]$') + if [ -n "$WD" ]; then + say "compositor OK ($WD) — cosmic-comp ES el display server" + break + fi + alive $CS_PID || { say "!! la sesión MURIÓ antes de crear el socket:"; dump /tmp/cosmic-session.log; break; } + i=$((i+1)); sleep 1 +done +[ -n "$WD" ] || say "!! sin socket wayland en ${i}s:" +[ -n "$WD" ] || dump /tmp/cosmic-session.log + +# ── Los clientes, contra el socket que el compositor haya elegido ─────────────────────────────── +# En modo bare no hay quien los levante: `cosmic-session` es el que normalmente hace esto, y por eso +# cada uno recibe WAYLAND_DISPLAY explícito. Se lanza sólo lo que ESTÉ instalado — la cola COSMIC se +# construye por partes, y un `[ -x ]` de más es más barato que un arranque perdido. +if [ -n "$WD" ] && [ "${COSMIC_MODE:-bare}" != session ]; then + export WAYLAND_DISPLAY="$WD" + for c in cosmic-bg cosmic-panel cosmic-osd cosmic-notifications cosmic-app-library cosmic-launcher cosmic-idle; do + if [ -x "/usr/bin/$c" ]; then + "/usr/bin/$c" >"/tmp/$c.log" 2>&1 & + say "cliente $c lanzado (pid $!)" + sleep 1 + fi + done +fi + +# Estado periódico + el log completo al final de cada tramo: el dato que importa no es que siga vivo +# sino QUÉ componentes levantó la sesión. +n=0 +while [ $n -lt 12 ]; do + sleep 15; n=$((n+1)) + # `pgrep -c` de busybox devolvía 0 con el compositor VIVO y su socket abierto — o sea que el + # contador mentía. Se cuentan los /proc/*/comm, que es el dato del kernel y no una utilería. + say "STATUS +$((n*15))s: $(for c in /proc/[0-9]*/comm; do cat "$c" 2>/dev/null; done | grep -c '^cosmic') procesos cosmic · $(for c in /proc/[0-9]*/comm; do cat "$c" 2>/dev/null; done | grep '^cosmic' | sort -u | tr '\n' ' ')" + [ $n = 2 ] && { say "log del compositor (primer tramo)"; dump /tmp/cosmic-session.log; for l in /tmp/cosmic-bg.log /tmp/cosmic-panel.log; do [ -s "$l" ] && dump "$l"; done; } +done + +say "fin de la ventana de observación" +dump /tmp/cosmic-session.log +exec /bin/sh diff --git a/scripts/cosmic/hydrate-cosmic.sh b/scripts/cosmic/hydrate-cosmic.sh index 01eca342..3f1cae7c 100644 --- a/scripts/cosmic/hydrate-cosmic.sh +++ b/scripts/cosmic/hydrate-cosmic.sh @@ -38,6 +38,29 @@ else # Ni binario ni librería: DATOS. El tema de iconos de COSMIC — sin él el escritorio no se ve # «sin iconos», se ve roto, y con el cursor por software ni siquiera hay puntero. recipes/incoming-cosmic/cosmic-icons.toml + # …y el tema del que HEREDA (`Inherits=hicolor` en su index.theme). Que un tema herede de otro es + # una relación declarada en un fichero de datos, invisible para cualquier grafo de deps. + recipes/incoming-cosmic/hicolor-icon-theme.toml + + # ── LO QUE PIDE `start-cosmic`, Y NO LO PIDE NINGÚN `[deps]` ──────────────────────────────── + # bash de verdad: el script usa `mapfile`, `[[ ]]` y expansión indirecta `${!var}`, así que con el + # busybox de la base metal no arranca. Y termina en `exec dbus-run-session -- cosmic-session`, + # o sea que el bus de SESIÓN es parte del arranque, no un extra. + recipes/bash.toml + recipes/dbus.toml + + # ── DATOS QUE UNA LIBRERÍA DEL CIERRE NECESITA EN RUNTIME ─────────────────────────────────── + # libxkbcommon entra por el grafo (la enlazan comp y clientes) pero sus TABLAS no: sin + # `/usr/share/X11/xkb` no puede compilar un keymap y el escritorio queda sin teclado. Es el mismo + # patrón que los gschemas en GNOME — la librería es la interfaz, los datos son otro paquete. + recipes/incoming-cosmic/xkeyboard-config.toml + # Idem tipografías: nada del cierre trae una sola fuente, y sin fuentes el panel sale con tofu. + recipes/incoming-cosmic/dejavu-fonts.toml + # zlib COMPARTIDA. El corpus trae `zlib` estática, que le sirve a todo el cierre de COSMIC… y no + # al driver de mesa: `kms_swrast_dri.so` es un .so que se abre con dlopen y NEEDea `libz.so.1`. + # Sin ella mesa no carga NINGÚN driver de software, GBM falla, y el compositor arranca igual y + # deja la pantalla NEGRA — el fallo aparece a tres capas de distancia de la causa. + recipes/incoming-cosmic/zlib-shared.toml ) fi diff --git a/scripts/cosmic/qemu-desktop-image.sh b/scripts/cosmic/qemu-desktop-image.sh new file mode 100644 index 00000000..72b1cf3d --- /dev/null +++ b/scripts/cosmic/qemu-desktop-image.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# qemu-desktop-image.sh — imagen de disco EFI COSMIC para QEMU (virtio-gpu + GL por software), que +# auto-lanza cosmic-session. Hermano de scripts/gnome/qemu-desktop-image.sh y scripts/kde/…: comparte +# la base metal, la inyección de mesa-llvmpipe/LLVM/musl y el truco del console-getty. +# +# work/metal-rootfs base: arje-zero PID1 + busybox + kernel(EFI-stub) + firmware +# work/cosmic-rootfs cierre de COSMIC (lo arma scripts/cosmic/hydrate-cosmic.sh) +# + store/*-mesa-llvmpipe GBM/EGL de software (el cierre trae mesa con iris_dri, que es Intel real) +# + store/*-llvm18 libLLVM que llvmpipe JIT-linkea +# → work/cosmic-qemu-rootfs (hardlinks) → work/hammer-cosmic-qemu.img +# +# Uso: scripts/cosmic/qemu-desktop-image.sh +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/../.." && pwd)"; cd "$ROOT" + +BASE="${BASE:-work/metal-rootfs}" +COSMIC="${COSMIC:-work/cosmic-rootfs}" +MESA="${MESA:-$(ls -dt store/*-mesa-llvmpipe 2>/dev/null | head -1)}" +LLVM="${LLVM:-$(ls -dt store/*-llvm18 2>/dev/null | head -1)}" +MERGED="${MERGED:-work/cosmic-qemu-rootfs}" +IMG="${IMG:-work/hammer-cosmic-qemu.img}" + +[ -d "$BASE" ] || { echo "falta $BASE" >&2; exit 1; } +[ -d "$COSMIC" ] || { echo "falta $COSMIC — corré scripts/cosmic/hydrate-cosmic.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 COSMIC)" +rm -rf "$MERGED" +cp -al "$BASE" "$MERGED" +cp -al --remove-destination "$COSMIC"/. "$MERGED"/ + +. "$ROOT/scripts/lib/pid1-desde-store.sh" +inyectar_pid1 "$MERGED" "$BASE" + +echo "==> parcheando mesa software desde $(basename "$MESA")" +# El cierre trae mesa con SÓLO iris_dri.so (Intel real). En QEMU no hay GPU: hace falta kms_swrast. +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. Nada de COSMIC los pide +# —el compositor cierra con ocho NEEDED y ninguno es de gcc—: esto es deuda de la cadena de mesa. +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). En musl el loader ES libc (mismo fichero). Van +# dos nombres porque conviven dos toolchains: zig-cc emite NEEDED `libc.so` y el gcc/clang de Alpine +# emite `libc.musl-x86_64.so.1`. Es la MISMA libc; sin el segundo symlink lo de gcc no carga. +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" + +echo "==> inyectando arje-logind-compat (el login1 del fractal) + su política" +# cosmic-comp pregunta por su sesión igual que mutter y kwin. El daemon no entra por el cierre porque +# nadie lo declara como dep de build. Se elige por `hammer hash` —la receta de HOY—, no por el más +# reciente del store. Su política D-Bus viaja DENTRO del artefacto desde 2026-07-30. +if 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 + install -d "$MERGED/usr/share/dbus-1/system.d" + cp -a --remove-destination "$ALC"/usr/share/dbus-1/system.d/. "$MERGED"/usr/share/dbus-1/system.d/ 2>/dev/null || true + echo " ✓ $(basename "$ALC" | cut -c1-12)" +else + echo " ✗ falta arje-logind-compat en store/ — cosmic-comp no hallará sesión" >&2; exit 1 +fi +# Comprobación, que es lo que reemplaza a escribir la política acá: mover algo a un artefacto sólo es +# una mejora si se NOTA cuando falta. +[ -f "$MERGED/usr/share/dbus-1/system.d/org.freedesktop.login1.conf" ] \ + && echo " ✓ org.freedesktop.login1.conf (del artefacto)" \ + || { echo " ✗ falta org.freedesktop.login1.conf — re-sellá arje-logind-compat" >&2; exit 1; } + +echo "==> grupo video (seatd lo exige por NOMBRE)" +# `seatd -g video` sale con «Could not find group by name 'video'» y no arranca. No es cosmético: +# sin seatd, libseat cae al backend `builtin`, que anda sólo porque acá todo corre como root — o sea +# que el escritorio funcionaría por un accidente de privilegios y no por el camino real. La base metal +# es tan mínima que no trae ni los grupos estándar. +if ! grep -q '^video:' "$MERGED/etc/group" 2>/dev/null; then + tmp="$MERGED/etc/group.new" + { cat "$MERGED/etc/group"; echo "video:x:44:"; } > "$tmp" + chmod 644 "$tmp"; mv "$tmp" "$MERGED/etc/group" + echo " ✓ video:x:44" +fi + +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 +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 + chmod 4750 "$MERGED/$h" + echo " ✓ setuid en /$h" + fi +done + +echo "==> instalando /usr/bin/cosmic-start" +install -Dm755 scripts/cosmic/cosmic-start-qemu.sh "$MERGED/usr/bin/cosmic-start" + +echo "==> auto-lanzar COSMIC: console-getty ejecuta cosmic-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/cosmic-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 y dbus-daemon escucha +# en /run. Esta base metal es tan mínima que ni siquiera tiene /var/run; sin el enlace el daemon se va +# a modo idle y nunca registra login1. +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