use actix_web::{App, HttpServer, web}; use config::AppConfig; use db::cache::AppCache; use db::database::AppDatabase; use slog::{Logger, error, info}; use std::sync::Arc; use std::time::Duration; use tokio::time::timeout; pub mod auth; pub mod handler; pub mod lfs; pub mod lfs_routes; pub mod rate_limit; pub mod routes; pub mod utils; #[derive(Clone)] pub struct HttpAppState { pub db: AppDatabase, pub cache: AppCache, pub sync: crate::ssh::ReceiveSyncService, pub rate_limiter: Arc, pub logger: Logger, } pub fn git_http_cfg(cfg: &mut web::ServiceConfig) { cfg.route( "/{namespace}/{repo_name}.git/info/refs", web::get().to(routes::info_refs), ) .route( "/{namespace}/{repo_name}.git/git-upload-pack", web::post().to(routes::upload_pack), ) .route( "/{namespace}/{repo_name}.git/git-receive-pack", web::post().to(routes::receive_pack), ) .route( "/{namespace}/{repo_name}.git/info/lfs/objects/batch", web::post().to(lfs_routes::lfs_batch), ) .route( "/{namespace}/{repo_name}.git/info/lfs/objects/{oid}", web::put().to(lfs_routes::lfs_upload), ) .route( "/{namespace}/{repo_name}.git/info/lfs/objects/{oid}", web::get().to(lfs_routes::lfs_download), ) .route( "/{namespace}/{repo_name}.git/info/lfs/locks", web::post().to(lfs_routes::lfs_lock_create), ) .route( "/{namespace}/{repo_name}.git/info/lfs/locks", web::get().to(lfs_routes::lfs_lock_list), ) .route( "/{namespace}/{repo_name}.git/info/lfs/locks/{id}", web::get().to(lfs_routes::lfs_lock_get), ) .route( "/{namespace}/{repo_name}.git/info/lfs/locks/{id}", web::delete().to(lfs_routes::lfs_lock_delete), ); } pub async fn run_http(config: AppConfig, logger: Logger) -> anyhow::Result<()> { let (db, app_cache) = tokio::join!(AppDatabase::init(&config), AppCache::init(&config),); let db = db?; let app_cache = app_cache?; let redis_pool = app_cache.redis_pool().clone(); let sync = crate::ssh::ReceiveSyncService::new(redis_pool, logger.clone()); let rate_limiter = Arc::new(rate_limit::RateLimiter::new( rate_limit::RateLimitConfig::default(), )); let _cleanup = rate_limiter.clone().start_cleanup(); let state = HttpAppState { db: db.clone(), cache: app_cache.clone(), sync, rate_limiter, logger: logger.clone(), }; let logger_startup = logger.clone(); info!(&logger_startup, "Starting git HTTP server on 0.0.0.0:8021"); let server = HttpServer::new(move || { App::new() .app_data(web::Data::new(state.clone())) .configure(git_http_cfg) }) .bind("0.0.0.0:8021")? .run(); let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>(); let server = server; let logger_shutdown = logger.clone(); let server_handle = tokio::spawn(async move { tokio::select! { result = server => { if let Err(e) = result { error!(&logger_shutdown, "HTTP server error: {}", e); } } _ = shutdown_rx => { info!(&logger_shutdown, "HTTP server shutting down"); } } }); tokio::signal::ctrl_c().await?; info!(&logger, "Received shutdown signal"); drop(shutdown_tx); let _ = timeout(Duration::from_secs(5), server_handle).await; info!(&logger, "Git HTTP server stopped"); Ok(()) }