serlib/
lib.rs

1pub mod platform;
2pub mod plist;
3pub mod systemd;
4
5use std::process::Command;
6use std::sync::atomic::{AtomicBool, Ordering};
7
8static VERBOSE: AtomicBool = AtomicBool::new(false);
9
10/// Set verbose mode. When enabled, all executed commands are printed to stderr.
11pub fn set_verbose(verbose: bool) {
12    VERBOSE.store(verbose, Ordering::SeqCst);
13}
14
15/// Print a command to stderr if verbose mode is enabled.
16pub fn print_command(cmd: &Command) {
17    if VERBOSE.load(Ordering::SeqCst) {
18        let program = cmd.get_program().to_string_lossy();
19        let args: Vec<_> = cmd.get_args().map(|a| a.to_string_lossy()).collect();
20        eprintln!("+ {} {}", program, args.join(" "));
21    }
22}
23
24/// Represents a calendar-based schedule for running services.
25/// Fields are optional - None means "any" (like * in cron).
26#[derive(Debug, Clone, Default)]
27pub struct CalendarSchedule {
28    /// Month (1-12)
29    pub month: Option<u8>,
30    /// Day of month (1-31)
31    pub day: Option<u8>,
32    /// Day of week (0=Sunday, 1=Monday, ..., 6=Saturday)
33    pub weekday: Option<u8>,
34    /// Hour (0-23)
35    pub hour: Option<u8>,
36    /// Minute (0-59)
37    pub minute: Option<u8>,
38}
39
40impl CalendarSchedule {
41    /// Convert to systemd OnCalendar format.
42    /// Examples: "*-*-* 03:00:00" (daily at 3am), "Mon *-*-* 00:00:00" (every Monday)
43    pub fn to_systemd_oncalendar(&self) -> String {
44        let weekday_str = match self.weekday {
45            Some(0) => "Sun ",
46            Some(1) => "Mon ",
47            Some(2) => "Tue ",
48            Some(3) => "Wed ",
49            Some(4) => "Thu ",
50            Some(5) => "Fri ",
51            Some(6) => "Sat ",
52            _ => "",
53        };
54
55        let month = self
56            .month
57            .map(|m| format!("{:02}", m))
58            .unwrap_or_else(|| "*".to_string());
59        let day = self
60            .day
61            .map(|d| format!("{:02}", d))
62            .unwrap_or_else(|| "*".to_string());
63        let hour = self
64            .hour
65            .map(|h| format!("{:02}", h))
66            .unwrap_or_else(|| "*".to_string());
67        let minute = self
68            .minute
69            .map(|m| format!("{:02}", m))
70            .unwrap_or_else(|| "00".to_string());
71
72        format!("{weekday_str}*-{month}-{day} {hour}:{minute}:00")
73    }
74
75    /// Convert to launchd StartCalendarInterval dictionary entries.
76    pub fn to_launchd_dict(&self) -> Vec<(String, i64)> {
77        let mut entries = Vec::new();
78        if let Some(month) = self.month {
79            entries.push(("Month".to_string(), month as i64));
80        }
81        if let Some(day) = self.day {
82            entries.push(("Day".to_string(), day as i64));
83        }
84        if let Some(weekday) = self.weekday {
85            entries.push(("Weekday".to_string(), weekday as i64));
86        }
87        if let Some(hour) = self.hour {
88            entries.push(("Hour".to_string(), hour as i64));
89        }
90        if let Some(minute) = self.minute {
91            entries.push(("Minute".to_string(), minute as i64));
92        }
93        entries
94    }
95
96    /// Format schedule for human-readable display.
97    pub fn display(&self) -> String {
98        let mut parts = Vec::new();
99
100        if let Some(weekday) = self.weekday {
101            let days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
102            if let Some(day) = days.get(weekday as usize) {
103                parts.push(day.to_string());
104            }
105        }
106
107        if let Some(day) = self.day {
108            parts.push(format!("day {}", day));
109        }
110
111        if let Some(hour) = self.hour {
112            let minute = self.minute.unwrap_or(0);
113            parts.push(format!("{:02}:{:02}", hour, minute));
114        }
115
116        if parts.is_empty() {
117            "scheduled".to_string()
118        } else {
119            parts.join(" ")
120        }
121    }
122}
123
124#[derive(Debug, Clone)]
125pub struct ServiceDetails {
126    pub name: String,
127    pub program: String,
128    pub arguments: Vec<String>,
129    pub working_directory: Option<String>,
130    pub run_at_load: bool,
131    pub keep_alive: bool,
132    pub env_file: Option<String>,
133    pub env_vars: Vec<(String, String)>,
134    pub after: Vec<String>,
135    pub schedule: Option<CalendarSchedule>,
136}
137
138#[derive(Debug, Clone)]
139pub struct FsServiceDetails {
140    pub service: ServiceDetails,
141    pub path: String,
142    pub enabled: bool,
143    pub running: bool,
144}