1use std::process::{Command, Stdio};
2use std::fs;
3use std::path::PathBuf;
4
5use crate::cli::{DaemonArgs, DaemonAction};
6
7fn pid_path() -> PathBuf {
8 std::env::temp_dir().join("nomad-daemon.pid")
9}
10
11fn daemon_binary() -> PathBuf {
12 let candidates = [
14 PathBuf::from("/usr/local/bin/nomad-daemon"),
15 PathBuf::from("/usr/bin/nomad-daemon"),
16 std::env::current_exe()
18 .ok()
19 .and_then(|p| p.parent().map(|d| d.join("nomad-daemon")))
20 .unwrap_or_default(),
21 ];
22 for c in &candidates {
23 if c.exists() {
24 return c.clone();
25 }
26 }
27 PathBuf::from("nomad-daemon")
29}
30
31pub fn run(args: DaemonArgs) -> Result<(), Box<dyn std::error::Error>> {
32 match args.action {
33 DaemonAction::Start { port, cache_cap, parse_concurrency } => cmd_start(port, cache_cap, parse_concurrency),
34 DaemonAction::Stop => cmd_stop(),
35 DaemonAction::Status => cmd_status(),
36 DaemonAction::Restart { port } => {
37 cmd_stop().ok();
38 std::thread::sleep(std::time::Duration::from_secs(1));
39 cmd_start(port, 1, 2)
40 }
41 }
42}
43
44fn cmd_start(port: u16, cache_cap: usize, parse_concurrency: usize) -> Result<(), Box<dyn std::error::Error>> {
45 if is_running() {
46 println!("nomad-daemon is already running");
47 return Ok(());
48 }
49
50 let binary = daemon_binary();
51 if !binary.exists() {
52 println!("Building nomad-daemon...");
54 let status = Command::new("cargo")
55 .args(["build", "--release", "-p", "nomad-daemon"])
56 .stdout(Stdio::null())
57 .stderr(Stdio::null())
58 .status()?;
59 if !status.success() {
60 return Err("failed to build nomad-daemon".into());
61 }
62 let target = PathBuf::from("target/release/nomad-daemon");
63 if target.exists() {
64 fs::copy(&target, &binary).ok();
65 }
66 }
67
68 let child = Command::new(&binary)
69 .env("PORT", port.to_string())
70 .env("MALLOC_ARENA_MAX", "1")
71 .env("NOMAD_CACHE_CAP", cache_cap.to_string())
72 .env("NOMAD_PARSE_CONCURRENCY", parse_concurrency.to_string())
73 .env("NOMAD_API_AUTH", "disabled")
74 .stdout(Stdio::null())
75 .stderr(Stdio::null())
76 .spawn()?;
77
78 let pid = child.id();
79 fs::write(pid_path(), pid.to_string())?;
80
81 for _ in 0..20 {
83 if crate::client::daemon_alive(port) {
84 println!("nomad-daemon started (PID {pid}, port {port})");
85 return Ok(());
86 }
87 std::thread::sleep(std::time::Duration::from_millis(250));
88 }
89 println!("Started but waiting for readiness...");
90 Ok(())
91}
92
93fn cmd_stop() -> Result<(), Box<dyn std::error::Error>> {
94 let pid_path = pid_path();
95 if !pid_path.exists() {
96 #[cfg(unix)]
98 {
99 let _ = Command::new("pkill")
100 .args(["-x", "nomad-daemon"])
101 .stdout(Stdio::null())
102 .status();
103 }
104 #[cfg(windows)]
105 {
106 let _ = Command::new("taskkill")
107 .args(["/f", "/im", "nomad-daemon.exe"])
108 .stdout(Stdio::null())
109 .status();
110 }
111 println!("nomad-daemon stopped");
112 return Ok(());
113 }
114
115 let pid_str = fs::read_to_string(&pid_path)?;
116 let pid: u32 = pid_str.trim().parse()?;
117
118 #[cfg(unix)]
119 {
120 use std::process;
121 let rc = Command::new("kill")
122 .args(["-TERM", &pid.to_string()])
123 .stdout(Stdio::null())
124 .status()?;
125 if rc.success() {
126 for _ in 0..10 {
128 if process::Command::new("kill")
129 .args(["-0", &pid.to_string()])
130 .stdout(Stdio::null())
131 .status()
132 .is_err()
133 {
134 break;
135 }
136 std::thread::sleep(std::time::Duration::from_millis(200));
137 }
138 }
139 }
140 #[cfg(windows)]
141 {
142 let _ = Command::new("taskkill")
143 .args(["/pid", &pid.to_string()])
144 .stdout(Stdio::null())
145 .status();
146 }
147
148 fs::remove_file(&pid_path).ok();
149 println!("nomad-daemon stopped");
150 Ok(())
151}
152
153fn cmd_status() -> Result<(), Box<dyn std::error::Error>> {
154 if is_running() {
155 for port in &[8000, 8001, 8080] {
157 if crate::client::daemon_alive(*port) {
158 crate::client::health(*port, false)?;
159 return Ok(());
160 }
161 }
162 println!("nomad-daemon: PID EXISTS but not responding");
163 } else {
164 println!("nomad-daemon: STOPPED");
165 }
166 Ok(())
167}
168
169fn is_running() -> bool {
170 let pid_path = pid_path();
171 if !pid_path.exists() {
172 return false;
173 }
174 let pid_str = match fs::read_to_string(&pid_path) {
175 Ok(s) => s,
176 Err(_) => return false,
177 };
178 let pid: u32 = match pid_str.trim().parse() {
179 Ok(p) => p,
180 Err(_) => return false,
181 };
182
183 #[cfg(unix)]
184 {
185 std::process::Command::new("kill")
186 .args(["-0", &pid.to_string()])
187 .stdout(Stdio::null())
188 .status()
189 .is_ok()
190 }
191 #[cfg(windows)]
192 {
193 std::process::Command::new("tasklist")
194 .args(["/fi", &format!("PID eq {}", pid)])
195 .stdout(Stdio::null())
196 .status()
197 .is_ok()
198 }
199 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
200 {
201 false
202 }
203}