1use axum::{
2 Router,
3 extract::DefaultBodyLimit,
4 middleware,
5 routing::{get, post},
6};
7use std::net::SocketAddr;
8use tokio::signal;
9use tower_cookies::CookieManagerLayer;
10use tower_http::trace::TraceLayer;
11
12use mini_apm::{DbPool, config::Config, jobs, models};
13use crate::{api, web};
14
15#[derive(Clone)]
17pub struct AppState {
18 pub pool: DbPool,
19 pub config: Config,
20}
21
22const MAX_BODY_SIZE: usize = 10 * 1024 * 1024;
24
25pub async fn run(pool: DbPool, config: Config, port: u16) -> anyhow::Result<()> {
26 api::health::init_start_time();
28
29 let default_project = models::project::ensure_default_project(&pool)?;
32 models::user::ensure_default_admin(&pool)?;
33
34 if !config.enable_projects {
35 tracing::info!("Single-project mode - API key: {}", default_project.api_key);
36 }
37
38 jobs::start(pool.clone(), config.clone());
40
41 let app = Router::new()
43 .route("/health", get(api::health_handler))
45 .nest(
47 "/ingest",
48 Router::new()
49 .route("/deploys", post(api::ingest_deploys))
50 .route("/v1/traces", post(api::ingest_spans))
51 .route("/errors", post(api::ingest_errors))
52 .route("/errors/batch", post(api::ingest_errors_batch))
53 .layer(middleware::from_fn_with_state(
54 pool.clone(),
55 api::auth_middleware,
56 )),
57 )
58 .merge(web::auth_routes())
60 .merge(web::routes(pool.clone()))
62 .nest_service("/static", tower_http::services::ServeDir::new("static"))
64 .with_state(pool)
66 .layer(DefaultBodyLimit::max(MAX_BODY_SIZE))
67 .layer(CookieManagerLayer::new())
68 .layer(TraceLayer::new_for_http());
69
70 let addr = SocketAddr::from(([0, 0, 0, 0], port));
71 tracing::info!("MiniAPM server listening on http://{}", addr);
72
73 if config.enable_user_accounts {
74 tracing::info!("User accounts ENABLED - login required");
75 }
76
77 let listener = tokio::net::TcpListener::bind(addr).await?;
78
79 axum::serve(listener, app)
81 .with_graceful_shutdown(shutdown_signal())
82 .await?;
83
84 tracing::info!("Server shutdown complete");
85 Ok(())
86}
87
88async fn shutdown_signal() {
89 let ctrl_c = async {
90 signal::ctrl_c()
91 .await
92 .expect("Failed to install Ctrl+C handler");
93 };
94
95 #[cfg(unix)]
96 let terminate = async {
97 signal::unix::signal(signal::unix::SignalKind::terminate())
98 .expect("Failed to install SIGTERM handler")
99 .recv()
100 .await;
101 };
102
103 #[cfg(not(unix))]
104 let terminate = std::future::pending::<()>();
105
106 tokio::select! {
107 _ = ctrl_c => {
108 tracing::info!("Received Ctrl+C, starting graceful shutdown...");
109 }
110 _ = terminate => {
111 tracing::info!("Received SIGTERM, starting graceful shutdown...");
112 }
113 }
114}