use std::io::Write as _;
use std::path::PathBuf;
use clap::Args;
#[derive(Clone, Debug, Args)]
pub struct ServeArgs {
#[arg(long)]
pub id: Option<uuid::Uuid>,
#[arg(long = "listen", value_name = "URI")]
pub listeners: Vec<String>,
#[arg(long, value_name = "PATH")]
pub pairing_store: Option<PathBuf>,
#[arg(long, value_name = "PATH")]
pub store_root: Option<PathBuf>,
#[arg(long, value_name = "PATH", conflicts_with = "no_discovery")]
pub discovery_file: Option<PathBuf>,
#[arg(long)]
pub no_discovery: bool,
#[arg(long, value_name = "SECONDS", value_parser = clap::value_parser!(u64).range(1..))]
pub daemon_idle_timeout: Option<u64>,
#[arg(long = "runner-provider", value_name = "PATH")]
pub runner_providers: Vec<PathBuf>,
}
pub async fn run(args: ServeArgs, project_root: &std::path::Path) -> Result<i32, String> {
let listeners = if args.listeners.is_empty() {
vec![auv_daemon::default_local_listener(
args.discovery_file.as_deref(),
)?]
} else {
args.listeners
};
let listeners =
listeners.iter().map(|listener| auv_daemon::parse_listener(listener, args.pairing_store.is_some())).collect::<Result<Vec<_>, _>>()?;
run_listeners(
HostOptions {
id: args.id,
listeners,
pairing_store: args.pairing_store,
store_root: args.store_root,
discovery_file: args.discovery_file,
publish_discovery: !args.no_discovery,
daemon_idle_timeout: args.daemon_idle_timeout,
runner_providers: args.runner_providers,
},
project_root,
)
.await
}
pub(super) struct HostOptions {
pub id: Option<uuid::Uuid>,
pub listeners: Vec<auv_daemon::ListenEndpoint>,
pub pairing_store: Option<PathBuf>,
pub store_root: Option<PathBuf>,
pub discovery_file: Option<PathBuf>,
pub publish_discovery: bool,
pub daemon_idle_timeout: Option<u64>,
pub runner_providers: Vec<PathBuf>,
}
pub(super) async fn run_listeners(options: HostOptions, project_root: &std::path::Path) -> Result<i32, String> {
let store_root = options.store_root.map_or_else(|| project_root.join(".auv").join("store"), |path| resolve_path(project_root, &path));
let providers = options
.runner_providers
.iter()
.map(|path| {
let path = resolve_path(project_root, path);
auv_daemon::runner_provider::RunnerProviderConfig::load_json(&path)
.map_err(|error| format!("failed to load --runner-provider {}: {error}", path.display()))
})
.collect::<Result<Vec<_>, _>>()?;
let server = auv_daemon::Server::bind(auv_daemon::Config {
id: options.id,
listeners: options.listeners,
first_party_runners: first_party_runner_runtimes(&store_root)?,
store_root,
pairing_store: options.pairing_store.map(|path| resolve_path(project_root, &path)),
discovery_file: options.discovery_file,
publish_discovery: options.publish_discovery,
daemon_idle_timeout: options.daemon_idle_timeout.map(std::time::Duration::from_secs),
runner_providers: providers,
})
.await?;
for endpoint in server.endpoints() {
println!("auv serve: {endpoint}");
}
std::io::stdout().flush().map_err(|error| format!("failed to flush daemon listener log: {error}"))?;
let shutdown = tokio_util::sync::CancellationToken::new();
let signal = shutdown.clone();
tokio::spawn(async move {
if tokio::signal::ctrl_c().await.is_ok() {
signal.cancel();
}
});
server.serve(shutdown).await?;
Ok(0)
}
fn resolve_path(root: &std::path::Path, path: &std::path::Path) -> PathBuf {
if path.is_absolute() {
path.to_path_buf()
} else {
root.join(path)
}
}
fn first_party_runner_runtimes(store_root: &std::path::Path) -> Result<auv_daemon::runner_provider::FirstPartyRunnerRuntimes, String> {
use auv_daemon::runner_provider::{ExecutableRunnerRuntime, RunnerRuntime};
use std::collections::BTreeMap;
let executable = std::env::current_exe().map_err(|error| format!("failed to resolve the auv executable for Runner hosting: {error}"))?;
let runner_state_root = store_root.join("runner-state").join("auv.core.local");
let runner_state_root =
runner_state_root.to_str().ok_or_else(|| format!("local Runner state path is not valid UTF-8: {}", runner_state_root.display()))?;
let environment = BTreeMap::from([(crate::runner::STATE_ROOT_ENV.to_string(), runner_state_root.to_string())]);
Ok(auv_daemon::runner_provider::FirstPartyRunnerRuntimes {
local_driver: Some(RunnerRuntime::Executable(ExecutableRunnerRuntime {
executable,
arguments: vec![
crate::runner::INTERNAL_SENTINEL.to_string(),
crate::runner::LOCAL_DRIVER_ROLE.to_string(),
],
working_directory: None,
environment,
})),
})
}