Skip to main content

hematite/agent/
scheduler.rs

1const TASK_NAME: &str = "Hematite Health Check";
2
3pub fn register_scheduled_task(cadence: &str, exe_path: &str) -> Result<String, String> {
4    #[cfg(not(target_os = "windows"))]
5    {
6        let _ = (cadence, exe_path);
7        return Err("Scheduled tasks require Windows (schtasks.exe).\n\
8             On Linux/macOS use cron instead:\n\
9               hematite --triage --report-format html"
10            .into());
11    }
12
13    #[cfg(target_os = "windows")]
14    {
15        let task_run = format!("\"{}\" --triage --report-format html", exe_path);
16
17        let (schedule_type, extra_args, label): (&str, &[&str], &str) = match cadence {
18            "daily" => ("daily", &[], "daily at 08:00"),
19            _ => ("weekly", &["/d", "MON"], "weekly on Monday at 08:00"),
20        };
21
22        let mut args: Vec<String> = vec![
23            "/create".into(),
24            "/tn".into(),
25            TASK_NAME.into(),
26            "/tr".into(),
27            task_run.clone(),
28            "/sc".into(),
29            schedule_type.into(),
30            "/st".into(),
31            "08:00".into(),
32        ];
33        for a in extra_args {
34            args.push(a.to_string());
35        }
36        args.push("/f".into());
37
38        let out = std::process::Command::new("schtasks")
39            .args(&args)
40            .output()
41            .map_err(|e| format!("Failed to run schtasks: {}", e))?;
42
43        if out.status.success() {
44            let reports_dir = crate::tools::file_ops::hematite_dir().join("reports");
45            Ok(format!(
46                "Task \"{}\" registered — runs {}.\n\
47                 Action: {}\n\
48                 Reports will save to: {}\n\
49                 Run `hematite --schedule status` to confirm.",
50                TASK_NAME,
51                label,
52                task_run,
53                reports_dir.display()
54            ))
55        } else {
56            let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string();
57            let stdout = String::from_utf8_lossy(&out.stdout).trim().to_string();
58            Err(if !stderr.is_empty() { stderr } else { stdout })
59        }
60    }
61}
62
63pub fn remove_scheduled_task() -> Result<String, String> {
64    #[cfg(not(target_os = "windows"))]
65    return Err("Scheduled tasks require Windows.".into());
66
67    #[cfg(target_os = "windows")]
68    {
69        let out = std::process::Command::new("schtasks")
70            .args(["/delete", "/tn", TASK_NAME, "/f"])
71            .output()
72            .map_err(|e| format!("Failed to run schtasks: {}", e))?;
73
74        if out.status.success() {
75            Ok(format!("Task \"{}\" removed.", TASK_NAME))
76        } else {
77            let stderr = String::from_utf8_lossy(&out.stderr).trim().to_string();
78            Err(if !stderr.is_empty() {
79                stderr
80            } else {
81                format!("Task \"{}\" not found — nothing to remove.", TASK_NAME)
82            })
83        }
84    }
85}
86
87pub fn query_scheduled_task() -> String {
88    #[cfg(not(target_os = "windows"))]
89    return "Scheduled tasks are Windows-only. Use cron for recurring triage:\n\
90            hematite --triage --report-format html"
91        .to_string();
92
93    #[cfg(target_os = "windows")]
94    {
95        let out = std::process::Command::new("schtasks")
96            .args(["/query", "/tn", TASK_NAME, "/fo", "LIST"])
97            .output();
98
99        match out {
100            Ok(o) if o.status.success() => String::from_utf8_lossy(&o.stdout).trim().to_string(),
101            Ok(o) => {
102                let stderr = String::from_utf8_lossy(&o.stderr).to_ascii_lowercase();
103                if stderr.contains("cannot find") || stderr.contains("does not exist") {
104                    format!("Task \"{}\" is not registered.", TASK_NAME)
105                } else {
106                    format!(
107                        "Not registered: {}",
108                        String::from_utf8_lossy(&o.stderr).trim()
109                    )
110                }
111            }
112            Err(e) => format!("Error querying task: {}", e),
113        }
114    }
115}