From ef4b18bc888bc67648148e4da7f524f1780d6108 Mon Sep 17 00:00:00 2001 From: sergio Date: Mon, 3 Aug 2026 11:48:44 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=96=A5=20cosmic:=20COMPONE=20EN=20QEMU=20?= =?UTF-8?q?=E2=80=94=20cosmic-comp=20presenta=20frames=20sobre=20el=20kern?= =?UTF-8?q?el=20de=20hammer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pantalla completa en el gris de COSMIC (39,41,42) con el cursor dibujado, 134 colores distintos, validado CON PANTALLA (DISP=gtk + screendump). Entra el andamiaje completo: hydrate-cosmic.sh (44 recetas, 0 faltantes), qemu-desktop-image.sh y cosmic-start-qemu.sh. Tres causas medidas en tres arranques, ninguna donde uno la busca: 1. cosmic-settings-daemon NO es opcional: cosmic-session lo lanza con .expect() (main.rs:255) y panickea si falta. Corrige lo que este runbook afirmaba. De ahi el modo bare del lanzador, que separa como compone de como arranca la sesion. 2. La pantalla negra era libz.so.1: kms_swrast_dri.so lo NEEDea y el corpus solo traia zlib estatica. Sin driver de software no hay GBM y el compositor arranca igual, negro. Causa a tres capas del sintoma. 3. Los clientes no pueden ser estaticos: wayland-client entra con la feature dlopen y un musl estatico no tiene dlopen funcional — moria 'The wayland library could not be loaded' CON el .so presente. Y dos carreras con /run (dbus y XDG_RUNTIME_DIR) de la misma forma que el bug de PolicyKit1 de hoy en GNOME: comprobar al principio y usar al final. Se crea justo antes de usar. Co-Authored-By: Claude Opus 5 (1M context) --- .../evidencia/cosmic-comp-qemu-2026-08-03.png | Bin 0 -> 7973 bytes docs/runbooks/cosmic-desktop.md | 62 +++++- .../incoming-cosmic/cosmic-app-library.toml | 12 +- recipes/incoming-cosmic/cosmic-bg.toml | 7 +- recipes/incoming-cosmic/cosmic-idle.toml | 7 +- recipes/incoming-cosmic/cosmic-launcher.toml | 12 +- .../incoming-cosmic/cosmic-notifications.toml | 12 +- recipes/incoming-cosmic/cosmic-osd.toml | 12 +- recipes/incoming-cosmic/cosmic-panel.toml | 12 +- .../cosmic-workspaces-epoch.toml | 12 +- recipes/incoming-cosmic/dejavu-fonts.toml | 21 ++ .../incoming-cosmic/hicolor-icon-theme.toml | 33 +++ recipes/incoming-cosmic/xkeyboard-config.toml | 23 ++ recipes/incoming-cosmic/zlib-shared.toml | 33 +++ scripts/cosmic/cosmic-start-qemu.sh | 197 ++++++++++++++++++ scripts/cosmic/hydrate-cosmic.sh | 23 ++ scripts/cosmic/qemu-desktop-image.sh | 154 ++++++++++++++ 17 files changed, 615 insertions(+), 17 deletions(-) create mode 100644 docs/evidencia/cosmic-comp-qemu-2026-08-03.png create mode 100644 recipes/incoming-cosmic/dejavu-fonts.toml create mode 100644 recipes/incoming-cosmic/hicolor-icon-theme.toml create mode 100644 recipes/incoming-cosmic/xkeyboard-config.toml create mode 100644 recipes/incoming-cosmic/zlib-shared.toml create mode 100644 scripts/cosmic/cosmic-start-qemu.sh create mode 100644 scripts/cosmic/qemu-desktop-image.sh 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 0000000000000000000000000000000000000000..28f4c82c87117bb32b07817d94c5f49e5384511b GIT binary patch literal 7973 zcmeH|>r;|x7{+Psu%+hAZiR`fvmeUYHq))tN`qZ1+h#S|EH%%h1?D7~8F+$P6UQx= zZCo`}WLm~*Do^8jTF}=dwK5<=!xP|HO9Vj_h|@l=|G|EL`2zED-_LVB?|og*ujtI_ zlg?Y*wxCcb=Tko)KZ`mc4ap7LcK!9w{b;j7*AYLe5ihZnpRo|}AS)DWToOQb zn@B=B2$Z0@_Uv>KG_3Su?2FB^h=_=rizCe=Iq*MG+>nyj(~~`S7e3UQ#|17Blpz}% z8*5DJi^$my7o>`)t_&|uuI^>g*8x}M+4Juf9336Q!^59-z{kGjM+|Zjynp|``e@GS z>YyxXae&8R%dAePvl^7phyY$9DyCQ98;ZyKczX+lLhpV16m6IKE%yaJ{H1P{N_BTl#p?{1g~zNtDfp( ziqopIQYYqtf)0a%7u^rnM1}Vzv}1EtRcwkPOEuKU3agxDy3-R#izVk(LL-tKJr z9GlW|b@XOw`8rV6@F+L$i5p|WVp{aiYw!F z@^xjNp<{I<^+2WnH#nQLw6wInz5Q8>6DYdnH$&fYUf`Rn?Lh-cw3*UKn<_?mWc343 z*WFu;sej`Oi~3Mz-*qmog($8rkIzn#wnEmXsXm{0KD8!bP0hwId*h~2Ag?)9R`W|a zgYY19x2Dsq7O?{%ldzcVo^8z;wa=Etq#e%uG;n@wGg!0BPwf&sNIDn|(X0iIXfS~+ z-Sp(-qz1FpKPQ1+Kfs~S1=3rS`tF-dgDm*GUCpFyS4RJaPuf0D9Cc~j z*NGXVdnLu=jyehM`g9tHl9)m{&1|!}gofc0Xp1^CS3(N%!0#>@jM=)T`4X_XN1k4? zvLM5)41_}#%W|d^{#5#3{jBTvDI^TFl9dO;p zY|A*2^;6_%E17P$8o+KKGnM2{(_QIfgH3>o$6nq5x7szp{Co2(S_c;_bs_+|(3&P6 z7#LvE^aEwyF&;GI1i>|gE13`!CgiOQdN(!Y7}S|h)}r-&vK}l(U&^N+$(VgRBd)>Y z@d<6$CWOZd(>hI$;LY+~u&xXO$gT#szn-3Ft8XQ9*leqDiW!;X5AQFl&@N_BEV^Ye z-SC*!-`@|kgZU)y&jJBr5ZV^Vn&_ygC@^~GfLQu?*X^2`nnA_di2O);w9?uyA>C*{ zYZZY=Hx?8KD~UVMXfkiHh2W|%o5W0yIHSzdZX9dRNXS}?jZG7x?3k&yndQLWm^#Sz zaukOt4k3!FOy!=or4^Y=T=WH2MTHx)FOt4%O(OqeeFu*qV2B~(#;goX$W5gH;iL}-Z6@PE<}+ED2Hu=z439fBKAoj85GE;#YG{{Xi-V~GF& literal 0 HcmV?d00001 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