use clap::Parser;
use lfsx_server::config::Config;
use tokio::net::TcpListener;
#[derive(Parser)]
#[command(
name = "lfsx-server",
version,
about = "A fast, lightweight, secure Git LFS server",
after_help = "Configuration is environment variables only; see https://lfsx.dev/docs/configuration"
)]
struct Cli {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
Cli::parse();
let telemetry = lfsx_server::telemetry::init();
let mut config = Config::from_env();
tokio::fs::create_dir_all(&config.storage_root).await?;
lfsx_server::verify_presign(&mut config).await;
lfsx_server::verify_locking(&mut config).await;
let config = config;
lfsx_server::reclaim(&config).await;
tokio::spawn(reclaim_periodically(config.clone()));
let listener = TcpListener::bind(config.bind).await?;
tracing::info!(
version = env!("CARGO_PKG_VERSION"),
bind = %config.bind,
root = ?config.storage_root,
"lfsx listening"
);
let served = axum::serve(listener, lfsx_server::app(config))
.with_graceful_shutdown(shutdown())
.await;
if let Some(provider) = telemetry
&& let Err(error) = provider.shutdown()
{
tracing::warn!(%error, "the last batch of spans may not have been exported");
}
served?;
Ok(())
}
async fn reclaim_periodically(config: Config) {
let mut hourly = tokio::time::interval(std::time::Duration::from_secs(3600));
hourly.tick().await;
loop {
hourly.tick().await;
lfsx_server::reclaim(&config).await;
}
}
async fn shutdown() {
let interrupt = async {
let _ = tokio::signal::ctrl_c().await;
};
#[cfg(unix)]
let terminate = async {
use tokio::signal::unix::{SignalKind, signal};
match signal(SignalKind::terminate()) {
Ok(mut sigterm) => {
sigterm.recv().await;
}
Err(_) => std::future::pending().await,
}
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = interrupt => {}
_ = terminate => {}
}
tracing::info!("shutting down");
}