gioser

tawasuyu/brahman

Rust 476TOML 190Markdown 50JSON 176602 Sutichaykuna

⎇ 762bf95dfd770cc8bf51f21605bc2cc6cc88ea76 crates/init/arje-absorb/src/card.rs Rust · 193 siq'ikuna · 6.3 KB Ñawpa willay Pi imata llamkarqan ch'uya

feat(arje): arje-absorb — absorbe otros inits a una Semilla brahman 762bf95df · sergio ·

1//! Traducción del modelo intermedio a Tarjetas Semilla brahman.
2//!
3//! Cada [`ForeignService`] se vuelve una Card hija; el conjunto cuelga
4//! como `genesis` de una Card raíz que `arje-zero` encarna al arrancar.
5
6use std::time::Duration;
7
8use brahman_card::{
9    Capability, Card, CgroupSpec, FsPolicy, Lifecycle, NetworkingPolicy, Payload, Permissions,
10    Priority, SomaSpec, Supervision,
11};
12
13use crate::model::{ForeignService, ServiceKind};
14
15/// Convierte un servicio absorbido en una Card hija (genesis child).
16fn service_to_card(svc: &ForeignService) -> Card {
17    let (lifecycle, supervision) = match svc.kind {
18        ServiceKind::Daemon => (
19            Lifecycle::Daemon,
20            Supervision::Restart {
21                initial: Duration::from_millis(1_000),
22                max: Duration::from_millis(60_000),
23            },
24        ),
25        ServiceKind::OneShot => (Lifecycle::Oneshot, Supervision::OneShot),
26    };
27    Card {
28        payload: Payload::Native {
29            exec: svc.exec.clone(),
30            argv: svc.argv.clone(),
31            envp: svc.env.clone(),
32        },
33        supervision,
34        lifecycle,
35        priority: Priority::Normal,
36        // Servicio de sistema absorbido: sin aislar (namespaces en
37        // `false` por defecto), con FS de escritura, red y subprocesos.
38        // La migración conserva el comportamiento; endurecer el sandbox
39        // queda como trabajo posterior, Card por Card.
40        permissions: Permissions {
41            filesystem: FsPolicy::ReadWrite,
42            networking: NetworkingPolicy::Full,
43            processes: true,
44            ..Permissions::default()
45        },
46        soma: SomaSpec {
47            cgroup: CgroupSpec {
48                path: "ente.slice/absorbed".to_string(),
49                ..CgroupSpec::default()
50            },
51            ..SomaSpec::default()
52        },
53        ..Card::new(svc.name.clone())
54    }
55}
56
57/// La Card de `carmen-dm`: el compositor `mirada` en modo greeter,
58/// como gestor de login gráfico. Para agregar a una Semilla absorbida
59/// al migrar un escritorio (flag `--with-carmen`). Idéntica a la
60/// entrada `carmen-dm` de `seeds/arje-host.card.json`.
61pub fn carmen_dm_card() -> Card {
62    Card {
63        payload: Payload::Native {
64            exec: "/usr/bin/mirada-compositor".to_string(),
65            argv: vec!["--greeter".to_string(), "--drm".to_string()],
66            envp: vec![
67                (
68                    "PATH".to_string(),
69                    "/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin".to_string(),
70                ),
71                ("XDG_RUNTIME_DIR".to_string(), "/run".to_string()),
72                (
73                    "MIRADA_GREETER_BIN".to_string(),
74                    "/usr/bin/mirada-greeter".to_string(),
75                ),
76            ],
77        },
78        supervision: Supervision::Restart {
79            initial: Duration::from_millis(2_000),
80            max: Duration::from_millis(60_000),
81        },
82        lifecycle: Lifecycle::Daemon,
83        priority: Priority::High,
84        permissions: Permissions {
85            filesystem: FsPolicy::ReadWrite,
86            networking: NetworkingPolicy::None,
87            processes: true,
88            ..Permissions::default()
89        },
90        soma: SomaSpec {
91            cgroup: CgroupSpec {
92                path: "ente.slice/carmen".to_string(),
93                ..CgroupSpec::default()
94            },
95            ..SomaSpec::default()
96        },
97        ..Card::new("carmen-dm")
98    }
99}
100
101/// Arma una Tarjeta Semilla raíz que encarna todos los servicios
102/// absorbidos como hijas `genesis` de `arje-zero`.
103pub fn build_seed(label: &str, services: &[ForeignService]) -> Card {
104    let genesis: Vec<Card> = services.iter().map(service_to_card).collect();
105    Card {
106        provides: [Capability::Spawn, Capability::Journal]
107            .into_iter()
108            .collect(),
109        permissions: Permissions {
110            filesystem: FsPolicy::ReadWrite,
111            networking: NetworkingPolicy::Full,
112            processes: true,
113            ..Permissions::default()
114        },
115        genesis,
116        ..Card::new(label)
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    fn svc(name: &str, kind: ServiceKind) -> ForeignService {
125        ForeignService {
126            name: name.to_string(),
127            exec: "/usr/bin/foo".to_string(),
128            argv: vec!["-x".to_string()],
129            env: Vec::new(),
130            kind,
131        }
132    }
133
134    #[test]
135    fn seed_validates() {
136        let seed = build_seed(
137            "arje.seed.absorbed",
138            &[svc("a", ServiceKind::Daemon), svc("b", ServiceKind::OneShot)],
139        );
140        seed.validate().expect("la Semilla absorbida debe validar");
141        assert_eq!(seed.genesis.len(), 2);
142    }
143
144    #[test]
145    fn daemon_maps_to_restart() {
146        let c = service_to_card(&svc("d", ServiceKind::Daemon));
147        assert!(matches!(c.supervision, Supervision::Restart { .. }));
148        assert_eq!(c.lifecycle, Lifecycle::Daemon);
149    }
150
151    #[test]
152    fn oneshot_maps_to_oneshot() {
153        let c = service_to_card(&svc("o", ServiceKind::OneShot));
154        assert!(matches!(c.supervision, Supervision::OneShot));
155        assert_eq!(c.lifecycle, Lifecycle::Oneshot);
156    }
157
158    #[test]
159    fn children_get_distinct_ids() {
160        let seed = build_seed(
161            "x",
162            &[svc("a", ServiceKind::Daemon), svc("b", ServiceKind::Daemon)],
163        );
164        assert_ne!(seed.genesis[0].id, seed.genesis[1].id);
165    }
166
167    #[test]
168    fn empty_service_list_still_validates() {
169        let seed = build_seed("arje.seed.absorbed", &[]);
170        seed.validate().expect("una Semilla sin hijas es válida");
171    }
172
173    #[test]
174    fn carmen_card_is_valid_greeter() {
175        let c = carmen_dm_card();
176        c.validate().expect("la Card de carmen-dm debe validar");
177        match &c.payload {
178            brahman_card::Payload::Native { exec, argv, .. } => {
179                assert_eq!(exec, "/usr/bin/mirada-compositor");
180                assert!(argv.contains(&"--greeter".to_string()));
181            }
182            _ => panic!("carmen-dm debe ser un payload Native"),
183        }
184    }
185
186    #[test]
187    fn carmen_can_be_appended_to_absorbed_seed() {
188        let mut seed = build_seed("arje.seed.absorbed", &[svc("a", ServiceKind::Daemon)]);
189        seed.genesis.push(carmen_dm_card());
190        seed.validate().expect("la Semilla con carmen debe validar");
191        assert_eq!(seed.genesis.len(), 2);
192    }
193}