mod api_v1; pub mod host_meta; pub mod model; pub mod nodeinfo; mod rpc_v1; pub mod service; pub mod util; pub mod vars; 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 futures::{select, FutureExt}; use magnetar_common::config::{MagnetarConfig, MagnetarRpcSocketKind}; use magnetar_model::{CacheConnectorConfig, CalckeyCache, CalckeyModel, ConnectorConfig}; use miette::{miette, IntoDiagnostic}; use rpc_v1::create_rpc_router; use rpc_v1::proto::RpcSockAddr; use std::convert::Infallible; use std::future::Future; 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?); let shutdown_signal = shutdown_signal().shared(); select! { rpc_res = run_rpc(service.clone(), config, shutdown_signal.clone()).fuse() => rpc_res, web_res = run_web(service, config, shutdown_signal).fuse() => web_res } } async fn run_rpc( service: Arc, config: &'static MagnetarConfig, shutdown_signal: impl Future + Send + 'static, ) -> miette::Result<()> { let rpc_bind_addr = match &config.rpc.connection_settings { MagnetarRpcSocketKind::None => { std::future::pending::().await; unreachable!(); } MagnetarRpcSocketKind::Unix(path) => RpcSockAddr::Unix(path.clone()), MagnetarRpcSocketKind::Tcp(ip) => RpcSockAddr::Ip(*ip), }; let rpc = create_rpc_router(); rpc.run(service, rpc_bind_addr, Some(shutdown_signal)).await } async fn run_web( service: Arc, config: &'static MagnetarConfig, shutdown_signal: impl Future + Send + 'static, ) -> miette::Result<()> { let well_known_router = Router::new() .route( "/webfinger", get(webfinger::handle_webfinger).with_state((config, service.db.clone())), ) .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()) .with_graceful_shutdown(shutdown_signal) .await .map_err(|e| miette!("Error running server: {}", e)) } 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!("Received a signal to shut down..."); }