gioser

tawasuyu/brahman

Rust 570TOML 215Markdown 68JSON 198261 Definiciones

⎇ 762bf95dfd770cc8bf51f21605bc2cc6cc88ea76 crates/init/arje-absorb/src/openrc.rs Rust · 86 líneas · 2.9 KB Historia Quién tocó qué crudo

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

1//! Absorbe **OpenRC**: descubre los servicios habilitados por runlevel.
2
3use std::collections::BTreeSet;
4use std::fs;
5use std::path::Path;
6
7use crate::model::{ForeignService, ServiceKind};
8
9/// Descubre los servicios OpenRC habilitados en `<root>`.
10///
11/// OpenRC habilita un servicio con un symlink en `/etc/runlevels/<rl>/`.
12/// Los scripts de `/etc/init.d/` son shell completo —no se parsean—; se
13/// absorben como tarea one-shot `/etc/init.d/<svc> start`, que arranca
14/// el daemon (OpenRC mismo lo lleva a segundo plano). Recorremos los
15/// runlevels `sysinit` → `boot` → `default` y deduplicamos.
16pub fn absorb(root: &Path) -> anyhow::Result<Vec<ForeignService>> {
17    let runlevels = ["sysinit", "boot", "default"];
18    let mut found_any = false;
19    let mut seen: BTreeSet<String> = BTreeSet::new();
20    let mut out = Vec::new();
21    for rl in runlevels {
22        let dir = root.join("etc/runlevels").join(rl);
23        if !dir.is_dir() {
24            continue;
25        }
26        found_any = true;
27        let mut names: Vec<String> = fs::read_dir(&dir)?
28            .flatten()
29            .map(|e| e.file_name().to_string_lossy().into_owned())
30            .filter(|n| !n.starts_with('.'))
31            .collect();
32        names.sort();
33        for svc in names {
34            if !seen.insert(svc.clone()) {
35                continue; // ya absorbido en un runlevel anterior
36            }
37            out.push(ForeignService {
38                name: format!("openrc-{svc}"),
39                exec: format!("/etc/init.d/{svc}"),
40                argv: vec!["start".to_string()],
41                env: Vec::new(),
42                kind: ServiceKind::OneShot,
43            });
44        }
45    }
46    anyhow::ensure!(
47        found_any,
48        "no encontré /etc/runlevels/* en {}",
49        root.display()
50    );
51    Ok(out)
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn absorbs_enabled_services_deduped() {
60        let tmp = tempfile::tempdir().unwrap();
61        for (rl, svcs) in [
62            ("boot", ["bootmisc", "hostname"].as_slice()),
63            ("default", ["sshd", "hostname"].as_slice()),
64        ] {
65            let d = tmp.path().join("etc/runlevels").join(rl);
66            fs::create_dir_all(&d).unwrap();
67            for s in svcs {
68                // Un archivo simple basta: sólo leemos los nombres.
69                fs::write(d.join(s), "").unwrap();
70            }
71        }
72        let out = absorb(tmp.path()).unwrap();
73        // bootmisc, hostname, sshd — `hostname` deduplicado entre runlevels.
74        assert_eq!(out.len(), 3);
75        let sshd = out.iter().find(|s| s.name == "openrc-sshd").unwrap();
76        assert_eq!(sshd.exec, "/etc/init.d/sshd");
77        assert_eq!(sshd.argv, ["start"]);
78        assert_eq!(sshd.kind, ServiceKind::OneShot);
79    }
80
81    #[test]
82    fn errors_without_runlevels() {
83        let tmp = tempfile::tempdir().unwrap();
84        assert!(absorb(tmp.path()).is_err());
85    }
86}