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(()) }