#[cfg(unix)]
mod imp {
use std::path::PathBuf;
use leindex::cli::daemon::client::{LOCK_NAME, SOCKET_NAME};
use leindex::cli::daemon::endpoint::{
DaemonEndpoint, ENDPOINT_SIDECAR, read_sidecar, write_endpoint_sidecar,
};
use leindex::cli::daemon::handshake::DAEMON_PROTOCOL_VERSION;
use leindex::cli::mcp::server::{McpServer, McpServerConfig, ProcessIdleClock};
use tracing::{error, info};
const TOKIO_WORKERS_ENV: &str = "LEINDEX_TOKIO_WORKERS";
const DEFAULT_TOKIO_WORKERS: usize = 2;
const DEFAULT_IDLE_TIMEOUT_SECS: u64 = 900;
fn configured_worker_count() -> usize {
std::env::var(TOKIO_WORKERS_ENV)
.ok()
.and_then(|v| v.parse::<usize>().ok())
.filter(|&n| n > 0)
.unwrap_or(DEFAULT_TOKIO_WORKERS)
}
fn build_runtime() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(configured_worker_count())
.enable_all()
.build()
.expect("failed to build Tokio runtime")
}
fn parse_arg_value(args: &[String], prefix: &str) -> Option<(String, usize)> {
for (i, arg) in args.iter().enumerate() {
if let Some(val) = arg.strip_prefix(&format!("{prefix}=")) {
return Some((val.to_string(), 1));
}
if arg == prefix {
if let Some(val) = args.get(i + 1) {
return Some((val.clone(), 2));
}
}
}
None
}
fn acquire_daemon_lock(path: &std::path::Path) -> std::io::Result<std::fs::File> {
use std::os::unix::io::AsRawFd;
let file = std::fs::OpenOptions::new()
.create(true)
.truncate(false)
.write(true)
.open(path)?;
let locked = unsafe { libc::flock(file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) };
if locked != 0 {
return Err(std::io::Error::last_os_error());
}
let held = fstat_pair(&file);
let at_path = std::fs::metadata(path).ok().map(|m| {
use std::os::unix::fs::MetadataExt;
(m.dev(), m.ino())
});
if let (Some((dev, ino)), Some((pdev, pino))) = (held, at_path) {
if (dev, ino) != (pdev, pino) {
return Err(std::io::Error::other(
"daemon lock file was replaced during acquisition; retrying would race two daemons",
));
}
}
Ok(file)
}
fn fstat_pair(file: &std::fs::File) -> Option<(u64, u64)> {
use std::os::fd::AsRawFd;
let mut st: libc::stat = unsafe { std::mem::zeroed() };
if unsafe { libc::fstat(file.as_raw_fd(), &mut st) } == 0 {
Some((st.st_dev as u64, st.st_ino as u64))
} else {
None
}
}
fn init_logging() {
let subscriber = tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("warn")),
)
.finish();
let _ = tracing::subscriber::set_global_default(subscriber);
}
pub fn main() -> anyhow::Result<()> {
init_logging();
let args: Vec<String> = std::env::args().skip(1).collect();
let idle_timeout_secs: u64 = parse_arg_value(&args, "--idle-timeout-secs")
.map(|(v, _)| v.parse::<u64>().unwrap_or(DEFAULT_IDLE_TIMEOUT_SECS))
.unwrap_or(DEFAULT_IDLE_TIMEOUT_SECS);
let run_dir = leindex::config::resolve_leindex_home()
.ok_or_else(|| anyhow::anyhow!("cannot resolve LeIndex home directory"))?
.join("run");
std::fs::create_dir_all(&run_dir)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&run_dir, std::fs::Permissions::from_mode(0o700))?;
}
let socket_path: PathBuf = parse_arg_value(&args, "--socket")
.map(|(v, _)| PathBuf::from(v))
.unwrap_or_else(|| run_dir.join(SOCKET_NAME));
let _lock = match acquire_daemon_lock(&run_dir.join(LOCK_NAME)) {
Ok(lock) => lock,
Err(e) => {
if e.kind() == std::io::ErrorKind::Other {
anyhow::bail!("{e}");
}
let holder = read_sidecar(&run_dir.join(ENDPOINT_SIDECAR)).ok().flatten();
match holder {
Some(ep) => anyhow::bail!(
"daemon already live at {} (pid {}); refusing second instance",
ep.socket_path.display(),
ep.pid
),
None => anyhow::bail!(
"daemon already live (lock {} is held); refusing second instance",
run_dir.join(LOCK_NAME).display()
),
}
}
};
info!(
"leindexd starting; socket={}, idle_timeout_secs={}",
socket_path.display(),
idle_timeout_secs
);
leindex::feature_flags::log_flag_state();
let rt = build_runtime();
rt.block_on(async {
let server = McpServer::new(McpServerConfig::default())?;
let idle_timeout = if idle_timeout_secs == 0 {
None } else {
Some(std::time::Duration::from_secs(idle_timeout_secs))
};
let run_dir_clone = run_dir.clone();
let post_bind = move |bound_socket: &std::path::Path| {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
if let Err(e) = std::fs::set_permissions(
bound_socket,
std::fs::Permissions::from_mode(0o600),
) {
error!("failed to restrict socket {}: {e}", bound_socket.display());
}
}
let endpoint = DaemonEndpoint::current_process(
bound_socket.to_path_buf(),
DAEMON_PROTOCOL_VERSION,
);
match write_endpoint_sidecar(&run_dir_clone, &endpoint) {
Ok(()) => {
info!(
"wrote {} (socket={}, pid={})",
ENDPOINT_SIDECAR,
bound_socket.display(),
endpoint.pid
);
}
Err(e) => {
error!("failed to write endpoint sidecar: {e}");
}
}
};
let mut terminate =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?;
tokio::select! {
served = server.run_socket(
&socket_path,
ProcessIdleClock::new(),
idle_timeout,
Some(&post_bind),
) => served,
_ = terminate.recv() => {
info!("leindexd received SIGTERM; exiting");
let _ = std::fs::remove_file(&socket_path);
Ok(())
}
}
})
}
}
#[cfg(not(unix))]
mod imp {
pub fn main() -> anyhow::Result<()> {
anyhow::bail!("leindexd is only supported on Unix platforms")
}
}
fn main() -> anyhow::Result<()> {
imp::main()
}