Skip to main content

weft_core/service_manager/
mod.rs

1use std::path::{Path, PathBuf};
2
3pub use platform::platform_service_manager;
4
5#[cfg(target_os = "windows")]
6mod platform {
7    pub use super::windows::WindowsServiceManager as PlatformImpl;
8    pub fn platform_service_manager() -> PlatformImpl {
9        PlatformImpl
10    }
11}
12
13#[cfg(target_os = "linux")]
14mod platform {
15    pub use super::linux::LinuxServiceManager as PlatformImpl;
16    pub fn platform_service_manager() -> PlatformImpl {
17        PlatformImpl
18    }
19}
20
21#[cfg(target_os = "macos")]
22mod platform {
23    pub use super::macos::MacosServiceManager as PlatformImpl;
24    pub fn platform_service_manager() -> PlatformImpl {
25        PlatformImpl
26    }
27}
28
29#[cfg(windows)]
30pub mod windows;
31#[cfg(target_os = "linux")]
32pub mod linux;
33#[cfg(target_os = "macos")]
34pub mod macos;
35
36/// Options resolved before calling install().
37#[derive(Debug, Clone)]
38pub struct ServiceInstallOptions {
39    /// Path to the weft-core binary that the service will execute.
40    /// Inplace mode: current executable path.
41    /// --path mode: destination after copy.
42    pub binary_path: PathBuf,
43    /// Directory containing config.toml.
44    pub config_dir: PathBuf,
45    /// Directory for runtime data (KV store, etc.).
46    pub data_dir: PathBuf,
47    /// OS service name (default: "weft-core").
48    pub service_name: String,
49}
50
51#[derive(Debug, Clone, serde::Serialize)]
52pub struct ServiceStatus {
53    pub installed: bool,
54    pub running: bool,
55    pub pid: Option<u32>,
56    pub binary_path: Option<PathBuf>,
57}
58
59pub trait PlatformServiceManager {
60    fn install(&self, opts: &ServiceInstallOptions) -> anyhow::Result<()>;
61    fn uninstall(&self, service_name: &str) -> anyhow::Result<()>;
62    fn start(&self, service_name: &str) -> anyhow::Result<()>;
63    fn stop(&self, service_name: &str) -> anyhow::Result<()>;
64    fn status(&self, service_name: &str) -> anyhow::Result<ServiceStatus>;
65}
66
67/// Parse CLI args into ServiceInstallOptions.
68/// Handles: --path, --config, --data, --service-name, --mode=system
69pub fn resolve_install_options(args: &[String]) -> anyhow::Result<ServiceInstallOptions> {
70    let current_exe = std::env::current_exe()
71        .map_err(|e| anyhow::anyhow!("cannot determine current executable: {}", e))?;
72
73    let mut dest_dir: Option<PathBuf> = None;
74    let mut config_dir: Option<PathBuf> = None;
75    let mut data_dir: Option<PathBuf> = None;
76    let mut service_name = "weft-core".to_string();
77    let mut mode_system = false;
78
79    let mut i = 0;
80    while i < args.len() {
81        match args[i].as_str() {
82            "--path" => {
83                dest_dir = Some(PathBuf::from(next_arg(args, i, "--path")?));
84                i += 2;
85            }
86            "--config" => {
87                config_dir = Some(PathBuf::from(next_arg(args, i, "--config")?));
88                i += 2;
89            }
90            "--data" => {
91                data_dir = Some(PathBuf::from(next_arg(args, i, "--data")?));
92                i += 2;
93            }
94            "--service-name" => {
95                service_name = next_arg(args, i, "--service-name")?.to_string();
96                i += 2;
97            }
98            "--mode=system" | "--mode" if args.get(i + 1).map(|s| s.as_str()) == Some("system") => {
99                mode_system = true;
100                i += if args[i] == "--mode" { 2 } else { 1 };
101            }
102            other => {
103                anyhow::bail!("unknown flag: {}", other);
104            }
105        }
106    }
107
108    if mode_system && dest_dir.is_none() {
109        dest_dir = Some(system_binary_dir());
110    }
111    if mode_system && config_dir.is_none() {
112        config_dir = Some(system_config_dir());
113    }
114    if mode_system && data_dir.is_none() {
115        data_dir = Some(system_data_dir());
116    }
117
118    // Resolve binary path: copy if --path given, otherwise use current exe in-place.
119    let binary_path = if let Some(dir) = dest_dir {
120        let bin_name = current_exe
121            .file_name()
122            .ok_or_else(|| anyhow::anyhow!("cannot determine binary filename"))?;
123        let dest = dir.join(bin_name);
124        std::fs::create_dir_all(&dir)
125            .map_err(|e| anyhow::anyhow!("cannot create destination dir {:?}: {}", dir, e))?;
126        std::fs::copy(&current_exe, &dest)
127            .map_err(|e| anyhow::anyhow!("cannot copy binary to {:?}: {}", dest, e))?;
128        println!("Copied binary to {}", dest.display());
129        dest
130    } else {
131        current_exe.clone()
132    };
133
134    // Default config/data dirs: sibling of binary.
135    let binary_dir = binary_path
136        .parent()
137        .unwrap_or_else(|| Path::new("."))
138        .to_path_buf();
139
140    let config_dir = config_dir.unwrap_or_else(|| binary_dir.join("config"));
141    let data_dir = data_dir.unwrap_or_else(|| binary_dir.join("data"));
142
143    Ok(ServiceInstallOptions {
144        binary_path,
145        config_dir,
146        data_dir,
147        service_name,
148    })
149}
150
151fn next_arg<'a>(args: &'a [String], i: usize, flag: &str) -> anyhow::Result<&'a str> {
152    args.get(i + 1)
153        .map(|s| s.as_str())
154        .ok_or_else(|| anyhow::anyhow!("{} requires a value", flag))
155}
156
157#[cfg(target_os = "windows")]
158fn system_binary_dir() -> PathBuf {
159    PathBuf::from(std::env::var("ProgramData").unwrap_or_else(|_| "C:\\ProgramData".into()))
160        .join("WEFT")
161}
162#[cfg(target_os = "windows")]
163fn system_config_dir() -> PathBuf {
164    system_binary_dir().join("config")
165}
166#[cfg(target_os = "windows")]
167fn system_data_dir() -> PathBuf {
168    system_binary_dir().join("data")
169}
170
171#[cfg(target_os = "linux")]
172fn system_binary_dir() -> PathBuf {
173    PathBuf::from("/usr/local/bin")
174}
175#[cfg(target_os = "linux")]
176fn system_config_dir() -> PathBuf {
177    PathBuf::from("/etc/weft")
178}
179#[cfg(target_os = "linux")]
180fn system_data_dir() -> PathBuf {
181    PathBuf::from("/var/lib/weft")
182}
183
184#[cfg(target_os = "macos")]
185fn system_binary_dir() -> PathBuf {
186    PathBuf::from("/usr/local/bin")
187}
188#[cfg(target_os = "macos")]
189fn system_config_dir() -> PathBuf {
190    PathBuf::from("/Library/Application Support/WEFT")
191}
192#[cfg(target_os = "macos")]
193fn system_data_dir() -> PathBuf {
194    PathBuf::from("/Library/Application Support/WEFT/data")
195}