use std::path::PathBuf;
use std::sync::Arc;
use clap::Parser;
use tokio::sync::Mutex;
use zlayer_overlayd::server::OverlaydServer;
use zlayer_overlayd::transport;
use zlayer_paths::ZLayerDirs;
use zlayer_types::overlayd::OverlaydFrame;
#[cfg(windows)]
mod service;
#[derive(Debug, Parser)]
#[command(name = "zlayer-overlayd", about, version)]
struct Args {
#[arg(long, value_name = "PATH")]
data_dir: Option<PathBuf>,
#[arg(long, value_name = "PATH")]
socket: Option<PathBuf>,
#[arg(long, hide = true)]
#[cfg_attr(not(windows), allow(dead_code))]
service: bool,
}
fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.init();
let args = Args::parse();
let data_dir = args.data_dir.unwrap_or_else(ZLayerDirs::default_data_dir);
let socket = args
.socket
.unwrap_or_else(|| PathBuf::from(ZLayerDirs::default_overlayd_socket_path_for(&data_dir)));
tracing::info!(
data_dir = %data_dir.display(),
socket = %socket.display(),
"starting zlayer-overlayd"
);
#[cfg(windows)]
if args.service {
return service::run_as_overlayd_service(data_dir, socket);
}
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.thread_name("zlayer-overlayd-worker")
.build()?;
runtime.block_on(async move {
let (external_tx, external_rx) = tokio::sync::oneshot::channel::<()>();
tokio::spawn(async move {
if tokio::signal::ctrl_c().await.is_ok() {
tracing::info!("overlayd: Ctrl-C received; signaling shutdown");
let _ = external_tx.send(());
}
});
run_overlayd(data_dir, socket, external_rx).await
})
}
async fn run_overlayd(
data_dir: PathBuf,
socket: PathBuf,
external_shutdown: tokio::sync::oneshot::Receiver<()>,
) -> anyhow::Result<()> {
let dirs = ZLayerDirs::new(data_dir.clone());
let server = OverlaydServer::new(data_dir).with_uapi_sock_dir(dirs.wireguard());
let server = Arc::new(Mutex::new(server));
let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>();
let shutdown_tx = Arc::new(Mutex::new(Some(shutdown_tx)));
let serve = {
let server = Arc::clone(&server);
let shutdown_tx = Arc::clone(&shutdown_tx);
transport::serve(&socket, move |mut conn| {
let server = Arc::clone(&server);
let shutdown_tx = Arc::clone(&shutdown_tx);
async move {
loop {
let frame = match conn.recv().await {
Ok(f) => f,
Err(zlayer_overlayd::OverlaydError::Closed) => break,
Err(e) => {
tracing::warn!(error = %e, "overlayd: recv failed; closing connection");
break;
}
};
let OverlaydFrame::Request { id, request } = frame else {
tracing::warn!("overlayd: received non-Request frame; ignoring");
continue;
};
let (response, shutdown) = {
let mut guard = server.lock().await;
let response = guard.handle(request).await;
(response, guard.shutdown_requested())
};
if let Err(e) = conn.send(&OverlaydFrame::Response { id, response }).await {
tracing::warn!(error = %e, "overlayd: send failed; closing connection");
break;
}
if shutdown {
tracing::info!("overlayd: shutdown requested; signaling exit");
if let Some(tx) = shutdown_tx.lock().await.take() {
let _ = tx.send(());
}
break;
}
}
}
})
};
tokio::select! {
res = serve => {
res?;
}
_ = shutdown_rx => {
tracing::info!("overlayd: exiting after graceful shutdown (in-band request)");
}
_ = external_shutdown => {
tracing::info!("overlayd: exiting after graceful shutdown (external signal)");
}
}
Ok(())
}