pub mod api;
pub mod cache;
pub mod config;
pub mod sse;
pub mod upstream;
pub mod watch;
pub use config::Args;
use std::sync::Arc;
use tokio::net::TcpListener;
use tracing::info;
pub async fn run(args: Args) -> anyhow::Result<()> {
if args.prefix.is_empty() {
anyhow::bail!("at least one --prefix is required");
}
let upstream = upstream::UpstreamClient::new(args.server.clone());
let prefixes: Vec<Arc<cache::PrefixState>> = args
.prefix
.iter()
.map(|p| Arc::new(cache::PrefixState::new(p.clone())))
.collect();
for prefix in &prefixes {
let prefix = prefix.clone();
let upstream = upstream.clone();
let min_backoff = args.min_backoff;
let max_backoff = args.max_backoff;
tokio::spawn(async move {
watch::run_loop(prefix, upstream, min_backoff, max_backoff).await;
});
}
let state = Arc::new(api::AgentState {
upstream,
prefixes: prefixes.clone(),
});
let app = api::router(state);
let listener = TcpListener::bind(&args.listen).await?;
let actual = listener.local_addr()?;
info!(addr = %actual, prefixes = ?args.prefix, "ferra-agent listening");
axum::serve(listener, app)
.with_graceful_shutdown(shutdown_signal())
.await?;
Ok(())
}
async fn shutdown_signal() {
#[cfg(unix)]
{
use tokio::signal::unix::{signal, SignalKind};
let mut term = signal(SignalKind::terminate()).expect("install SIGTERM handler");
tokio::select! {
_ = tokio::signal::ctrl_c() => {},
_ = term.recv() => {},
}
}
#[cfg(not(unix))]
{
let _ = tokio::signal::ctrl_c().await;
}
tracing::info!("shutdown signal received");
}