cvx_server/
main.rs

1//! `cvx-server` — ChronosVector server binary.
2//!
3//! Entry point responsible for:
4//! - Configuration loading from file or environment
5//! - Dependency injection and service wiring
6//! - Tokio runtime bootstrap
7//! - Graceful shutdown on SIGTERM/SIGINT
8
9use std::path::PathBuf;
10use std::sync::Arc;
11
12use cvx_api::router::build_router;
13use cvx_api::state::AppState;
14use cvx_core::CvxConfig;
15use tokio::net::TcpListener;
16use tracing_subscriber::EnvFilter;
17
18#[tokio::main]
19async fn main() -> anyhow::Result<()> {
20    // Initialize tracing
21    tracing_subscriber::fmt()
22        .with_env_filter(
23            EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")),
24        )
25        .init();
26
27    // Load configuration
28    let config = load_config()?;
29    let host = config.server.host.clone();
30    let port = config.server.port;
31    let addr = format!("{host}:{port}");
32
33    tracing::info!("Configuration loaded");
34    tracing::debug!(?config);
35
36    // Build application state
37    let state = Arc::new(AppState::new());
38    let app = build_router(state);
39
40    // Start server
41    let listener = TcpListener::bind(&addr).await?;
42    tracing::info!(
43        "ChronosVector v{} listening on {addr}",
44        env!("CARGO_PKG_VERSION")
45    );
46
47    axum::serve(listener, app)
48        .with_graceful_shutdown(shutdown_signal())
49        .await?;
50
51    tracing::info!("Server shut down gracefully");
52    Ok(())
53}
54
55/// Load configuration from file or environment, with defaults.
56fn load_config() -> anyhow::Result<CvxConfig> {
57    // Check for config file path
58    let config_path = std::env::var("CVX_CONFIG")
59        .map(PathBuf::from)
60        .ok()
61        .or_else(|| {
62            let default = PathBuf::from("config.toml");
63            default.exists().then_some(default)
64        });
65
66    let mut config = if let Some(path) = config_path {
67        let content = std::fs::read_to_string(&path)?;
68        tracing::info!("Loading config from {}", path.display());
69        CvxConfig::parse(&content)?
70    } else {
71        tracing::info!("Using default configuration");
72        CvxConfig::default()
73    };
74
75    // Environment overrides
76    if let Ok(host) = std::env::var("CVX_HOST") {
77        config.server.host = host;
78    }
79    if let Ok(port) = std::env::var("CVX_PORT") {
80        config.server.port = port.parse().unwrap_or(3000);
81    }
82
83    Ok(config)
84}
85
86async fn shutdown_signal() {
87    let ctrl_c = async {
88        tokio::signal::ctrl_c()
89            .await
90            .expect("failed to install Ctrl+C handler");
91    };
92
93    #[cfg(unix)]
94    let terminate = async {
95        tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
96            .expect("failed to install SIGTERM handler")
97            .recv()
98            .await;
99    };
100
101    #[cfg(not(unix))]
102    let terminate = std::future::pending::<()>();
103
104    tokio::select! {
105        () = ctrl_c => tracing::info!("Received Ctrl+C"),
106        () = terminate => tracing::info!("Received SIGTERM"),
107    }
108}