tawasuyu

tawasuyu/brahman

Rust 321TOML 122JSON 14Markdown 114332 Definitions

⎇ main crates/init/arje-zero/src/seed.rs Rust · 249 lines · 8.3 KB History Who touched what raw

refactor(naming): A1 — ente→arje, vista→revista, pluma→fana b83d40a83 · sergio ·

1//! Construcción de la Tarjeta Semilla.
2//!
3//! Tres caminos:
4//!   1. `--restore <path>`: leer `FractalSnapshot` y reconstruir Semilla
5//!      con seed_id preservado + entes anteriores como genesis.
6//!   2. `seed.card.json` en disco: deserialize directo (prod o dev).
7//!   3. Fallback dev: sintetizar Semilla + 6 genesis Entes que ejercitan
8//!      todas las capacidades del fractal.
9
10use anyhow::Context;
11use arje_card::{
12    Capability, CardError, CgroupSpec, EntityCard, NamespaceSet, Payload,
13    ResourceLimits, SomaSpec, Supervision, CARD_SCHEMA_VERSION,
14};
15use std::collections::BTreeSet;
16use std::path::{Path, PathBuf};
17use std::time::Duration;
18use tracing::{info, warn};
19use ulid::Ulid;
20
21const SEED_PATH_PROD: &str = "/ente/seed.card";
22const SEED_PATH_DEV: &str = "seed.card";
23
24pub fn load(dev_mode: bool, restore: Option<&Path>) -> anyhow::Result<EntityCard> {
25    let card = if let Some(path) = restore {
26        load_from_snapshot(path)?
27    } else {
28        load_or_synthesize(dev_mode)?
29    };
30    card.validate()
31        .map_err(|e: CardError| anyhow::anyhow!("semilla inválida: {e}"))?;
32    Ok(card)
33}
34
35fn load_from_snapshot(path: &Path) -> anyhow::Result<EntityCard> {
36    let snap = arje_snapshot::FractalSnapshot::read(path)
37        .with_context(|| format!("read snapshot {}", path.display()))?;
38    info!(
39        path = %path.display(),
40        seed_id = %snap.seed_id,
41        entes = snap.entes.len(),
42        timestamp_ms = snap.timestamp_ms,
43        "snapshot cargado, restaurando fractal"
44    );
45    // Reconstruimos la Semilla con su Ulid original. Las Cards persistidas
46    // van a `genesis` con sus Ulids preservados — son las mismas identidades
47    // que vivieron antes del checkpoint.
48    let mut provides = BTreeSet::new();
49    provides.insert(Capability::Spawn);
50    provides.insert(Capability::Journal);
51    Ok(EntityCard {
52        schema_version: CARD_SCHEMA_VERSION,
53        id: snap.seed_id,
54        lineage: None,
55        label: snap.seed_label,
56        provides,
57        requires: BTreeSet::new(),
58        soma: SomaSpec::default(),
59        payload: Payload::Virtual,
60        supervision: Supervision::OneShot,
61        genesis: snap.entes,
62        ..Default::default()
63    })
64}
65
66fn load_or_synthesize(dev_mode: bool) -> anyhow::Result<EntityCard> {
67    // Buscamos primero `.json` (canónico), luego sin extensión por
68    // compatibilidad con instalaciones que dejan el archivo crudo. La puerta
69    // genética se cruza vía `arje_brain::load_card_file` que pasa por
70    // `validate()` extendido.
71    let candidates: &[&str] = if dev_mode {
72        &["seed.card.json", SEED_PATH_DEV]
73    } else {
74        &["/ente/seed.card.json", SEED_PATH_PROD]
75    };
76    for cand in candidates {
77        let path = PathBuf::from(cand);
78        if !path.exists() { continue; }
79        let card = arje_brain::load_card_file(&path)
80            .with_context(|| format!("load {}", path.display()))?;
81        info!(path = %path.display(), "Tarjeta Semilla cargada y validada");
82        return Ok(card);
83    }
84    if dev_mode {
85        info!("sin seed.card — sintetizando semilla mínima (dev)");
86        return Ok(synthesize_dev_seed());
87    }
88    anyhow::bail!("seed.card no encontrada en /ente/seed.card.json ni /ente/seed.card")
89}
90
91fn synthesize_dev_seed() -> EntityCard {
92    let mut provides = BTreeSet::new();
93    provides.insert(Capability::Spawn);
94    provides.insert(Capability::Journal);
95
96    // Pre-registramos el módulo Wasm demo en el CAS y obtenemos su SHA real.
97    // Si el CAS no es escribible (raro en dev) caemos a un SHA cero — la
98    // resolución fallará y el Wasm no encarnará, pero el resto queda intacto.
99    let demo_wasm_sha = match arje_wasm::demo_module_bytes()
100        .and_then(|b| arje_cas::store(&b))
101    {
102        Ok(sha) => sha,
103        Err(e) => {
104            warn!(?e, "CAS no disponible — demo-wasm no encarnará");
105            [0u8; 32]
106        }
107    };
108
109    let mut genesis = Vec::new();
110    genesis.push(make_card("demo-sleep", Payload::Native {
111        exec: "/bin/sleep".into(), argv: vec!["1".into()], envp: vec![],
112    }, Supervision::OneShot));
113
114    genesis.push(make_card("demo-persist", Payload::Native {
115        exec: "/bin/sleep".into(), argv: vec!["60".into()], envp: vec![],
116    }, restart_supervision()));
117
118    // Card namespaced: padre escribe uid_map, hijo cat /proc/self/uid_map.
119    let mut ns_card = make_card("demo-userns", Payload::Native {
120        exec: "/bin/cat".into(),
121        argv: vec!["/proc/self/uid_map".into()],
122        envp: vec![],
123    }, Supervision::OneShot);
124    ns_card.soma = SomaSpec {
125        namespaces: NamespaceSet { user: true, ..Default::default() },
126        ..Default::default()
127    };
128    genesis.push(ns_card);
129
130    genesis.push(make_card("demo-wasm", Payload::Wasm {
131        module_sha256: demo_wasm_sha,
132        entry: "_start".into(),
133    }, Supervision::OneShot));
134
135    if let Some(card) = optional_native_card(
136        "demo-echo", "target/debug/ente-echo",
137        [arje_echo::echo_capability()].into_iter().collect(),
138        restart_supervision(),
139    ) {
140        genesis.push(card);
141    }
142
143    if let Some(card) = optional_native_card(
144        "compat-logind", "target/debug/ente-logind-compat",
145        [Capability::LegacyLogind].into_iter().collect(),
146        restart_supervision(),
147    ) {
148        genesis.push(card);
149    }
150
151    // Constelación de shims D-Bus que reemplazan systemd: cada uno provee
152    // un nombre `org.freedesktop.X1` que GNOME/KDE consultan al boot.
153    for (label, bin) in &[
154        ("compat-hostnamed", "target/debug/ente-hostnamed-compat"),
155        ("compat-timedated", "target/debug/ente-timedated-compat"),
156        ("compat-localed",   "target/debug/ente-localed-compat"),
157        ("compat-journald",  "target/debug/ente-journald-compat"),
158        ("compat-resolved",  "target/debug/ente-resolved-compat"),
159        ("compat-polkit",    "target/debug/ente-polkit-compat"),
160        ("compat-machined",  "target/debug/ente-machined-compat"),
161        ("policy-provider",  "target/debug/ente-policy-provider"),
162        ("compat-systemd1",  "target/debug/ente-systemd1-compat"),
163        ("compat-notify",    "target/debug/ente-notify-compat"),
164        ("compat-timer",     "target/debug/ente-timer-compat"),
165    ] {
166        if let Some(card) = optional_native_card(
167            label, bin,
168            std::collections::BTreeSet::new(),
169            restart_supervision(),
170        ) {
171            genesis.push(card);
172        }
173    }
174
175    EntityCard {
176        schema_version: CARD_SCHEMA_VERSION,
177        id: Ulid::new(),
178        lineage: None,
179        label: "ente-zero-dev".into(),
180        provides,
181        requires: BTreeSet::new(),
182        soma: SomaSpec {
183            namespaces: NamespaceSet::default(),
184            rlimits: ResourceLimits::default(),
185            cgroup: CgroupSpec {
186                path: "ente.slice/zero".into(),
187                cpu_weight: None,
188                io_weight: None,
189            },
190            cpu_affinity: None,
191        },
192        payload: Payload::Virtual,
193        supervision: Supervision::OneShot,
194        genesis,
195        ..Default::default()
196    }
197}
198
199fn make_card(label: &str, payload: Payload, supervision: Supervision) -> EntityCard {
200    EntityCard {
201        schema_version: CARD_SCHEMA_VERSION,
202        id: Ulid::new(),
203        lineage: None,
204        label: label.into(),
205        provides: BTreeSet::new(),
206        requires: BTreeSet::new(),
207        soma: SomaSpec::default(),
208        payload,
209        supervision,
210        genesis: vec![],
211        ..Default::default()
212    }
213}
214
215fn optional_native_card(
216    label: &str,
217    bin_path: &str,
218    provides: BTreeSet<Capability>,
219    supervision: Supervision,
220) -> Option<EntityCard> {
221    let path = Path::new(bin_path);
222    if !path.exists() {
223        return None;
224    }
225    Some(EntityCard {
226        schema_version: CARD_SCHEMA_VERSION,
227        id: Ulid::new(),
228        lineage: None,
229        label: label.into(),
230        provides,
231        requires: BTreeSet::new(),
232        soma: SomaSpec::default(),
233        payload: Payload::Native {
234            exec: path.to_string_lossy().into_owned(),
235            argv: vec![],
236            envp: vec![],
237        },
238        supervision,
239        genesis: vec![],
240        ..Default::default()
241    })
242}
243
244fn restart_supervision() -> Supervision {
245    Supervision::Restart {
246        initial: Duration::from_millis(100),
247        max: Duration::from_secs(30),
248    }
249}