Skip to main content

nomad_cli/
daemon.rs

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    // Look for nomad-daemon in standard locations
13    let candidates = [
14        PathBuf::from("/usr/local/bin/nomad-daemon"),
15        PathBuf::from("/usr/bin/nomad-daemon"),
16        // Relative to the nom binary
17        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    // Fallback: just try the name
28    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        // Try to build it
53        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    // Wait for daemon to become ready
82    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        // Try pkill
97        #[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            // Wait for process to die
127            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        // Try health endpoint on common ports
156        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}