use std::sync::Arc; use cache::RedisCache; use config::ConfigLoader; use db::conn::Db; use gitauth::GitAuthService; use jwt::{JwtState, SigningKeyCache}; use tokio::net::TcpListener; use tracing::{info, warn}; const DEFAULT_LOG_FILTER: &str = "info,gitpro=debug,gitsmart=debug,gitssh=debug"; #[tokio::main] async fn main() { if let Err(e) = run().await { eprintln!("gitpro: fatal: {e}"); std::process::exit(1); } } async fn run() -> Result<(), Box> { let mut loader = ConfigLoader::new(); let mut args = std::env::args().skip(1); while let Some(arg) = args.next() { match arg.as_str() { "--config" => { let path = args.next().ok_or("--config requires a path argument")?; loader = loader.config_file(path); } other => { return Err( format!("unknown argument: {other} (usage: gitpro [--config ])").into(), ); } } } let mut app = loader.load()?; app.obs = app.obs.with_app_defaults("gitpro", DEFAULT_LOG_FILTER); let _obs = obs::init(&app.obs)?; let db = Db::from_config(&app.db).await?; let kv = if app.cache.urls.is_empty() { None } else { Some(Arc::new(RedisCache::new(&app.cache).await?)) }; let queue = queue::QueueBuilder::from_config(app.queue.clone()) .build() .await? .map(Arc::new); let storage = storage::repo::from_config(&app.storage)?; let jwt = JwtState::new( db.writer(), app.jwt.clone(), Arc::new(tokio::sync::RwLock::new(SigningKeyCache::new())), ); let auth = GitAuthService::new(db, kv.clone(), jwt).with_config(app.git_auth.clone()); let smart = gitsmart::SmartService::new(auth.clone(), app.git_smart.clone(), storage) .await? .with_kv(kv.clone()) .with_queue(queue.clone()) .with_sync_settings(app.git_sync.clone()); let http_listener = TcpListener::bind(( app.git_smart.bind_addr.as_str(), app.git_smart.port, )) .await?; info!( addr = %app.git_smart.bind_addr, port = app.git_smart.port, "gitsmart http server listening" ); let router = gitsmart::handler::router(Arc::new(smart)); let ssh = gitssh::GitSshService::new(auth, app.git_ssh.clone()) .with_kv(kv) .with_queue(queue) .with_sync_settings(app.git_sync); let ssh_listener = TcpListener::bind((app.git_ssh.bind_addr.as_str(), app.git_ssh.port)).await?; info!( addr = %app.git_ssh.bind_addr, port = app.git_ssh.port, "gitssh server listening" ); let http_task = tokio::spawn(async move { axum::serve(http_listener, router) .with_graceful_shutdown(shutdown_signal()) .await }); let ssh_task = tokio::spawn(async move { gitssh::server::serve_on_listener(ssh, ssh_listener).await }); tokio::select! { http = http_task => http??, ssh = ssh_task => ssh??, } info!("gitpro shut down cleanly"); Ok(()) } async fn shutdown_signal() { let ctrl_c = async { if let Err(e) = tokio::signal::ctrl_c().await { warn!(error = %e, "failed to install ctrl-c handler"); } }; #[cfg(unix)] let term = async { match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) { Ok(mut s) => { s.recv().await; } Err(e) => { warn!(error = %e, "failed to install SIGTERM handler"); std::future::pending::<()>().await; } } }; #[cfg(not(unix))] let term = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => info!("ctrl-c received, shutting down"), _ = term => info!("SIGTERM received, shutting down"), } }