use clap::Args;
#[derive(Debug, Args)]
pub struct ServeArgs {
#[arg(long, default_value = "127.0.0.1:8765")]
pub bind: String,
#[arg(long)]
pub allow_scan: bool,
}
#[cfg(feature = "web")]
pub async fn execute(args: ServeArgs) -> anyhow::Result<u8> {
use anyhow::Context;
let cfg = crate::config::load::<()>(None).context("loading config")?;
let cache = crate::storage::Cache::open(cfg.cache.resolved_path()).context("opening cache")?;
let router = crate::web::router(cache, args.allow_scan);
let listener = tokio::net::TcpListener::bind(&args.bind)
.await
.with_context(|| format!("binding TCP listener at {}", args.bind))?;
let local_addr = listener.local_addr().ok();
tracing::info!(addr = %args.bind, allow_scan = args.allow_scan, "serving repo-trust web viewer");
if !is_localhost_bind(&args.bind) {
tracing::warn!(
addr = %args.bind,
"non-localhost bind: the web viewer is now reachable from the network. \
See docs/architecture.md §12."
);
}
if let Some(addr) = local_addr {
eprintln!("repo-trust serve: listening on http://{addr}");
}
axum::serve(listener, router)
.with_graceful_shutdown(shutdown_signal())
.await
.context("axum::serve failed")?;
Ok(0)
}
#[cfg(feature = "web")]
fn is_localhost_bind(bind: &str) -> bool {
let host = match bind.rsplit_once(':') {
Some((host, _)) => host,
None => bind,
};
let host = host.trim_start_matches('[').trim_end_matches(']');
host == "localhost" || host.starts_with("127.") || host == "::1"
}
#[cfg(feature = "web")]
async fn shutdown_signal() {
let ctrl_c = async {
let _ = tokio::signal::ctrl_c().await;
};
#[cfg(unix)]
let terminate = async {
if let Ok(mut sig) =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
{
sig.recv().await;
}
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
() = ctrl_c => {},
() = terminate => {},
}
tracing::info!("shutdown signal received; stopping web viewer");
}
#[cfg(all(test, feature = "web"))]
mod tests {
use super::is_localhost_bind;
#[test]
fn localhost_variants_are_localhost() {
assert!(is_localhost_bind("127.0.0.1:8765"));
assert!(is_localhost_bind("127.1.2.3:8765"));
assert!(is_localhost_bind("localhost:8765"));
assert!(is_localhost_bind("[::1]:8765"));
}
#[test]
fn external_bind_is_not_localhost() {
assert!(!is_localhost_bind("0.0.0.0:8765"));
assert!(!is_localhost_bind("10.0.0.5:8765"));
assert!(!is_localhost_bind("[::]:8765"));
}
}