gioser

tawasuyu/brahman

Rust 570TOML 215Markdown 68JSON 198261 Sutichaykuna

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

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

1//! Absorbe **runit**: descubre los servicios bajo supervisión.
2
3use std::fs;
4use std::path::Path;
5
6use crate::model::{ForeignService, ServiceKind};
7
8/// Descubre los servicios runit de `<root>`.
9///
10/// runit supervisa un directorio de servicios activos (el `runsvdir`
11/// del runlevel). Cada entrada apunta a `/etc/sv/<nombre>`, cuyo script
12/// `run` es un daemon en primer plano — el calce con arje es 1:1: arje
13/// supervisa ese mismo `run`. Si no hay `runsvdir`, cae a `/etc/sv`
14/// (todos los servicios definidos).
15pub fn absorb(root: &Path) -> anyhow::Result<Vec<ForeignService>> {
16    let runsvdir = [
17        "etc/runit/runsvdir/default",
18        "etc/runit/runsvdir/current",
19        "service",
20        "var/service",
21        "etc/service",
22    ]
23    .into_iter()
24    .map(|c| root.join(c))
25    .find(|p| p.is_dir());
26
27    let scan = match runsvdir {
28        Some(d) => d,
29        None => {
30            let sv = root.join("etc/sv");
31            anyhow::ensure!(
32                sv.is_dir(),
33                "no encontré servicios runit en {}",
34                root.display()
35            );
36            sv
37        }
38    };
39
40    let mut names: Vec<String> = fs::read_dir(&scan)?
41        .flatten()
42        .map(|e| e.file_name().to_string_lossy().into_owned())
43        .filter(|n| !n.starts_with('.'))
44        .collect();
45    names.sort();
46
47    let mut out = Vec::new();
48    for name in names {
49        // El servicio vive en /etc/sv/<name>; su run-script es el exec.
50        let run = root.join("etc/sv").join(&name).join("run");
51        if !run.exists() {
52            continue; // entrada sin run-script real — la saltamos
53        }
54        out.push(ForeignService {
55            name: format!("runit-{name}"),
56            exec: format!("/etc/sv/{name}/run"),
57            argv: Vec::new(),
58            env: Vec::new(),
59            kind: ServiceKind::Daemon, // runit siempre supervisa
60        });
61    }
62    Ok(out)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    fn make_sv(root: &Path, name: &str) {
70        let d = root.join("etc/sv").join(name);
71        fs::create_dir_all(&d).unwrap();
72        fs::write(d.join("run"), "#!/bin/sh\nexec daemon\n").unwrap();
73    }
74
75    #[test]
76    fn absorbs_from_etc_sv_fallback() {
77        let tmp = tempfile::tempdir().unwrap();
78        make_sv(tmp.path(), "sshd");
79        make_sv(tmp.path(), "dhcpcd");
80        let svcs = absorb(tmp.path()).unwrap();
81        assert_eq!(svcs.len(), 2);
82        // Salida ordenada por nombre.
83        assert_eq!(svcs[0].name, "runit-dhcpcd");
84        assert_eq!(svcs[0].exec, "/etc/sv/dhcpcd/run");
85        assert_eq!(svcs[0].kind, ServiceKind::Daemon);
86    }
87
88    #[test]
89    fn absorbs_only_enabled_from_runsvdir() {
90        let tmp = tempfile::tempdir().unwrap();
91        for s in ["sshd", "dhcpcd", "apagado"] {
92            make_sv(tmp.path(), s);
93        }
94        let rsv = tmp.path().join("etc/runit/runsvdir/default");
95        fs::create_dir_all(&rsv).unwrap();
96        // Sólo sshd y dhcpcd están habilitados (symlink en el runsvdir).
97        for s in ["sshd", "dhcpcd"] {
98            std::os::unix::fs::symlink(tmp.path().join("etc/sv").join(s), rsv.join(s))
99                .unwrap();
100        }
101        let svcs = absorb(tmp.path()).unwrap();
102        assert_eq!(svcs.len(), 2);
103        assert!(svcs.iter().all(|s| s.name != "runit-apagado"));
104    }
105
106    #[test]
107    fn errors_without_services() {
108        let tmp = tempfile::tempdir().unwrap();
109        assert!(absorb(tmp.path()).is_err());
110    }
111}