gioser

tawasuyu/brahman

Rust 570TOML 215Markdown 68JSON 198261 Sutichaykuna

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

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

1//! `arje-absorb` — traduce la configuración de otro init a una Semilla.
2//!
3//! Lee la configuración de un init clásico (sysvinit, runit, dinit,
4//! OpenRC) y emite una Tarjeta Semilla brahman (`Card` JSON) con cada
5//! servicio como hija `genesis` de `arje-zero`. Es el paso «absorber»
6//! de la migración: `scripts/migrate-to-arje.sh` lo usa para no perder
7//! los servicios del sistema al cambiar de init.
8//!
9//! La Semilla resultante NO endurece el sandbox — conserva el
10//! comportamiento del init viejo. Revisala antes de instalarla.
11
12use std::path::{Path, PathBuf};
13use std::process::exit;
14
15mod card;
16mod dinit;
17mod model;
18mod openrc;
19mod runit;
20mod sysvinit;
21
22const HELP: &str = "\
23arje-absorb — absorbe la configuración de otro init en una Semilla brahman.
24
25USO:
26    arje-absorb [--from <init>] [--root <dir>] [--output <archivo>] [--label <s>]
27
28OPCIONES:
29    --from <init>    sysvinit | runit | dinit | openrc | auto   (def: auto)
30    --root <dir>     raíz del sistema a leer                    (def: /)
31    --output <f>     archivo de salida, o '-' para stdout       (def: -)
32    --label <s>      label de la Semilla raíz       (def: arje.seed.absorbed)
33    --with-carmen    agrega carmen-dm (gestor de login gráfico) a la Semilla
34    -h, --help       esta ayuda
35
36Emite una Tarjeta Semilla con cada servicio del init ajeno como hija
37genesis de arje-zero. Revisala antes de instalarla como
38/ente/seed.card.json.";
39
40fn main() {
41    if let Err(e) = run() {
42        eprintln!("arje-absorb: error: {e:#}");
43        exit(1);
44    }
45}
46
47fn run() -> anyhow::Result<()> {
48    let mut from = "auto".to_string();
49    let mut root = PathBuf::from("/");
50    let mut output = "-".to_string();
51    let mut label = "arje.seed.absorbed".to_string();
52    let mut with_carmen = false;
53
54    let mut args = std::env::args().skip(1);
55    while let Some(arg) = args.next() {
56        match arg.as_str() {
57            "-h" | "--help" => {
58                println!("{HELP}");
59                return Ok(());
60            }
61            "--from" => {
62                from = args.next().ok_or_else(|| anyhow::anyhow!("--from necesita un valor"))?
63            }
64            "--root" => {
65                root = PathBuf::from(
66                    args.next().ok_or_else(|| anyhow::anyhow!("--root necesita un valor"))?,
67                )
68            }
69            "--output" => {
70                output =
71                    args.next().ok_or_else(|| anyhow::anyhow!("--output necesita un valor"))?
72            }
73            "--label" => {
74                label =
75                    args.next().ok_or_else(|| anyhow::anyhow!("--label necesita un valor"))?
76            }
77            "--with-carmen" => with_carmen = true,
78            other => anyhow::bail!("opción desconocida «{other}» (usá --help)"),
79        }
80    }
81
82    let init = if from == "auto" {
83        detect(&root).ok_or_else(|| {
84            anyhow::anyhow!(
85                "no pude autodetectar el init en {} — pasá --from <init>",
86                root.display()
87            )
88        })?
89    } else {
90        from.clone()
91    };
92
93    let services = match init.as_str() {
94        "sysvinit" => sysvinit::absorb(&root)?,
95        "runit" => runit::absorb(&root)?,
96        "dinit" => dinit::absorb(&root)?,
97        "openrc" => openrc::absorb(&root)?,
98        other => anyhow::bail!(
99            "init «{other}» no soportado (sysvinit | runit | dinit | openrc | auto)"
100        ),
101    };
102
103    eprintln!(
104        "arje-absorb: init «{init}» → {} servicio(s) absorbido(s).",
105        services.len()
106    );
107    if services.is_empty() {
108        eprintln!("arje-absorb: aviso: 0 servicios — la Semilla quedará sin hijas.");
109    }
110
111    let mut seed = card::build_seed(&label, &services);
112    if with_carmen {
113        seed.genesis.push(card::carmen_dm_card());
114        eprintln!("arje-absorb: agregado carmen-dm (gestor de login gráfico).");
115    }
116    seed.validate()
117        .map_err(|e| anyhow::anyhow!("la Semilla generada no valida: {e}"))?;
118    let json = seed.to_json_pretty()?;
119
120    if output == "-" {
121        println!("{json}");
122    } else {
123        std::fs::write(&output, json)
124            .map_err(|e| anyhow::anyhow!("escribiendo {output}: {e}"))?;
125        eprintln!("arje-absorb: Semilla escrita en {output}");
126    }
127    Ok(())
128}
129
130/// Autodetecta el init presente en `root`. El orden importa: lo más
131/// específico primero — un sistema OpenRC suele llevar también un
132/// `/etc/inittab` (para las consolas), así que sysvinit va último.
133fn detect(root: &Path) -> Option<String> {
134    if root.join("etc/dinit.d").is_dir() {
135        return Some("dinit".to_string());
136    }
137    if root.join("etc/runlevels").is_dir() || root.join("sbin/openrc").exists() {
138        return Some("openrc".to_string());
139    }
140    if root.join("etc/runit").is_dir() || root.join("etc/sv").is_dir() {
141        return Some("runit".to_string());
142    }
143    if root.join("etc/inittab").exists() {
144        return Some("sysvinit".to_string());
145    }
146    None
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn detect_prefers_dinit() {
155        let tmp = tempfile::tempdir().unwrap();
156        std::fs::create_dir_all(tmp.path().join("etc/dinit.d")).unwrap();
157        std::fs::create_dir_all(tmp.path().join("etc/runlevels")).unwrap();
158        assert_eq!(detect(tmp.path()).as_deref(), Some("dinit"));
159    }
160
161    #[test]
162    fn detect_openrc_over_sysvinit() {
163        let tmp = tempfile::tempdir().unwrap();
164        std::fs::create_dir_all(tmp.path().join("etc/runlevels/default")).unwrap();
165        std::fs::write(tmp.path().join("etc/inittab"), "").unwrap();
166        assert_eq!(detect(tmp.path()).as_deref(), Some("openrc"));
167    }
168
169    #[test]
170    fn detect_none_on_empty_root() {
171        let tmp = tempfile::tempdir().unwrap();
172        assert!(detect(tmp.path()).is_none());
173    }
174}