//! varde-daemon: content-addressed blob mirror daemon. use std::path::PathBuf; use anyhow::{bail, Context, Result}; use tracing::info; use varde_daemon::config::{Config, Mode, Overrides}; use varde_daemon::{daemon, server}; const USAGE: &str = "\ varde-daemon — content-addressed blob mirror daemon USAGE: varde-daemon [OPTIONS] OPTIONS: --config Config file (default: /etc/varde/config.toml or $XDG_CONFIG_HOME/varde/config.toml) --store Store directory override --socket API socket path override --system Force system-mode default paths --user Force user-mode default paths --version Print version and exit --help Print this help and exit "; // The spec reserves clap for varde-ctl; the daemon takes five flags, which // a hand parse covers without the dependency. fn parse_args() -> Result<(Mode, Overrides)> { let mut overrides = Overrides::default(); let mut mode = Mode::detect(); let mut args = std::env::args().skip(1); while let Some(arg) = args.next() { let mut value = |name: &str| -> Result { args.next() .map(PathBuf::from) .with_context(|| format!("{name} requires a value")) }; match arg.as_str() { "--config" => overrides.config_path = Some(value("--config")?), "--store" => overrides.store_dir = Some(value("--store")?), "--socket" => overrides.socket_path = Some(value("--socket")?), "--system" => mode = Mode::System, "--user" => mode = Mode::User, "--version" => { println!("varde-daemon {}", env!("CARGO_PKG_VERSION")); std::process::exit(0); } "--help" | "-h" => { print!("{USAGE}"); std::process::exit(0); } other => bail!("unknown argument {other:?}\n\n{USAGE}"), } } Ok((mode, overrides)) } fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() .unwrap_or_else(|_| "varde_daemon=info".into()), ) .init(); let (mode, overrides) = parse_args()?; let config = Config::load(mode, &overrides)?; info!(store = %config.store_dir.display(), socket = %config.socket_path.display(), "starting"); std::fs::create_dir_all(&config.store_dir) .with_context(|| format!("creating store directory {}", config.store_dir.display()))?; tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .context("building tokio runtime")? .block_on(run(config)) } async fn run(config: Config) -> Result<()> { let socket_path = config.socket_path.clone(); let daemon = daemon::Daemon::open(config).await?; // Prefer a systemd activation socket; bind ourselves otherwise so // non-systemd distros work identically. let (listener, activated) = match server::activation_listener()? { Some(listener) => (listener, true), None => (server::bind_socket(&socket_path)?, false), }; server::notify_ready(); let result = tokio::select! { r = server::serve(listener, daemon.clone()) => r, r = shutdown_signal() => r.map(|signal| info!(signal, "shutting down")), }; // Close the endpoint and flush the store. The socket file is ours to // remove only when we bound it (systemd owns activation sockets). daemon.shutdown().await; if !activated { let _ = std::fs::remove_file(&socket_path); } result } async fn shutdown_signal() -> Result<&'static str> { use tokio::signal::unix::{signal, SignalKind}; let mut term = signal(SignalKind::terminate()).context("installing SIGTERM handler")?; let mut int = signal(SignalKind::interrupt()).context("installing SIGINT handler")?; tokio::select! { _ = term.recv() => Ok("SIGTERM"), _ = int.recv() => Ok("SIGINT"), } }