Metered detection polls NetworkManager's Metered property over D-Bus (feature "metered", default on; builds without D-Bus via no-default-features). While metered the daemon closes incoming blob connections and defers every fetch; VARDE_FORCE_METERED=true forces the state as a kill switch and test hook. Endpoint UDP sockets get DSCP CS1 best-effort by matching bound ports to /proc/net/udp inodes (iroh hides its fds). systemd socket activation adopts LISTEN_FDS fd 3, readiness is a hand-rolled sd_notify READY=1 (abstract + path sockets), and standalone binding still works unchanged. dist/ ships hardened system and user units (DynamicUser, ProtectSystem=strict, StateDirectory, RestrictAddressFamilies), a commented config example, scdoc man pages validated with scdoc, and an untested PKGBUILD skeleton. Tests: activation-socket round trip via a real fd-3 handoff, READY=1 received on a NOTIFY_SOCKET, metered daemons neither serve nor fetch. Dependencies: zbus (optional, feature-gated D-Bus client for the NetworkManager metered flag). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
116 lines
4.0 KiB
Rust
116 lines
4.0 KiB
Rust
//! 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 <PATH> Config file (default: /etc/varde/config.toml or
|
|
$XDG_CONFIG_HOME/varde/config.toml)
|
|
--store <PATH> Store directory override
|
|
--socket <PATH> 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<PathBuf> {
|
|
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"),
|
|
}
|
|
}
|