Skip to main content

regent_sdk/hosts/
properties.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5use crate::hosts::handlers::HostHandler;
6
7use crate::{Privilege, RegentError};
8
9#[derive(Clone, Serialize, Deserialize, Debug)]
10#[serde(deny_unknown_fields)]
11pub struct HostProperties {
12    os_kind: OsKind,
13    hostname: Option<String>,
14}
15
16impl HostProperties {
17    pub async fn collect_dynamically<Handler: HostHandler>(
18        host_handler: &mut Handler,
19    ) -> Result<HostProperties, RegentError> {
20        if !host_handler.is_connected().await {
21            return Err(RegentError::NotConnectedToHost);
22        }
23
24        let mut os_kind = OsKind::Unknown;
25
26        // Linux & FreeBSD -> try to get file /etc/os-release
27        if let Ok(os_release_file_content) = host_handler
28            .get_file(PathBuf::from("/etc/os-release"))
29            .await
30        {
31            let content = String::from_utf8_lossy(&os_release_file_content);
32            for line in content.lines() {
33                if line.starts_with("NAME=") {
34                    // Some distributions are using quotes, others are not...
35                    let mut equals_iterator = line.split('=');
36                    equals_iterator.next().unwrap(); // 'NAME'
37                    let os_name_part = equals_iterator.next().unwrap();
38
39                    let os_name = if os_name_part.starts_with('"') {
40                        &os_name_part[1..os_name_part.len() - 1]
41                    } else {
42                        os_name_part
43                    };
44
45                    if os_name.contains("FreeBSD") {
46                        os_kind = OsKind::FreeBsd(FreeBsdSpecifics);
47                    } else {
48                        let linux_flavor = if os_name.contains("Arch") {
49                            LinuxFlavor::Arch
50                        } else if os_name.contains("CentOS") {
51                            LinuxFlavor::Fedora
52                        } else if os_name.contains("Debian") {
53                            LinuxFlavor::Debian
54                        } else if os_name.contains("Ubuntu") {
55                            LinuxFlavor::Debian
56                        } else if os_name.contains("Arch") {
57                            LinuxFlavor::Arch
58                        } else if os_name.contains("openSUSE") {
59                            LinuxFlavor::Suse
60                        } else {
61                            // Unknown Linux flavor, but still Linux
62                            LinuxFlavor::Debian
63                        };
64
65                        let init_system = Self::detect_init_system(host_handler).await;
66                        os_kind = OsKind::Linux(LinuxSpecifics {
67                            linux_flavor,
68                            init_system,
69                        });
70                    }
71
72                    break;
73                }
74            }
75        }
76
77        // OsKind still unknown, trying to detect Windows -> run "systeminfo"
78        #[cfg(feature = "windows")]
79        if let OsKind::Unknown = os_kind {
80            if let Ok(cmd_result) = host_handler.run_windows_command("systeminfo").await {
81                if cmd_result.stdout.contains("Microsoft Windows") {
82                    os_kind = OsKind::Windows(WindowsSpecifics);
83                }
84            }
85        }
86
87        // OsKind still unknown, trying to detect MacOS -> run "sw_vers -productName"
88        if let OsKind::Unknown = os_kind {
89            if let Ok(cmd_result) = host_handler
90                .run_command("sw_vers -productName", &Privilege::None)
91                .await
92            {
93                if cmd_result.stdout.contains("macOS") {
94                    os_kind = OsKind::MacOs(MacOsSpecifics);
95                }
96            }
97        }
98
99        let hostname = Self::collect_hostname(host_handler, os_kind.clone()).await;
100
101        Ok(HostProperties { os_kind, hostname })
102    }
103
104    pub fn os_kind(&self) -> &OsKind {
105        &self.os_kind
106    }
107
108    pub fn hostname(&self) -> &Option<String> {
109        &self.hostname
110    }
111
112    async fn collect_hostname<Handler: HostHandler>(
113        host_handler: &mut Handler,
114        os_kind: OsKind,
115    ) -> Option<String> {
116        match os_kind {
117            OsKind::Unknown => None,
118            #[cfg(feature = "windows")]
119            OsKind::Windows(_) => {
120                let cmd_result = host_handler.run_windows_command("hostname").await.ok()?;
121                if cmd_result.return_code == 0 {
122                    Some(cmd_result.stdout.trim().to_string())
123                } else {
124                    None
125                }
126            }
127            OsKind::Linux(_) | OsKind::FreeBsd(_) | OsKind::MacOs(_) => {
128                if let Ok(hostname_file_content) =
129                    host_handler.get_file(PathBuf::from("/etc/hostname")).await
130                {
131                    Some(
132                        String::from_utf8_lossy(&hostname_file_content)
133                            .trim()
134                            .to_string(),
135                    )
136                } else {
137                    let cmd_result = host_handler
138                        .run_command("hostname", &Privilege::None)
139                        .await
140                        .ok()?;
141                    if cmd_result.return_code == 0 {
142                        Some(cmd_result.stdout.trim().to_string())
143                    } else {
144                        None
145                    }
146                }
147            }
148        }
149    }
150
151    async fn detect_init_system<Handler: HostHandler>(host_handler: &mut Handler) -> InitSystem {
152        // Check for systemd by looking for the /run/systemd/system directory
153        if host_handler
154            .get_file(PathBuf::from("/run/systemd/system"))
155            .await
156            .is_ok()
157        {
158            return InitSystem::Systemd;
159        }
160
161        // Alternative check: see if systemd is PID 1
162        if let Ok(cmd_result) = host_handler
163            .run_command("ps -p 1 -o comm=", &Privilege::None)
164            .await
165        {
166            if cmd_result.return_code == 0 {
167                let process_name = cmd_result.stdout.trim();
168                if process_name == "systemd" || process_name.contains("systemd") {
169                    return InitSystem::Systemd;
170                }
171            }
172        }
173
174        InitSystem::Unknown
175    }
176}
177
178#[derive(Clone, Serialize, Deserialize, Debug)]
179pub enum OsKind {
180    Unknown,
181    #[cfg(feature = "windows")]
182    Windows(WindowsSpecifics),
183    FreeBsd(FreeBsdSpecifics),
184    MacOs(MacOsSpecifics),
185    Linux(LinuxSpecifics),
186}
187
188#[derive(Clone, Serialize, Deserialize, Debug)]
189pub struct LinuxSpecifics {
190    pub linux_flavor: LinuxFlavor,
191    pub init_system: InitSystem,
192}
193
194#[derive(Clone, Serialize, Deserialize, Debug)]
195pub struct MacOsSpecifics;
196
197#[derive(Clone, Serialize, Deserialize, Debug)]
198pub struct FreeBsdSpecifics;
199
200#[derive(Clone, Serialize, Deserialize, Debug)]
201#[cfg(feature = "windows")]
202pub struct WindowsSpecifics;
203
204#[derive(Clone, Serialize, Deserialize, Debug)]
205pub enum LinuxFlavor {
206    Debian,
207    Fedora,
208    Arch,
209    Suse,
210    Gentoo,
211}
212
213#[derive(Clone, Serialize, Deserialize, Debug)]
214pub enum InitSystem {
215    Unknown,
216    Systemd,
217}