Skip to main content

sysd_manager_proxy_lib/
install.rs

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            //  cmd = ["flatpak", "run", APP_ID]
80
81            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    /*     #[cfg(feature = "flatpak")]
141    {
142        use base::file::inside_flatpak;
143
144        let stream = gio::functions::resources_open_stream(
145            &src_path.to_string_lossy(),
146            ResourceLookupFlags::NONE,
147        )?;
148
149        let path = PathBuf::from(format!("XXXXXX{}", POLICY_FILE));
150        let (file, ios_stream) = gio::File::new_tmp(Some(&path)).unwrap();
151
152        let mut tmp_path = file.path().ok_or(Box::<dyn Error>::from("No file path"))?;
153        info!("temp file path {:?}", tmp_path);
154
155        let os_strem = ios_stream.output_stream();
156        os_strem
157            .splice(
158                &stream,
159                OutputStreamSpliceFlags::NONE,
160                None::<&gio::Cancellable>,
161            )
162            .unwrap();
163
164        /*         /run/user/1000/.flatpak/io.github.plrigaux.sysd-manager/tmp
165        /run/user/USERID/.flatpak/FLATPAK_ID/tmp/ */
166
167        if inside_flatpak()
168            && let Ok(run_time_dir) = env::var("XDG_RUNTIME_DIR")
169            && let Ok(flatpak_id) = env::var("FLATPAK_ID")
170        {
171            tmp_path = PathBuf::from_iter(args![
172                run_time_dir,
173                ".flatpak",
174                flatpak_id,
175                tmp_path.strip_prefix("/").expect("tmp_path not empty")
176            ]);
177            debug!("flatpack tmp dir {}", tmp_path.display());
178        }
179
180        Ok(tmp_path)
181    } */
182
183    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    //let mut command = commander(args!(sudo(), "sed", "-i"), None);
196
197    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        // command.args(args!("-e", );
204    }
205    s.push(dst.to_string_lossy().to_string());
206
207    //command.arg(dst);
208
209    content.push_str(&s.join(" "));
210    content.push('\n');
211    /*  let output = command.output().await?;
212
213    ouput_to_screen(output); */
214    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    //  map: Option<&BTreeMap<&str, &str>>,
236    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 mut command = commander(
248           args!(
249               sudo(),
250               "install",
251               format!("-vDm{}", mode),
252               src,
253               dir_arg,
254               dst
255           ),
256           None,
257       );
258    */
259    let s = [
260        //     sudo(),
261        "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    /*     if let Some(map) = map {
273        command.args(["&&", "sed", "-i"]);
274        for (k, v) in map {
275            command.args(args!("-e", format!("s/{{{k}}}/{}/", v.replace("/", r"\/"))));
276        }
277        command.arg(dst);
278    }
279
280    let output = command.output().await?;
281    ouput_to_screen(output); */
282    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    //TODO: use run_mode to clean only relevant files
323    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}