Skip to content
Clone
69f12aa
104 lines 3.9 KB Rust
use std::net::SocketAddr;
use std::sync::Arc;
use anyhow::Context;
use config::scoped::{GithostConfig, ScopedConfig};
use tokio::net::TcpListener;
use tracing::{info, warn};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
export::telemetry::init_tracing();
let cfg = GithostConfig::load().context("loading configuration")?;
if !cfg.githost.enabled() {
info!("githost is disabled by config; exiting");
return Ok(());
}
info!("connecting to database / redis / storage / email");
let state = export::AppState::with_config(cfg.clone().into_app_config())
.await
.context("building AppState")?;
info!("dependencies ready");
if let Err(err) = export::telemetry::init_meter_provider() {
warn!(error = %err, "failed to init meter provider; metrics disabled");
}
let git_svc = git::service::GitService::new(state.clone());
let sync_queue = Arc::new(git::sync::queue::SyncQueue::new(state.redis.clone()));
let smart_svc = git::smart::service::SmartService::new(git_svc)
.with_kv(Some(Arc::new(state.redis.clone())))
.with_queue(Some(sync_queue))
.with_sync_settings(cfg.git.sync.clone())
.with_ci_event_topic(cfg.ci.enabled.then(|| cfg.ci.event_queue_topic.clone()));
let ssh_svc = git::ssh::service::GitSshService::new(smart_svc.clone(), cfg.git.ssh.clone())
.with_sync_settings(cfg.git.sync.clone())
.with_ci_event_topic(cfg.ci.enabled.then(|| cfg.ci.event_queue_topic.clone()));
let smart_router =
git::smart::handler::router(smart_svc).merge(export::health::router(state.clone()));
let http_addr = SocketAddr::new(cfg.githost.http_bind_addr(), cfg.githost.http_port());
let http_listener = TcpListener::bind(http_addr)
.await
.with_context(|| format!("binding smart HTTP {http_addr}"))?;
info!(%http_addr, "githost smart HTTP listening");
let ssh_bind = cfg.git.ssh.bind_addr.clone();
let ssh_port = cfg.git.ssh.port;
let ssh_addr: SocketAddr = format!("{ssh_bind}:{ssh_port}")
.parse()
.with_context(|| format!("parsing ssh address {ssh_bind}:{ssh_port}"))?;
git::ssh::build_server_config(&cfg.git.ssh)
.with_context(|| format!("validating ssh server config for {ssh_addr}"))?;
let ssh_listener = TcpListener::bind(ssh_addr)
.await
.with_context(|| format!("binding SSH {ssh_addr}"))?;
info!(%ssh_addr, "githost SSH listening");
let (http_tx, http_rx) = tokio::sync::oneshot::channel::<()>();
let (ssh_tx, ssh_rx) = tokio::sync::oneshot::channel::<()>();
let http_task = tokio::spawn(async move {
let http_shutdown = async move {
let _ = http_rx.await;
};
axum::serve(http_listener, smart_router.into_make_service())
.with_graceful_shutdown(http_shutdown)
.await
.map_err(anyhow::Error::from)
});
let mut ssh_task = tokio::spawn(async move {
tokio::select! {
res = git::ssh::serve_on_listener(ssh_svc, ssh_listener) => res.map_err(anyhow::Error::from),
_ = ssh_rx => Ok(()),
}
});
let ssh_crashed = tokio::select! {
_ = export::telemetry::shutdown_signal() => false,
res = &mut ssh_task => {
match res {
Err(e) => warn!(error = %e, "ssh task panicked"),
Ok(Err(e)) => warn!(error = %e, "git ssh server exited unexpectedly"),
Ok(Ok(())) => {}
}
true
}
};
if ssh_crashed {
let _ = http_tx.send(());
let _ = http_task.await;
anyhow::bail!("git ssh server on {ssh_addr} exited; refusing to keep running without it");
}
info!("shutdown signal received; stopping listeners");
let _ = http_tx.send(());
let _ = ssh_tx.send(());
let _ = ssh_task.await;
http_task.await.context("smart HTTP task panicked")??;
info!("githost shut down");
Ok(())
}