eba4fd1
use std::net::SocketAddr;
use admin::AdminService;
use anyhow::Context;
use axum::Router;
use config::scoped::{AdminhostConfig, ScopedConfig};
use tokio::net::TcpListener;
use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer};
use tracing::{Level, info, warn};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
export::telemetry::init_tracing();
let cfg = AdminhostConfig::load().context("loading configuration")?;
if !cfg.admin.enabled() {
info!("adminhost 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");
let health = export::health::router_with(Some(state.database.clone()), state.redis.clone());
let service = AdminService::new(state);
let grpc_addr: SocketAddr = format!("{}:{}", cfg.admin.bind_addr(), cfg.admin.grpc_port())
.parse()
.with_context(|| {
format!(
"parsing gRPC bind address {}:{}",
cfg.admin.bind_addr(),
cfg.admin.grpc_port()
)
})?;
let trusted_keys = cfg.admin.grpc_trusted_keys().to_vec();
if trusted_keys.is_empty() {
warn!("admin.grpc_trusted_keys is empty; every gRPC call will be rejected");
}
let grpc_service = service.clone();
tokio::spawn(async move {
if let Err(error) = admin_rpc::serve(
grpc_service,
grpc_addr,
trusted_keys,
export::telemetry::shutdown_signal(),
)
.await
{
warn!(%error, "admin gRPC server failed");
}
});
info!(%grpc_addr, "admin gRPC listening");
let router: Router = health
.layer(
TraceLayer::new_for_http()
.make_span_with(
DefaultMakeSpan::new()
.level(Level::INFO)
.include_headers(false),
)
.on_response(DefaultOnResponse::new().level(Level::INFO)),
)
.layer(export::middleware::AppTraceLayer::new());
let bind_addr = cfg.admin.bind_addr().to_string();
let port = cfg.admin.port();
let addr: SocketAddr = format!("{bind_addr}:{port}")
.parse()
.with_context(|| format!("parsing bind address {bind_addr}:{port}"))?;
let listener = TcpListener::bind(addr)
.await
.with_context(|| format!("binding {addr}"))?;
info!(%addr, "adminhost listening");
axum::serve(listener, router.into_make_service())
.with_graceful_shutdown(export::telemetry::shutdown_signal())
.await
.context("axum server error")?;
info!("adminhost shut down");
Ok(())
}