SDD 30 4c: GNOME con los demonios supervisados por arje — y el control arregló colord
Los demonios de sistema pasan de lanzarse con `&` desde un script de 500 líneas a ser Cards del `genesis`. El bloqueo que este frente daba por corpus se levantó solo: otro agente selló evolution-data-server y gnome-shell mientras esto se escribía, y escritorio-gnome quedó 309/309. SE CORRIÓ UN CONTROL PRIMERO, y es lo único que hace interpretable el resultado: colord control: `MURIÓ al arrancar` cards: `ya vive (pid 175)` ColorManager control: `NO apareció en 40s` cards: `OK` login1/Accounts/UPower OK en los dos compositor wayland-0 y shell vivo en los dos El fallo del control DESAPARECIÓ, y no lo buscaba: colord moría arrancado por el script y vive arrancado por arje, con el bus ya listo porque la espera está dentro de su argv. Sin el control, «ColorManager OK» sería un dato suelto en vez de una diferencia. Los PIDs lo confirman: polkit=119, colord=175, upowerd=178, accounts=180 — de antes de que el lanzador de sesión existiera. QUÉ NO SE COMPROBÓ: no hay screendump; QEMU salió por timeout y el control tampoco lo tuvo. La comparación es serial contra serial y lo que se afirma es sobre los DEMONIOS, no sobre el pintado. Las piezas donde corresponde: `takana service-cards` (UNA sola implementación de receta→Card; el formato es contrato con card_core::Card), `targets.py --service-paths` (une qué-es con si-arranca), y un inyector en FICHERO APARTE porque anidar dos heredocs de python falló en vivo — el terminador del interno cerró el externo y media cosa corrió como shell. La espera del bus va DENTRO del argv de las 5 recetas de sistema: sin ella un daemon arranca antes de que dbus escuche y queda en modo idle sin registrar su nombre — un fallo que no se ve, porque el proceso vive y el bus no lo tiene. Los 5 hashes intactos. Y el guardia de gnome-start es por «¿está corriendo?», no por una perilla: así es correcto venga de donde venga el proceso y la misma copia sirve donde no se inyectaron cards.
This commit is contained in:
@@ -49,6 +49,24 @@ enum Cmd {
|
||||
/// Ruta a la receta TOML.
|
||||
recipe: String,
|
||||
},
|
||||
/// [SDD 30] Emite las **Cards de arje** de los servicios que declaran estas recetas
|
||||
/// (`[[service]]`), como array JSON, para que el ensamblado de una imagen las meta en el
|
||||
/// `genesis` de la seed.
|
||||
///
|
||||
/// Existe para que la traducción `[[service]]` → Card tenga **una sola implementación**. Los
|
||||
/// scripts de imagen ya parchean la seed con python; si además compusieran el JSON de la Card,
|
||||
/// habría dos generadores del mismo formato divergiendo en silencio — y el formato es contrato
|
||||
/// con `card_core::Card`, que no es nuestro.
|
||||
///
|
||||
/// Por defecto sólo las de `scope = "system"`: una Card de sesión en el `genesis` arrancaría
|
||||
/// antes de que exista `XDG_RUNTIME_DIR` o un usuario logueado.
|
||||
ServiceCards {
|
||||
/// Rutas de recetas TOML. `scripts/targets.py --service-paths <perfil>` las lista.
|
||||
recipes: Vec<String>,
|
||||
/// Qué ámbito emitir: `system` (default), `session` o `all`.
|
||||
#[arg(long, default_value = "system")]
|
||||
scope: String,
|
||||
},
|
||||
/// [Fase 0] Calcula el `ArtifactHash` de una receta SIN construir — puro sobre las recetas
|
||||
/// (source_id + compiler/target/link + patches + flags + fases + hashes de deps). No baja
|
||||
/// fuentes ni compila. Responde "¿cuál es el hash VIGENTE de esta receta?", que el store no
|
||||
@@ -947,6 +965,25 @@ fn main() -> anyhow::Result<()> {
|
||||
let hash = takana_build::build(&recipe, &cfg, &store)?;
|
||||
println!("{hash}");
|
||||
}
|
||||
Cmd::ServiceCards { recipes, scope } => {
|
||||
use takana_core::service::Scope;
|
||||
let filtro = match scope.as_str() {
|
||||
"system" => Some(Scope::System),
|
||||
"session" => Some(Scope::Session),
|
||||
"all" => None,
|
||||
otro => anyhow::bail!("--scope inválido: {otro} (system | session | all)"),
|
||||
};
|
||||
let mut cards: Vec<serde_json::Value> = Vec::new();
|
||||
for ruta in &recipes {
|
||||
let r = takana_core::Recipe::load_from_path(ruta)?;
|
||||
for s in &r.services {
|
||||
if filtro.is_none_or(|f| f == s.scope) {
|
||||
cards.push(s.card());
|
||||
}
|
||||
}
|
||||
}
|
||||
println!("{}", serde_json::to_string_pretty(&cards)?);
|
||||
}
|
||||
Cmd::Hash { recipe, check } => {
|
||||
let store = takana_core::Store::open(&cli.store)?;
|
||||
let recipe = takana_core::Recipe::load_from_path(&recipe)?;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# SDD 30 — Los servicios que trae un paquete
|
||||
|
||||
> **Estado:** §3, §4a y §4b IMPLEMENTADOS y **VALIDADOS EN ARRANQUE REAL** (2026-09-12/14);
|
||||
> §4c cerrado para la imagen de producto y bloqueado para las de escritorio; §5 abierto. Nace de una pregunta directa:
|
||||
> §4c CERRADO también para GNOME (2026-09-14, con control); §5 abierto. Nace de una pregunta directa:
|
||||
> *«los paquetes que tienen sus servicios para systemd, openrc u otros, ¿ya saben empaquetarse en
|
||||
> takana con arje?»*. La respuesta medida era **no**, y este documento dice exactamente qué faltaba,
|
||||
> qué se cerró y qué queda.
|
||||
@@ -229,26 +229,74 @@ Dos cosas que NO son transcripción y hay que saber:
|
||||
a PID 1 por `RunCard`), pero mutter y kwin no tienen esa integración. `--services` avisa por cada
|
||||
una: el hueco queda contado, no omitido.
|
||||
|
||||
#### Por qué el escritorio NO se puede arrancar supervisado todavía (medido)
|
||||
#### 4c.1 GNOME con los demonios supervisados por arje — medido contra un control
|
||||
|
||||
El paso final —que el ensamblado de la imagen ponga las cards de sistema en el `genesis` en vez de
|
||||
que el script las lance con `&`— **no está bloqueado por diseño sino por corpus**, y conviene que
|
||||
esté escrito para no volver a averiguarlo:
|
||||
El bloqueo que este documento daba por corpus (gnome-shell en deuda) **se levantó solo**: otro
|
||||
agente selló `evolution-data-server` y `gnome-shell` mientras esto se escribía. `escritorio-gnome`
|
||||
quedó 309/309 y el cierre hidrata completo (319 nodos, 8,2 G).
|
||||
|
||||
| imagen | estado | qué falta |
|
||||
**Se corrió un CONTROL primero**, y fue lo que hizo interpretable el resultado: la misma imagen sin
|
||||
inyectar cards, para saber si GNOME arranca hoy en este hub. Arranca — y trae un fallo propio que
|
||||
sirvió de sonda.
|
||||
|
||||
| | control (el script con `&`) | con Cards en el `genesis` |
|
||||
|---|---|---|
|
||||
| `escritorio-gnome` | 307/309 de la clausura sellada | **`gnome-shell` en deuda**, y su único bloqueante es `evolution-data-server` (que tiene `blocked_by: []` — o sea que sus deps ya están: falta construirlo, no destrabarlo) |
|
||||
| `escritorio-sway` | 260/261 | **`sway` mismo sin sellar** |
|
||||
| `colord` | `!! colord MURIÓ al arrancar` | `ya vive (pid 175)` — lo encarnó PID 1 |
|
||||
| `org.freedesktop.ColorManager` | `NO apareció en 40s` | **`OK`** |
|
||||
| `login1` / `Accounts` / `UPower` | OK | OK |
|
||||
| compositor | `wayland-0`, shell vivo | `wayland-0`, shell vivo |
|
||||
|
||||
Sin el compositor/shell no hay sesión que arrancar, así que el arranque supervisado de escritorio no
|
||||
es verificable hoy por más cards que se inyecten. Y el andamiaje de la imagen tampoco está: en este
|
||||
hub `work/metal-rootfs` no existe y `work/gnome-rootfs` son 388 K de residuo, no el cierre hidratado.
|
||||
**El fallo del control desapareció.** No es una mejora buscada: `colord` moría arrancado por el
|
||||
script y vive arrancado por arje, con el bus ya listo porque la espera está dentro de su `argv`. Es
|
||||
exactamente lo que un supervisor está para dar, y el control es lo único que permite decirlo — sin
|
||||
él, «ColorManager OK» sería un dato suelto en vez de una diferencia.
|
||||
|
||||
**Lo que NO se hizo a propósito:** cablear los scripts de imagen a ciegas. Inyectar cards en un
|
||||
`genesis` que no se puede bootear es escribir código que nadie puede contradecir — y este documento
|
||||
ya tiene un ejemplo de lo que pasa entonces (el SDD 06 afirmando durante meses un lector que no
|
||||
existía). El mecanismo está probado donde SÍ se puede arrancar (§4b.2); el escritorio espera a que
|
||||
su corpus cierre.
|
||||
Los PIDs lo confirman: `polkit-compat`=119, `colord`=175, `upowerd`=178, `accounts-daemon`=180 —
|
||||
números bajos, de antes de que el lanzador de sesión existiera.
|
||||
|
||||
**Qué NO se comprobó:** no hay `screendump`. QEMU salió por timeout antes de poder pedirlo, y el
|
||||
control tampoco lo tuvo, así que la comparación es serial contra serial. Lo que se afirma acá es
|
||||
sobre los DEMONIOS (nombres del bus, PIDs, supervisión), no sobre el pintado; para afirmar que la
|
||||
imagen pinta sigue rigiendo la regla de validar escritorios con pantalla.
|
||||
|
||||
#### Las piezas, y por qué cada una está donde está
|
||||
|
||||
- **`takana service-cards <recetas…>`** — la traducción `[[service]]` → Card, con **una sola
|
||||
implementación**. El script de imagen ya parchea la seed con python; si además compusiera el JSON
|
||||
de la Card habría dos generadores del mismo formato divergiendo en silencio, y el formato es
|
||||
contrato con `card_core::Card`, que no es nuestro.
|
||||
- **`targets.py --service-paths <perfil>`** — une las dos mitades: qué es el servicio (receta) y si
|
||||
arranca (perfil). Sólo `scope = system`.
|
||||
- **`scripts/gnome/inyectar-cards.py`** — compone la seed, idempotente por `label`. Es un fichero y
|
||||
no un heredoc porque **el script de imagen ya tiene uno de python y anidar dos falló en vivo**: el
|
||||
terminador del interno cierra el externo y media cosa se ejecuta como shell.
|
||||
- **La espera del bus dentro del `argv`** de las cinco recetas de sistema. Sin ella un daemon puede
|
||||
arrancar antes de que dbus escuche y quedarse en modo idle sin registrar su nombre — un fallo que
|
||||
**no se ve**, porque el proceso vive y el bus no lo tiene. Los 5 hashes no se movieron.
|
||||
- **El guardia de `gnome-start` es por «¿está corriendo?», no por una perilla de la imagen.** Así es
|
||||
correcto venga de donde venga el proceso, y la misma copia sirve en una imagen sin cards. Una
|
||||
condición que consulta el estado real no puede desincronizarse de él; un flag sí.
|
||||
|
||||
#### Lo que este frente se encontró, no lo que se propuso
|
||||
|
||||
`--service-paths` devolvió **nueve** servicios de sistema y sólo seis eran míos: otro agente declaró
|
||||
`cupsd`, `bluetoothd` y `NetworkManager` en sus recetas y los habilitó en el perfil, con `-f`,
|
||||
`--nodetach` y `--no-daemon` — o sea que leyó y aplicó la regla de que arje supervisa al hijo directo
|
||||
y `Type=forking` no existe. El mecanismo se usó sin que nadie lo coordinara, que es la única prueba
|
||||
que vale de que el sitio donde se puso la declaración era el correcto.
|
||||
|
||||
#### Lo que queda de §4c
|
||||
|
||||
La base de la imagen es el `product-rootfs`, no `work/metal-rootfs`: ese fichero **no existe en este
|
||||
hub y ningún script del repo lo produce**. El producto sirve porque ya es arje-zero PID 1 + seed +
|
||||
busybox, pero el andamiaje de imagen de escritorio sigue dependiendo de un insumo sin dueño, y eso es
|
||||
su propia deuda.
|
||||
|
||||
Y las tres cosas de siempre: EXDEV. `store/` es bind-mount de `/dev/sdb` y `work/` vive en
|
||||
`/dev/sdc`, así que **todo lo que enlaza en vez de copiar** se rompe al cruzar — lo pegaron
|
||||
`hydrate-profile.py` (que ya lo detecta y dice dónde poner el `--into`) y el staging de
|
||||
`install-image-efi.sh` (configurable por `STAGE`). Hidratado, fundido y staging tienen que vivir bajo
|
||||
el mismo montaje.
|
||||
|
||||
## 5. El hueco que no es nuestro: no hay readiness
|
||||
|
||||
@@ -281,7 +329,8 @@ arriba en arje.
|
||||
| Emisor desde el sidecar (`service_cards`, falla ruidosa + test) | ✅ §4b |
|
||||
| Re-sellado de openssh con control → **reproduce bit a bit** | ✅ §4b.1 |
|
||||
| La cadena receta→sidecar→card→genesis→arje, **en arranque real** | ✅ §4b.2 (`PRODUCT_SSH_OK`) |
|
||||
| Que la imagen de ESCRITORIO los arranque y no el script `&` | ⛔ §4c, bloqueado (abajo) |
|
||||
| Que la imagen de ESCRITORIO los arranque y no el script `&` | ✅ §4c.1, con control (y arregló `colord`) |
|
||||
| `takana service-cards` + `--service-paths` + el inyector | ✅ §4c.1 |
|
||||
| `escritorio-cosmic`: auditar su `cosmic-start.sh` y habilitar | ◻ hoy sale con 5 AVISO |
|
||||
|
||||
**La primera lectura del vigía**, para que no haya que creerle a este documento: 0 errores y 18
|
||||
|
||||
@@ -89,9 +89,17 @@ XML
|
||||
# Va DESPUÉS del bus de sistema (registra `org.freedesktop.login1` ahí). Ese orden hoy lo garantiza
|
||||
# el script esperando el socket; en Cards lo daría el grafo de capacidades de arje, pero el
|
||||
# ordenamiento es por spawn y no por READINESS (SDD 30 §5) — el hueco está contado, no tapado.
|
||||
# ⚠ EL `argv` ESPERA AL BUS ANTES DEL `exec`, Y NO ES UNA MAÑA DE ESTA RECETA. arje ordena el
|
||||
# arranque por capacidades (orden topológico real), pero eso ordena el SPAWN, no el ESTAR LISTO: no
|
||||
# existe primitiva «esperá a que aparezca este socket» (DE-SYSTEMD-A-ARJE.md §13 lo lista como hueco
|
||||
# abierto). Sin la espera, este daemon puede arrancar antes de que dbus-daemon escuche y quedarse en
|
||||
# modo idle sin registrar nunca su nombre — un fallo que NO se ve: el proceso vive, el bus no lo
|
||||
# tiene. Mientras el hueco siga abierto la espera va DENTRO del card, a la vista en el argv y no
|
||||
# escondida en un wrapper, para que se pueda contar cuántos servicios la están pagando.
|
||||
[[service]]
|
||||
label = "logind-compat"
|
||||
id = "01HQAR53D4M2NBV8KZTYXFQA21"
|
||||
exec = "/usr/bin/arje-logind-compat"
|
||||
exec = "/bin/busybox"
|
||||
argv = ["sh", "-c", "i=0; while [ ! -S /run/dbus/system_bus_socket ] && [ $i -lt 100 ]; do i=$((i+1)); sleep 0.2; done; exec /usr/bin/arje-logind-compat"]
|
||||
envp = [["ARJE_LOGIN_STATE", "1"]]
|
||||
restart = { initial_ms = 500, max_ms = 20000 }
|
||||
|
||||
@@ -85,8 +85,16 @@ XML
|
||||
'''
|
||||
|
||||
# ── EL SERVICIO QUE ESTE PAQUETE TRAE (SDD 30) ──────────────────────────────────────────────────
|
||||
# ⚠ EL `argv` ESPERA AL BUS ANTES DEL `exec`, Y NO ES UNA MAÑA DE ESTA RECETA. arje ordena el
|
||||
# arranque por capacidades (orden topológico real), pero eso ordena el SPAWN, no el ESTAR LISTO: no
|
||||
# existe primitiva «esperá a que aparezca este socket» (DE-SYSTEMD-A-ARJE.md §13 lo lista como hueco
|
||||
# abierto). Sin la espera, este daemon puede arrancar antes de que dbus-daemon escuche y quedarse en
|
||||
# modo idle sin registrar nunca su nombre — un fallo que NO se ve: el proceso vive, el bus no lo
|
||||
# tiene. Mientras el hueco siga abierto la espera va DENTRO del card, a la vista en el argv y no
|
||||
# escondida en un wrapper, para que se pueda contar cuántos servicios la están pagando.
|
||||
[[service]]
|
||||
label = "polkit-compat"
|
||||
id = "01HQAR53D4M2NBV8KZTYXFQA22"
|
||||
exec = "/usr/bin/arje-polkit-compat"
|
||||
exec = "/bin/busybox"
|
||||
argv = ["sh", "-c", "i=0; while [ ! -S /run/dbus/system_bus_socket ] && [ $i -lt 100 ]; do i=$((i+1)); sleep 0.2; done; exec /usr/bin/arje-polkit-compat"]
|
||||
restart = { initial_ms = 500, max_ms = 20000 }
|
||||
|
||||
@@ -103,8 +103,16 @@ build = ["meson", "samurai", "python3", "pkgconf", "gettext-tiny", "py3-setuptoo
|
||||
# ── EL SERVICIO QUE ESTE PAQUETE TRAE (SDD 30) ──────────────────────────────────────────────────
|
||||
# Se lanza EXPLÍCITO, no por activación de D-Bus, y el motivo está medido en el script de sesión: la
|
||||
# activación por bus funciona, pero deja el arranque esperando el timeout y no deja log donde mirar.
|
||||
# ⚠ EL `argv` ESPERA AL BUS ANTES DEL `exec`, Y NO ES UNA MAÑA DE ESTA RECETA. arje ordena el
|
||||
# arranque por capacidades (orden topológico real), pero eso ordena el SPAWN, no el ESTAR LISTO: no
|
||||
# existe primitiva «esperá a que aparezca este socket» (DE-SYSTEMD-A-ARJE.md §13 lo lista como hueco
|
||||
# abierto). Sin la espera, este daemon puede arrancar antes de que dbus-daemon escuche y quedarse en
|
||||
# modo idle sin registrar nunca su nombre — un fallo que NO se ve: el proceso vive, el bus no lo
|
||||
# tiene. Mientras el hueco siga abierto la espera va DENTRO del card, a la vista en el argv y no
|
||||
# escondida en un wrapper, para que se pueda contar cuántos servicios la están pagando.
|
||||
[[service]]
|
||||
label = "accounts-daemon"
|
||||
id = "01HQAR53D4M2NBV8KZTYXFQA23"
|
||||
exec = "/usr/libexec/accounts-daemon"
|
||||
exec = "/bin/busybox"
|
||||
argv = ["sh", "-c", "i=0; while [ ! -S /run/dbus/system_bus_socket ] && [ $i -lt 100 ]; do i=$((i+1)); sleep 0.2; done; exec /usr/libexec/accounts-daemon"]
|
||||
restart = { initial_ms = 500, max_ms = 20000 }
|
||||
|
||||
@@ -74,8 +74,16 @@ build = ["meson", "samurai", "python3", "pkgconf", "gettext-tiny", "glib", "lcms
|
||||
# ── EL SERVICIO QUE ESTE PAQUETE TRAE (SDD 30) ──────────────────────────────────────────────────
|
||||
# Mutter lo pide al arrancar y, si no está, se come 25 s de timeout de activación D-Bus. Por eso es
|
||||
# un servicio declarado y no algo que el bus levante cuando alguien pregunte.
|
||||
# ⚠ EL `argv` ESPERA AL BUS ANTES DEL `exec`, Y NO ES UNA MAÑA DE ESTA RECETA. arje ordena el
|
||||
# arranque por capacidades (orden topológico real), pero eso ordena el SPAWN, no el ESTAR LISTO: no
|
||||
# existe primitiva «esperá a que aparezca este socket» (DE-SYSTEMD-A-ARJE.md §13 lo lista como hueco
|
||||
# abierto). Sin la espera, este daemon puede arrancar antes de que dbus-daemon escuche y quedarse en
|
||||
# modo idle sin registrar nunca su nombre — un fallo que NO se ve: el proceso vive, el bus no lo
|
||||
# tiene. Mientras el hueco siga abierto la espera va DENTRO del card, a la vista en el argv y no
|
||||
# escondida en un wrapper, para que se pueda contar cuántos servicios la están pagando.
|
||||
[[service]]
|
||||
label = "colord"
|
||||
id = "01HQAR53D4M2NBV8KZTYXFQA25"
|
||||
exec = "/usr/libexec/colord"
|
||||
exec = "/bin/busybox"
|
||||
argv = ["sh", "-c", "i=0; while [ ! -S /run/dbus/system_bus_socket ] && [ $i -lt 100 ]; do i=$((i+1)); sleep 0.2; done; exec /usr/libexec/colord"]
|
||||
restart = { initial_ms = 500, max_ms = 20000 }
|
||||
|
||||
@@ -58,8 +58,16 @@ install = "PYTHONPATH=/usr/lib/python3.12/site-packages DESTDIR=/out meson insta
|
||||
build = ["meson", "samurai", "python3", "pkgconf", "gettext-tiny", "py3-setuptools", "gobject-introspection", "gi-foreign-girs", "glib", "glib-introspected", "libgudev", "udev-pc", "libudev-zero", "polkit", "libffi", "pcre2", "zlib-shared", "expat", "dbus-shared"]
|
||||
|
||||
# ── EL SERVICIO QUE ESTE PAQUETE TRAE (SDD 30) ──────────────────────────────────────────────────
|
||||
# ⚠ EL `argv` ESPERA AL BUS ANTES DEL `exec`, Y NO ES UNA MAÑA DE ESTA RECETA. arje ordena el
|
||||
# arranque por capacidades (orden topológico real), pero eso ordena el SPAWN, no el ESTAR LISTO: no
|
||||
# existe primitiva «esperá a que aparezca este socket» (DE-SYSTEMD-A-ARJE.md §13 lo lista como hueco
|
||||
# abierto). Sin la espera, este daemon puede arrancar antes de que dbus-daemon escuche y quedarse en
|
||||
# modo idle sin registrar nunca su nombre — un fallo que NO se ve: el proceso vive, el bus no lo
|
||||
# tiene. Mientras el hueco siga abierto la espera va DENTRO del card, a la vista en el argv y no
|
||||
# escondida en un wrapper, para que se pueda contar cuántos servicios la están pagando.
|
||||
[[service]]
|
||||
label = "upowerd"
|
||||
id = "01HQAR53D4M2NBV8KZTYXFQA24"
|
||||
exec = "/usr/libexec/upowerd"
|
||||
exec = "/bin/busybox"
|
||||
argv = ["sh", "-c", "i=0; while [ ! -S /run/dbus/system_bus_socket ] && [ $i -lt 100 ]; do i=$((i+1)); sleep 0.2; done; exec /usr/libexec/upowerd"]
|
||||
restart = { initial_ms = 500, max_ms = 20000 }
|
||||
|
||||
@@ -187,7 +187,8 @@ say "dbus sesión: ${DBUS_SESSION_BUS_ADDRESS:-NO ARRANCO}"
|
||||
# org.freedesktop.login1 ahí) y ANTES del shell. Mutter no sólo lo consulta por D-Bus: pregunta por
|
||||
# SU sesión vía la C-ABI sd-login, que libelogind resuelve LEYENDO /run/systemd/sessions. Sin este
|
||||
# daemon esos ficheros no existen y mutter muere en "Failed to find any matching session".
|
||||
if [ -x /usr/bin/arje-logind-compat ]; then
|
||||
if supervisado arje-logind-compat "logind-compat"; then :
|
||||
elif [ -x /usr/bin/arje-logind-compat ]; then
|
||||
# ARJE_LOGIN_STATE=1: la perilla que YA traía el daemon y que nadie prendía. Su comentario en
|
||||
# arje-compat lo dice explícito — "en un host CON systemd real pisaría su estado; en arje (sin
|
||||
# systemd) el launcher de sesión lo prende. Default off" — y el launcher de sesión es ESTE script.
|
||||
@@ -285,6 +286,23 @@ esperar_nombre() {
|
||||
return 1
|
||||
}
|
||||
|
||||
# ── ¿YA LO SUPERVISA ARJE? (SDD 30 §4c) ─────────────────────────────────────────────────────────
|
||||
# Desde que los demonios de sistema son Cards del `genesis`, PID 1 ya los encarnó antes de que este
|
||||
# script corra. Relanzarlos daría DOS procesos peleando por el mismo nombre del bus, y el segundo
|
||||
# fallaría con un error que no nombra la causa.
|
||||
#
|
||||
# El guardia se hace por «¿está corriendo?» y NO por una perilla de la imagen, a propósito: así es
|
||||
# correcto venga de donde venga el proceso —arje, otro script, una corrida anterior— y esta misma
|
||||
# copia sigue sirviendo en una imagen donde las Cards no se inyectaron. Una condición que consulta
|
||||
# el estado real no puede desincronizarse de él; un flag sí.
|
||||
ya_corre() { # $1 = nombre del ejecutable tal como lo ve el kernel
|
||||
pidof "$1" >/dev/null 2>&1
|
||||
}
|
||||
supervisado() { # $1 = ejecutable, $2 = etiqueta para el log
|
||||
if ya_corre "$1"; then say "$2 ya vive (pid $(pidof "$1" | cut -d' ' -f1)) — lo supervisa arje, no lo relanzo"; return 0; fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# arje-polkit-compat: el PolicyKit1 del fractal. **VA ANTES que accounts-daemon y upowerd**, y el
|
||||
# orden no es estético: los dos se bloquean en `polkit_authority_get_sync()` al arrancar, así que si
|
||||
# `org.freedesktop.PolicyKit1` no está en el bus se quedan conectados pero SIN adquirir su nombre —y
|
||||
@@ -301,7 +319,8 @@ esperar_nombre() {
|
||||
# tranquilo y el chequeo del final dice `PolicyKit1 OK` — o sea que el resumen final da verde y los
|
||||
# clientes ya están muertos. La carrera es real y la ganaba cualquiera: es la misma lección de «un pid
|
||||
# no es un servicio», aplicada al productor en vez de al consumidor.
|
||||
if [ -x /usr/bin/arje-polkit-compat ]; then
|
||||
if supervisado arje-polkit-compat "polkit-compat"; then :
|
||||
elif [ -x /usr/bin/arje-polkit-compat ]; then
|
||||
/usr/bin/arje-polkit-compat >/tmp/polkit-compat.log 2>&1 &
|
||||
say "arje-polkit-compat lanzado (pid $!)"
|
||||
esperar_nombre org.freedesktop.PolicyKit1 /tmp/polkit-compat.log \
|
||||
@@ -318,7 +337,8 @@ fi
|
||||
# lo es. Corriendo todo como root la activación probablemente funcionaría igual, pero depender de eso
|
||||
# es depender de un accidente; lanzarlo a mano es una línea y no deja ambigüedad. Si además llegara a
|
||||
# activarse por bus, el segundo simplemente no adquiere el nombre y se va: no hay daño.
|
||||
if [ -x /usr/libexec/accounts-daemon ]; then
|
||||
if supervisado accounts-daemon "accounts-daemon"; then :
|
||||
elif [ -x /usr/libexec/accounts-daemon ]; then
|
||||
/usr/libexec/accounts-daemon >/tmp/accounts-daemon.log 2>&1 &
|
||||
say "accounts-daemon lanzado (pid $!)"
|
||||
else
|
||||
@@ -334,7 +354,8 @@ fi
|
||||
# Los directorios de estado los deriva upower de --prefix (historydir/statedir vacíos ⇒
|
||||
# $prefix/var/lib/upower); si no existen, arranca y se muere sin ruido.
|
||||
mkdir -p /usr/var/lib/upower /var/lib/upower
|
||||
if [ -x /usr/libexec/upowerd ]; then
|
||||
if supervisado upowerd "upowerd"; then :
|
||||
elif [ -x /usr/libexec/upowerd ]; then
|
||||
/usr/libexec/upowerd >/tmp/upowerd.log 2>&1 &
|
||||
UPOWERD_PID=$!
|
||||
say "upowerd lanzado (pid $UPOWERD_PID)"
|
||||
@@ -433,7 +454,8 @@ fi
|
||||
# 25 s de timeout de activación D-Bus. Se lanza explícito por el mismo motivo que accounts-daemon y
|
||||
# upowerd: la activación por bus funciona (el launch-helper ya es setuid) pero deja el arranque
|
||||
# esperando, y lanzarlo a mano además deja un log donde mirar si no adquiere el nombre.
|
||||
if [ -x /usr/libexec/colord ]; then
|
||||
if supervisado colord "colord"; then :
|
||||
elif [ -x /usr/libexec/colord ]; then
|
||||
# --verbose: su log normal se corta después de abrir las tres bases de datos y no dice nada más, así
|
||||
# que sin esto un colord que no adquiere el nombre es indistinguible de uno que se cuelga.
|
||||
CD_VERBOSE=1 /usr/libexec/colord --verbose >/tmp/colord.log 2>&1 &
|
||||
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
# inyectar-cards.py — mete las Cards de servicio de un perfil en el `genesis` de la seed (SDD 30 §4c).
|
||||
#
|
||||
# Por qué es un fichero y no un heredoc dentro del script de imagen: el script ya tiene un heredoc de
|
||||
# python (el del console-getty) y anidar dos es la receta del error mudo — el terminador del interno
|
||||
# cierra el externo y la mitad del programa se ejecuta como shell. Pasó al escribir esto.
|
||||
#
|
||||
# NO genera las Cards: las recibe ya emitidas por `takana service-cards`, que es la única
|
||||
# implementación de la traducción `[[service]]` → Card. Acá sólo se COMPONE la seed.
|
||||
import json, os, sys
|
||||
|
||||
seed_p, cards_p = sys.argv[1], sys.argv[2]
|
||||
seed = json.load(open(seed_p))
|
||||
cards = json.load(open(cards_p))
|
||||
|
||||
# Idempotente por `label`: re-armar la imagen no puede duplicar Entes. Dos Cards con el mismo label
|
||||
# no son "dos servicios": para el grafo de arje son una colisión de identidad, y el segundo se
|
||||
# perdería en silencio o pelearía con el primero por el mismo nombre del bus.
|
||||
ya = {g.get("label") for g in seed.get("genesis", [])}
|
||||
puestos = [c["label"] for c in cards if c["label"] not in ya]
|
||||
seed.setdefault("genesis", []).extend(c for c in cards if c["label"] not in ya)
|
||||
|
||||
tmp = seed_p + ".new"
|
||||
with open(tmp, "w") as f:
|
||||
json.dump(seed, f, indent=2)
|
||||
os.chmod(tmp, 0o644)
|
||||
# Reemplazar la ENTRADA de directorio, no escribir en sitio: la seed del árbol fundido es un hardlink
|
||||
# read-only al artefacto del store, y escribirle encima mutaría el store.
|
||||
os.replace(tmp, seed_p)
|
||||
print(" cards en el genesis:", ", ".join(puestos) or "(ninguna nueva)")
|
||||
@@ -209,6 +209,20 @@ os.chmod(tmp, 0o644)
|
||||
os.replace(tmp, p)
|
||||
PY
|
||||
|
||||
echo "==> inyectando las Cards de servicio en el genesis (SDD 30 4c)"
|
||||
# LO QUE ESTO CAMBIA: hasta hoy los demonios de sistema los lanzaba `gnome-start` con `&` -- sin
|
||||
# supervision, sin backoff y sin el `CRASHED` real, o sea sin nada de lo que arje es PID 1 para dar.
|
||||
# Aca pasan a ser Cards del `genesis`, que es de donde arje los encarna y revive.
|
||||
#
|
||||
# Las dos mitades salen de donde viven: QUE es cada servicio, de `[[service]]` en su receta; CUALES
|
||||
# arranca esta imagen, de `servicios = [...]` del perfil en targets.toml. La traduccion receta->Card
|
||||
# la hace `takana service-cards` y no este script: el formato es contrato con `card_core::Card`, y
|
||||
# dos generadores del mismo formato divergen en silencio.
|
||||
CARDS_JSON="${CARDS_JSON:-$(mktemp)}"
|
||||
./target/release/takana service-cards $(python3 scripts/targets.py --service-paths escritorio-gnome) > "$CARDS_JSON"
|
||||
[ -s "$CARDS_JSON" ] || { echo " x no pude emitir las cards de servicio" >&2; exit 1; }
|
||||
python3 scripts/gnome/inyectar-cards.py "$MERGED/ente/seed.card.json" "$CARDS_JSON"
|
||||
|
||||
# /var/run → /run: arje-logind-compat busca el bus de sistema en la ruta LEGACY
|
||||
# (/var/run/dbus/system_bus_socket) y dbus-daemon escucha en /run/dbus/system_bus_socket. En casi
|
||||
# toda distro /var/run es un symlink a /run desde hace 15 años; esta base metal es tan mínima que ni
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
# scripts/targets.py --json # todo el manifiesto expandido
|
||||
# scripts/targets.py --services cli # los servicios que ESA imagen arranca, resueltos
|
||||
# scripts/targets.py --selftest # roturas a propósito + un control que TIENE que pasar
|
||||
# scripts/targets.py --service-paths gnome # rutas de receta de los servicios de SISTEMA
|
||||
#
|
||||
# ⚠ La FLAG va en inglés (regla 4 del CLAUDE.md: la superficie de CLI es contrato) aunque `--lista`
|
||||
# sea deuda vieja del mismo fichero. La CLAVE del TOML sí es `servicios`, en castellano, porque ahí
|
||||
@@ -110,6 +111,19 @@ GRAFOS = ["build-state.json", "build-state-gnome.json", "build-state-kde.json",
|
||||
"build-state-cosmic.json", "build-state-wlr.json"]
|
||||
|
||||
|
||||
def _ruta_de(nombre, recipes_dir=None):
|
||||
"""La ruta de la receta que se llama `nombre`. Se busca por el campo `name`, no por el nombre de
|
||||
fichero: son casi siempre iguales, pero el que manda es el campo."""
|
||||
recipes_dir = Path(recipes_dir) if recipes_dir else ROOT / "recipes"
|
||||
for f in sorted(recipes_dir.rglob("*.toml")):
|
||||
try:
|
||||
if tomllib.load(open(f, "rb")).get("name") == nombre:
|
||||
return str(f.relative_to(ROOT))
|
||||
except tomllib.TOMLDecodeError:
|
||||
continue
|
||||
return None
|
||||
|
||||
|
||||
def _membresia():
|
||||
"""{receta: [perfiles]} fusionando TODOS los grafos de estado. `{}` si no hay ninguno (no es
|
||||
fatal: degrada a no poder comprobar la pertenencia, y se dice)."""
|
||||
@@ -268,6 +282,29 @@ def main():
|
||||
return 0
|
||||
if args[0] == "--selftest":
|
||||
return 0 if autotest() else 1
|
||||
if args[0] == "--service-paths":
|
||||
# Las RUTAS de receta de los servicios de SISTEMA habilitados, para que el ensamblado de la
|
||||
# imagen se las pase a `takana service cards`. Sólo `system`: una Card de sesión en el
|
||||
# `genesis` arrancaría antes de que exista XDG_RUNTIME_DIR o un usuario (SDD 30 §4c).
|
||||
if len(args) < 2 or args[1] not in perfiles:
|
||||
print(f"uso: targets.py --service-paths <perfil>", file=sys.stderr)
|
||||
return 1
|
||||
decl = declaraciones()
|
||||
memb = _membresia()
|
||||
raices = set(perfiles[args[1]]["raices"])
|
||||
vistas = []
|
||||
for lbl in perfiles[args[1]]["servicios_exp"]:
|
||||
for receta, svc in decl.get(lbl, []):
|
||||
if svc.get("scope", "system") != "system":
|
||||
continue
|
||||
if not (receta in raices or args[1] in memb.get(receta, [])):
|
||||
continue
|
||||
ruta = _ruta_de(receta)
|
||||
if ruta and ruta not in vistas:
|
||||
vistas.append(ruta)
|
||||
for r in vistas:
|
||||
print(r)
|
||||
return 0
|
||||
if args[0] in ("--services", "-s"):
|
||||
if len(args) < 2 or args[1] not in perfiles:
|
||||
print(f"uso: targets.py --services <perfil> (hay: {', '.join(sorted(perfiles))})",
|
||||
|
||||
Reference in New Issue
Block a user