2aef8d4
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<dyn std::error::Error>> {
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 <path>])").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"),
}
}