mod api_v1; pub mod host_meta; pub mod model; pub mod nodeinfo; pub mod service; pub mod util; pub mod web; pub mod webfinger; use crate::api_v1::create_api_router; use crate::host_meta::handle_host_meta; use crate::nodeinfo::{handle_nodeinfo, handle_nodeinfo_20, handle_nodeinfo_21}; use crate::service::MagnetarService; use axum::routing::get; use axum::Router; use dotenvy::dotenv; use magnetar_model::{CacheConnectorConfig, CalckeyCache, CalckeyModel, ConnectorConfig}; use miette::{miette, IntoDiagnostic}; use std::net::SocketAddr; use std::sync::Arc; use tokio::net::TcpListener; use tokio::signal; use tower_http::cors::{Any, CorsLayer}; use tower_http::trace::TraceLayer; use tracing::info; use tracing::log::error; use tracing_subscriber::EnvFilter; #[tokio::main] async fn main() -> miette::Result<()> { dotenv().ok(); let filter_layer = EnvFilter::try_from_default_env() .or_else(|_| EnvFilter::try_new("info")) .unwrap(); tracing_subscriber::fmt() .with_env_filter(filter_layer) .with_test_writer() .init(); let config = &*Box::leak::<'static>(Box::new( magnetar_common::config::load_config().into_diagnostic()?, )); info!("Loaded configuration: {config:#?}"); let db = CalckeyModel::new(ConnectorConfig { url: config.data.database_url.clone(), }) .await .into_diagnostic()?; db.migrate().await.into_diagnostic()?; let redis = CalckeyCache::new(CacheConnectorConfig { url: config.data.redis_url.clone(), }) .into_diagnostic()?; let service = Arc::new( MagnetarService::new(config, db.clone(), redis) .await .into_diagnostic()?, ); let well_known_router = Router::new() .route( "/webfinger", get(webfinger::handle_webfinger).with_state((config, db)), ) .route("/host-meta", get(handle_host_meta)) .route("/nodeinfo", get(handle_nodeinfo)); let nodeinfo_router = Router::new() .with_state(config) .route("/2.0", get(handle_nodeinfo_20)) .route("/2.1", get(handle_nodeinfo_21)); let app = Router::new() .nest("/.well-known", well_known_router.with_state(config)) .nest("/nodeinfo", nodeinfo_router.with_state(config)) .nest("/mag/v1", create_api_router(service)) .layer( CorsLayer::new() .allow_headers(Any) .allow_methods(Any) .allow_origin(Any), ) .layer(TraceLayer::new_for_http()); let addr = SocketAddr::from((config.networking.bind_addr, config.networking.port)); info!("Binding to: {addr}"); let listener = TcpListener::bind(addr).await.into_diagnostic()?; info!("Serving..."); axum::serve(listener, app.into_make_service()) .await .map_err(|e| miette!("Error running server: {}", e)) } // FIXME: Plug this back in when Axum reimplements graceful shutdown async fn shutdown_signal() { let ctrl_c = async { if let Err(e) = signal::ctrl_c().await { error!("Ctrl+C signal handler error: {}", e); } }; #[cfg(unix)] let terminate = async { signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("SIGTERM handler error") .recv() .await; }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => {}, _ = terminate => {}, } info!("Shutting down..."); }