use std::ffi::OsString;
use std::path::PathBuf;
use std::time::{Duration, Instant};
use anyhow::{Context, Result};
use mcpmesh_trust::paths;
pub use mcpmesh_local_api::client::{ClientError, ControlClient, connect_control};
#[derive(Clone, Debug)]
pub struct DaemonLaunch {
pub exe: PathBuf,
pub socket: PathBuf,
pub env: Vec<(OsString, OsString)>,
}
impl DaemonLaunch {
pub fn ambient() -> Result<Self> {
Ok(Self {
exe: std::env::current_exe().context("resolve current executable")?,
socket: paths::default_socket_path()?,
env: Vec::new(),
})
}
}
pub async fn ensure_daemon() -> Result<ControlClient> {
ensure_daemon_with(&DaemonLaunch::ambient()?).await
}
pub async fn ensure_daemon_with(launch: &DaemonLaunch) -> Result<ControlClient> {
if let Ok(client) = connect_control(&launch.socket).await {
return Ok(client);
}
spawn_detached(launch)?;
let deadline = Instant::now() + Duration::from_secs(10);
let mut backoff = Duration::from_millis(20);
loop {
match connect_control(&launch.socket).await {
Ok(client) => return Ok(client),
Err(e) => {
if Instant::now() >= deadline {
return Err(anyhow::Error::from(e)
.context("daemon did not accept connections within 10s"));
}
tokio::time::sleep(backoff).await;
backoff = (backoff * 2).min(Duration::from_millis(200));
}
}
}
}
fn spawn_detached(launch: &DaemonLaunch) -> Result<()> {
use std::os::unix::process::CommandExt;
let mut cmd = std::process::Command::new(&launch.exe);
cmd.arg("internal")
.arg("daemon")
.stdin(std::process::Stdio::null())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.process_group(0);
for (key, value) in &launch.env {
cmd.env(key, value);
}
let _child = cmd
.spawn()
.with_context(|| format!("spawn {} internal daemon", launch.exe.display()))?;
Ok(())
}