1use std::{
2 collections::BTreeMap,
3 env,
4 error::Error,
5 ffi::OsStr,
6 path::{Path, PathBuf},
7};
8
9use base::{
10 RunMode, args,
11 consts::*,
12 file::{commander, flatpak_host_file_path},
13};
14
15use tokio::{fs, process::Command};
16use tracing::{debug, error, info, warn};
17
18const SYSTEMD_DIR: &str = "/usr/share/dbus-1/system.d";
19const ACTION_DIR: &str = "/usr/share/polkit-1/actions";
20const SERVICE_DIR: &str = "/usr/lib/systemd/system";
21const POLICY_FILE: &str = "io.github.plrigaux.SysDManager.policy";
22const SERVICE_FILE: &str = "sysd-manager-proxy.service";
23const DBUSCONF_FILE: &str = "io.github.plrigaux.SysDManager.conf";
24
25pub async fn install(run_mode: RunMode) -> Result<(), Box<dyn Error>> {
26 info!("Install proxy mode {:?}", run_mode);
27
28 if run_mode == RunMode::Both {
29 self::sub_install(RunMode::Development).await?;
30 self::sub_install(RunMode::Normal).await?;
31 } else {
32 self::sub_install(run_mode).await?;
33 }
34 Ok(())
35}
36
37async fn sub_install(run_mode: RunMode) -> Result<(), Box<dyn Error>> {
38 for (key, value) in std::env::vars() {
39 println!("{}: {}", key, value);
40 }
41
42 if run_mode == RunMode::Both {
43 error!("sub_install should not be called with RunMode::Both");
44 return Err("Invalid RunMode::Both for sub_install".into());
45 }
46 let path = env::current_dir()?;
47 info!("The current directory is {}", path.display());
48
49 let mut normalized_path = PathBuf::new();
50 for token in path.iter() {
51 normalized_path.push(token);
52 if token == "sysd-manager" {
53 break;
54 } else {
55 debug!("{:?}", token)
56 }
57 }
58
59 info!("The base directory is {}", normalized_path.display());
60
61 let (interface, destination) = if run_mode == RunMode::Development {
62 (DBUS_INTERFACE, DBUS_DESTINATION_DEV)
63 } else {
64 (DBUS_INTERFACE, DBUS_DESTINATION)
65 };
66
67 let mut base_path = normalized_path.join("sysd-manager-proxy");
68 base_path.push("data");
69
70 let mut map = BTreeMap::new();
71
72 map.insert("BUS_NAME", run_mode.bus_name());
73 map.insert("DESTINATION", destination);
74 map.insert("INTERFACE", interface);
75 map.insert("ENVIRONMENT", "");
76
77 let exec = match run_mode {
78 RunMode::Normal => {
79 const BIN_DIR: &str = "/usr/bin";
82 const BIN_NAME: &str = "sysd-manager-proxy";
83 String::from_iter([BIN_DIR, "/", BIN_NAME])
84 }
85 RunMode::Development => {
86 let exec = std::env::current_exe().expect("supposed to exist");
87 let exec = exec.to_string_lossy();
88
89 format!("{} -d", exec)
90 }
91 _ => {
92 return Err("Invalid RunMode::Both for sub_install".into());
93 }
94 };
95
96 let src = source_path(&base_path, DBUSCONF_FILE)?;
97 let mut dst = PathBuf::from_iter(args!(
98 flatpak_host_file_path(SYSTEMD_DIR),
99 run_mode.bus_name()
100 ));
101 dst.add_extension("conf");
102
103 let mut content = String::new();
104 install_file(&src, &dst, false, &mut content).await?;
105 install_edit_file(&map, dst, &mut content).await?;
106
107 info!("Installing Polkit Policy");
108 let src = source_path(&base_path, POLICY_FILE)?;
109 let dst = flatpak_host_file_path(ACTION_DIR);
110 install_file(&src, &dst, true, &mut content).await?;
111
112 info!("Installing Service");
113
114 let src = source_path(&base_path, SERVICE_FILE)?;
115 let service_file_path = PathBuf::from_iter(args![
116 flatpak_host_file_path(SERVICE_DIR),
117 run_mode.proxy_service_name()
118 ]);
119
120 map.insert("EXECUTABLE", &exec);
121 map.insert("SERVICE_ID", run_mode.proxy_service_id());
122 install_file(&src, &service_file_path, false, &mut content).await?;
123 install_edit_file(&map, service_file_path, &mut content).await?;
124
125 let script_file = create_script(&content).await?;
126
127 content.push_str("echo End of script");
128
129 let output = commander(args!(sudo(), "sh", script_file), None)
130 .output()
131 .await?;
132
133 ouput_to_screen(output);
134 Ok(())
135}
136
137fn source_path(base_path: &Path, file_name: &str) -> Result<PathBuf, Box<dyn Error>> {
138 let src_path = base_path.join(file_name);
139
140 Ok(src_path)
184}
185
186async fn install_edit_file(
187 map: &BTreeMap<&str, &str>,
188 dst: PathBuf,
189 content: &mut String,
190) -> Result<(), Box<dyn Error + 'static>> {
191 info!("Edit file -- {}", dst.display());
192
193 let mut s = vec!["sed".to_string(), "-i".to_string()];
194
195 for (k, v) in map {
198 s.push("-e".to_string());
199 s.push(format!(
200 "s/{{{k}}}/{}/",
201 v.replace("/", r"\\/").replace(" ", r"\ ")
202 ));
203 }
205 s.push(dst.to_string_lossy().to_string());
206
207 content.push_str(&s.join(" "));
210 content.push('\n');
211 Ok(())
215}
216
217async fn install_file(
218 src: &Path,
219 dst: &Path,
220 dst_is_dir: bool,
221 content: &mut String,
222) -> Result<(), Box<dyn Error + 'static>> {
223 install_file_mode(src, dst, dst_is_dir, "644", content).await
224}
225
226fn sudo() -> &'static str {
227 "sudo"
228}
229
230async fn install_file_mode(
231 src: &Path,
232 dst: &Path,
233 dst_is_dir: bool,
234 mode: &str,
235 content: &mut String,
237) -> Result<(), Box<dyn Error + 'static>> {
238 info!(
239 "Installing {} --> {} with mode {}",
240 src.display(),
241 dst.display(),
242 mode
243 );
244
245 let dir_arg = if dst_is_dir { "-t" } else { "-T" };
246
247 let s = [
260 "install",
262 &format!("-vDm{}", mode),
263 &src.to_string_lossy(),
264 dir_arg,
265 &dst.to_string_lossy(),
266 ]
267 .join(" ");
268
269 content.push_str(&s);
270 content.push('\n');
271
272 Ok(())
283}
284
285enum Pattern {
286 Equals(String),
287 Start(String),
288}
289
290struct Clean {
291 dir: String,
292 patterns: Vec<Pattern>,
293}
294
295pub async fn clean(_run_mode: RunMode) -> Result<(), Box<dyn Error>> {
296 info!("Clean proxy files");
297
298 let mut to_clean = Vec::new();
299 let clean = Clean {
300 dir: SYSTEMD_DIR.to_string(),
301 patterns: vec![Pattern::Start("io.github.plrigaux.SysDM".to_string())],
302 };
303
304 to_clean.push(clean);
305
306 let clean = Clean {
307 dir: ACTION_DIR.to_string(),
308 patterns: vec![Pattern::Equals(
309 "io.github.plrigaux.SysDManager.policy".to_string(),
310 )],
311 };
312
313 to_clean.push(clean);
314
315 let clean = Clean {
316 dir: SERVICE_DIR.to_string(),
317 patterns: vec![Pattern::Start("sysd-manager-proxy".to_string())],
318 };
319 to_clean.push(clean);
320
321 let mut paths_to_clean = Vec::new();
322 for clean in to_clean {
324 let mut entries = fs::read_dir(clean.dir).await?;
325 while let Some(entry) = entries.next_entry().await? {
326 let path = entry.path();
327
328 for pattern in &clean.patterns {
329 match pattern {
330 Pattern::Equals(s) => {
331 if let Some(file_name) = path.file_name() {
332 let fname = file_name.to_string_lossy();
333 if fname == *s {
334 paths_to_clean.push(path.clone());
335 }
336 }
337 }
338 Pattern::Start(s) => {
339 if let Some(file_name) = path.file_name() {
340 let fname = file_name.to_string_lossy();
341 if fname.starts_with(s) {
342 paths_to_clean.push(path.clone());
343 }
344 }
345 }
346 }
347 }
348 }
349 }
350
351 info!("{} file to clean", paths_to_clean.len());
352 for path in paths_to_clean {
353 let output = Command::new("sudo")
354 .arg("rm")
355 .arg("-v")
356 .arg(path)
357 .output()
358 .await?;
359
360 ouput_to_screen(output);
361 }
362 Ok(())
363}
364
365use tokio::io::AsyncWriteExt;
366async fn create_script(content: &str) -> Result<PathBuf, std::io::Error> {
367 let mut file_path = env::temp_dir();
368
369 file_path.push("sysd-manager-install.sh");
370
371 let mut file = fs::OpenOptions::new()
372 .write(true)
373 .truncate(true)
374 .create(true)
375 .open(&file_path)
376 .await?;
377
378 file.write_all(b"#!/bin/bash\n\n").await?;
379
380 file.write_all(content.as_bytes()).await?;
381
382 info!("Script created to {}", file_path.display());
383
384 Ok(file_path)
385}
386
387fn ouput_to_screen(output: std::process::Output) {
388 if output.status.success() {
389 for l in String::from_utf8_lossy(&output.stdout).lines() {
390 info!("{l}");
391 }
392 } else {
393 warn!("Exit code {:?}", output.status.code());
394 for line in String::from_utf8_lossy(&output.stderr).lines() {
395 warn!("{line}");
396 }
397 }
398}
399
400#[cfg(test)]
401mod test {
402
403 #[test]
404 fn test_string() {
405 let k = "A";
406 let v = "B";
407 let x = format!(r#"s/{{{k}}}/{}/"#, v.replace("/", r"\/"));
408
409 assert_eq!(x, "s/{A}/B/")
410 }
411}