edera_check/checkers/preinstall/
system.rs1use crate::helpers::{
2 CheckGroup, CheckGroupCategory, CheckGroupResult, CheckResult,
3 CheckResultValue::{Errored, Failed, Passed},
4 host_executor::HostNamespaceExecutor,
5};
6
7use async_trait::async_trait;
8use futures::{FutureExt, future::join_all};
9use log::debug;
10use sysinfo::{Disks, System};
11
12const GROUP_IDENTIFIER: &str = "system";
13const NAME: &str = "System Checks";
14const MINIMUM_MEMORY: u64 = 4 * 1024 * 1024 * 1024; const MINIMUM_DISK: u64 = 20 * 1024 * 1024 * 1024; pub struct SystemChecks {
18 host_executor: HostNamespaceExecutor,
19}
20
21impl SystemChecks {
22 pub fn new(host_executor: HostNamespaceExecutor) -> Self {
23 SystemChecks { host_executor }
24 }
25 pub async fn run_all(&self) -> CheckGroupResult {
26 let results = join_all([
27 self.enough_memory().boxed(),
28 self.enough_disk().boxed(),
29 self.has_nft_bin().boxed(),
30 self.has_package_manager_bin().boxed(),
31 self.has_grub_mkconfig_bin().boxed(),
32 self.has_service_manager_bin().boxed(),
33 self.has_linux_util_bins(&["tar", "grep"]).boxed(),
34 ])
35 .await;
36
37 let mut group_result = Passed;
38 for res in results.iter() {
39 if !matches!(group_result, Errored(_)) && matches!(res.result, Failed(_)) {
41 group_result = Failed("".into());
42 }
43
44 if matches!(res.result, Errored(_)) {
45 group_result = Errored("".into());
46 }
47 }
48
49 CheckGroupResult {
50 name: NAME.to_string(),
51 result: group_result,
52 results,
53 }
54 }
55
56 pub async fn has_nft_bin(&self) -> CheckResult {
59 let name = String::from("'nft' Binary Present");
60 let found = self
61 .host_executor
62 .spawn_in_host_ns(async {
63 std::process::Command::new("nft")
64 .arg("--version")
65 .stdout(std::process::Stdio::null())
66 .stderr(std::process::Stdio::null())
67 .status()
68 .is_ok()
69 })
70 .await
71 .unwrap_or(false);
72
73 if found {
74 CheckResult::new(&name, Passed)
75 } else {
76 CheckResult::new(
77 &name,
78 Errored("'nft' binary is required but not present, install `nftables`".into()),
79 )
80 }
81 }
82
83 pub async fn has_linux_util_bins(&self, bins: &[&str]) -> CheckResult {
88 let name = String::from("Basic Linux Utility Binaries Present");
89 let owned_bins: Vec<String> = bins.iter().map(|s| s.to_string()).collect();
90
91 let missing = self
92 .host_executor
93 .spawn_in_host_ns(async move {
94 let mut missing = Vec::new();
95 for bin in &owned_bins {
96 let found = std::process::Command::new(bin)
97 .arg("--version")
98 .stdout(std::process::Stdio::null())
99 .stderr(std::process::Stdio::null())
100 .status()
101 .map(|s| s.success())
102 .unwrap_or(false);
103 if !found {
104 missing.push(bin.clone());
105 }
106 }
107 missing
108 })
109 .await
110 .unwrap_or_default();
111
112 if missing.is_empty() {
113 CheckResult::new(&name, Passed)
114 } else {
115 let msg = format!(
116 "required basic Linux utility binaries not found in PATH: {}",
117 missing.join(", ")
118 );
119 CheckResult::new(&name, Errored(msg))
120 }
121 }
122
123 pub async fn has_grub_mkconfig_bin(&self) -> CheckResult {
126 let name = String::from("'grub-mkconfig' Binary Present");
127 let found = self
128 .host_executor
129 .spawn_in_host_ns(async {
130 ["grub-mkconfig", "grub2-mkconfig"].iter().any(|bin| {
131 std::process::Command::new(bin)
132 .arg("--version")
133 .stdout(std::process::Stdio::null())
134 .stderr(std::process::Stdio::null())
135 .status()
136 .map(|s| s.success())
137 .unwrap_or(false)
138 })
139 })
140 .await
141 .unwrap_or(false);
142
143 if found {
144 CheckResult::new(&name, Passed)
145 } else {
146 CheckResult::new(
147 &name,
148 Errored("neither 'grub-mkconfig' nor 'grub2-mkconfig' found in PATH".into()),
149 )
150 }
151 }
152
153 pub async fn has_service_manager_bin(&self) -> CheckResult {
156 let name = String::from("Service Manager Binary Present");
157 let found = self
158 .host_executor
159 .spawn_in_host_ns(async {
160 ["systemctl", "rc-update"].iter().any(|bin| {
161 std::process::Command::new(bin)
162 .arg("--version")
163 .stdout(std::process::Stdio::null())
164 .stderr(std::process::Stdio::null())
165 .status()
166 .map(|s| s.success())
167 .unwrap_or(false)
168 })
169 })
170 .await
171 .unwrap_or(false);
172
173 if found {
174 CheckResult::new(&name, Passed)
175 } else {
176 CheckResult::new(
177 &name,
178 Errored("neither 'systemctl' nor 'rc-update' found in PATH".into()),
179 )
180 }
181 }
182
183 pub async fn has_package_manager_bin(&self) -> CheckResult {
186 let name = String::from("Package Manager Binary Present");
187 let found = self
188 .host_executor
189 .spawn_in_host_ns(async {
190 ["dnf", "yum", "zypper", "apt-get", "apk"]
191 .iter()
192 .any(|bin| {
193 std::process::Command::new(bin)
194 .arg("--version")
195 .stdout(std::process::Stdio::null())
196 .stderr(std::process::Stdio::null())
197 .status()
198 .map(|s| s.success())
199 .unwrap_or(false)
200 })
201 })
202 .await
203 .unwrap_or(false);
204
205 if found {
206 CheckResult::new(&name, Passed)
207 } else {
208 CheckResult::new(
209 &name,
210 Errored("no supported package manager found in PATH (tried: dnf, yum, zypper, apt-get, apk)".into()),
211 )
212 }
213 }
214
215 pub async fn enough_memory(&self) -> CheckResult {
222 let name = String::from("Enough Memory");
223
224 let total_mem = match self
225 .host_executor
226 .spawn_in_host_ns(async {
227 let mut sys = System::new_all();
228 sys.refresh_all();
229
230 sys.total_memory()
231 })
232 .await
233 {
234 Ok(mem) => mem,
235 Err(e) => {
236 return CheckResult::new(&name, Errored(e.to_string()));
237 }
238 };
239
240 debug!("total memory = {total_mem}");
241
242 let mut result = Passed;
243 if total_mem < MINIMUM_MEMORY {
244 let reason = format!("total memory is less than {}", MINIMUM_MEMORY);
245 result = Failed(reason);
246 }
247 CheckResult::new(&name, result)
248 }
249
250 pub async fn enough_disk(&self) -> CheckResult {
257 let name = String::from("Enough Disk");
258
259 let result = match self
260 .host_executor
261 .spawn_in_host_ns(async {
262 let mut result = Failed(String::from("Not enough disk space on any disk"));
263 let disks = Disks::new_with_refreshed_list();
264 for disk in &disks {
265 if disk.available_space() < MINIMUM_DISK {
266 debug!(
267 "Not enough space on disk mounted at {} - {}",
268 disk.mount_point().display(),
269 disk.available_space()
270 );
271 } else {
272 debug!(
273 "Enough space on disk mounted at {} - {}",
274 disk.mount_point().display(),
275 disk.available_space()
276 );
277 result = Passed;
278 }
279 }
280 result
281 })
282 .await
283 {
284 Ok(result) => result,
285 Err(e) => {
286 return CheckResult::new(&name, Errored(e.to_string()));
287 }
288 };
289
290 CheckResult::new(&name, result)
291 }
292}
293
294#[async_trait]
295impl CheckGroup for SystemChecks {
296 fn id(&self) -> &str {
297 GROUP_IDENTIFIER
298 }
299
300 fn name(&self) -> &str {
301 NAME
302 }
303
304 fn description(&self) -> &str {
305 "System requirement checks"
306 }
307
308 async fn run(&self) -> CheckGroupResult {
309 self.run_all().await
310 }
311
312 fn category(&self) -> CheckGroupCategory {
313 CheckGroupCategory::Required
314 }
315}