SDD 30 §4a+§4c: los 9 demonios de GNOME declarados — y aparecieron dos que no estaban en NINGÚN perfil
Lo que el script de sesión lanza con `&` ahora está declarado en las recetas y habilitado en el perfil. Ninguna receta movió su hash: 9/9 idénticos a los que los grafos ya registraban. EL HALLAZGO, y no lo buscaba: la comprobación inversa del resolutor rechazó `arje-logind-compat` y `arje-polkit-compat` porque están en CERO perfiles — y sin embargo qemu-desktop-image.sh los copia al rootfs a mano y el de COSMIC hace `exit 1` si falta logind-compat. Dos binarios imprescindibles, presentes en la imagen y ausentes del destino declarado: la misma forma del agujero de `foot`, encontrada por una comprobación en vez de por una imagen inusable. Son raíces de escritorio-gnome (los dos) y de escritorio-cosmic (sólo logind, verificado que sus scripts no nombran polkit). DOS COSAS QUE NO SON TRANSCRIPCIÓN: - `dbus-daemon --fork` no se traduce tal cual: arje supervisa al HIJO DIRECTO y Type=forking no existe, así que un daemon que forkea y sale deja a arje viendo morir al padre con éxito y reencarnándolo para siempre. La card usa --nofork. - `scope = system|session` decide DÓNDE va la card. Las de sesión necesitan XDG_RUNTIME_DIR y usuario logueado; en el genesis arrancarían antes de que exista ninguno. Y fuera de mirada NADIE entrega cards de sesión todavía, así que salen con AVISO: el hueco queda contado, no omitido. Correcciones propias: la unicidad del label es DENTRO del perfil, no del corpus (upower vive legítimamente en dos colas); la membresía se lee de los CINCO grafos, no sólo el del corpus; una RAÍZ manda sobre el grafo, que es derivado y lo regenera el cron; y la flag nace en inglés (`--services`) como manda la regla 4, aunque `--lista` sea deuda vieja del mismo fichero. `--selftest`: 7 casos, el primero es el CONTROL que tiene que pasar en verde.
This commit is contained in:
@@ -67,6 +67,21 @@ pub struct Service {
|
||||
/// `daemon` (default) u `oneshot` — el `lifecycle` de la Card.
|
||||
#[serde(default)]
|
||||
pub kind: Kind,
|
||||
/// **De sistema o de sesión** — y no es cosmético: decide DÓNDE va la Card.
|
||||
///
|
||||
/// `system` (default) = arranca al boot, va en el `genesis` de la seed. `session` = pertenece a
|
||||
/// una sesión gráfica (pipewire, wireplumber, el bus de sesión): necesita `XDG_RUNTIME_DIR` y un
|
||||
/// usuario logueado, así que meterla en el genesis la arrancaría antes de que exista ninguna de
|
||||
/// las dos cosas.
|
||||
///
|
||||
/// El campo existe porque el destino de una Card de sesión **hoy no está resuelto fuera de
|
||||
/// mirada**: ahí lo hace el compositor (`mirada-compositor/src/session.rs` arma
|
||||
/// `session.{label}` con `requires = [wayland_floor()]` y la entrega a PID 1 por `RunCard`),
|
||||
/// pero mutter y kwin no tienen esa integración y sus demonios de sesión los sigue lanzando el
|
||||
/// script de arranque. Declararlo igual hace que el hueco se CUENTE en vez de omitirse —
|
||||
/// `scripts/targets.py --servicios` avisa por cada uno.
|
||||
#[serde(default)]
|
||||
pub scope: Scope,
|
||||
/// Política de supervisión. **Ausente ⇒ `OneShot`** (no revive). Presente ⇒ `Restart` con
|
||||
/// backoff exponencial entre `initial_ms` y `max_ms`, y su muerte es el `CRASHED` real.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
@@ -97,6 +112,24 @@ impl Kind {
|
||||
}
|
||||
}
|
||||
|
||||
/// Dónde vive la Card: arranque del sistema o sesión gráfica. Ver [`Service::scope`].
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Scope {
|
||||
#[default]
|
||||
System,
|
||||
Session,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Scope::System => "system",
|
||||
Scope::Session => "session",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `permissions.networking` de la Card.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
@@ -243,6 +276,7 @@ mod tests {
|
||||
.collect(),
|
||||
envp: vec![("HOME".into(), "/root".into())],
|
||||
kind: Kind::Daemon,
|
||||
scope: Scope::System,
|
||||
restart: Some(Restart { initial_ms: 500, max_ms: 20000 }),
|
||||
networking: Networking::Full,
|
||||
cgroup: Some("arje.slice/sshd".into()),
|
||||
@@ -311,6 +345,24 @@ restart = { initial_ms = 500, max_ms = 20000 }
|
||||
assert!(s.validate().unwrap_err().to_string().contains("initial_ms"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn el_scope_es_system_por_defecto_y_no_entra_en_la_card() {
|
||||
// El scope decide DÓNDE se coloca la Card, no qué dice: la Card de un servicio de sesión
|
||||
// tiene la misma forma. Si entrara en el JSON, cambiaría el esquema de `card_core::Card`,
|
||||
// que no es nuestro.
|
||||
let s: Service = toml::from_str(
|
||||
"label='pipewire'\nid='01HQAR53D4M2NBV8KZTYXFQA20'\nexec='/usr/bin/pipewire'",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(s.scope, Scope::System, "default");
|
||||
let con_sesion: Service = toml::from_str(
|
||||
"label='pipewire'\nid='01HQAR53D4M2NBV8KZTYXFQA20'\nexec='/usr/bin/pipewire'\nscope='session'",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(con_sesion.scope, Scope::Session);
|
||||
assert_eq!(s.card(), con_sesion.card(), "el scope no cambia la Card");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_all_rechaza_duplicados() {
|
||||
let a = sshd();
|
||||
|
||||
Reference in New Issue
Block a user