autopulse_service/settings/targets/
command.rs

1use crate::settings::rewrite::Rewrite;
2use crate::settings::targets::TargetProcess;
3use autopulse_database::models::ScanEvent;
4use serde::{Deserialize, Serialize};
5use tracing::{debug, error};
6
7/// Command target
8///
9/// Note: Either `path` or `raw` must be set but not both
10#[derive(Serialize, Clone, Deserialize)]
11pub struct Command {
12    /// Path to the command to run
13    ///
14    /// Example: `/path/to/script.sh`
15    pub path: Option<String>,
16    /// Timeout for the command in seconds (default: 10)
17    ///
18    /// Example: `5`
19    pub timeout: Option<u64>,
20    /// Raw command to run
21    ///
22    /// Example: `echo $FILE_PATH >> list.log`
23    pub raw: Option<String>,
24    /// Rewrite path for the file
25    pub rewrite: Option<Rewrite>,
26}
27
28impl Command {
29    pub async fn run(&self, ev: &ScanEvent) -> anyhow::Result<()> {
30        if self.path.is_some() && self.raw.is_some() {
31            return Err(anyhow::anyhow!("command cannot have both path and raw"));
32        }
33
34        let ev_path = ev.get_path(&self.rewrite);
35
36        if let Some(path) = self.path.clone() {
37            let output = tokio::process::Command::new(path.clone())
38                .arg(&ev_path)
39                .output();
40
41            let timeout = self.timeout.unwrap_or(10);
42
43            let output = tokio::time::timeout(std::time::Duration::from_secs(timeout), output)
44                .await
45                .map_err(|_| anyhow::anyhow!("command timed out"))??;
46
47            if !output.status.success() {
48                return Err(anyhow::anyhow!(
49                    "command failed with status: {}",
50                    output.status
51                ));
52            }
53
54            debug!("command output: {:?}", output);
55        }
56
57        if let Some(raw) = self.raw.clone() {
58            let output = tokio::process::Command::new("sh")
59                .env("FILE_PATH", &ev_path)
60                .arg("-c")
61                .arg(&raw)
62                .output();
63
64            let timeout = self.timeout.unwrap_or(10000);
65
66            let output = tokio::time::timeout(std::time::Duration::from_millis(timeout), output)
67                .await
68                .map_err(|_| anyhow::anyhow!("command timed out"))??;
69
70            if !output.status.success() {
71                return Err(anyhow::anyhow!(
72                    "command failed with status: {}",
73                    output.status
74                ));
75            }
76
77            debug!("command output: {:?}", output);
78        }
79
80        Ok(())
81    }
82}
83
84impl TargetProcess for Command {
85    async fn process(&self, evs: &[&ScanEvent]) -> anyhow::Result<Vec<String>> {
86        let mut succeeded = Vec::new();
87
88        for ev in evs {
89            if let Err(e) = self.run(ev).await {
90                error!("failed to process '{}': {}", ev.get_path(&self.rewrite), e);
91            } else {
92                succeeded.push(ev.id.clone());
93            }
94        }
95
96        Ok(succeeded)
97    }
98}