Skip to main content

quorum_private/
main.rs

1//! This is the main entry point for the Quorum Private server.
2//!
3//! It sets up the server similar to the public server, tweaked to work with being self hostable by anyone.
4//! Use `cargo run -p quorum-private` to build/run the private version of the server, which combines the `quorum-core` shared code.
5
6mod cli;
7mod db;
8mod routes;
9
10use colored::Colorize;
11//use reqwest::Client;
12use crate::db::schema;
13use quorum_core::db as core_db;
14use quorum_core::startup;
15use routes::route::create_router;
16use std::net::SocketAddr;
17use std::time::Instant;
18
19#[tokio::main]
20async fn main() {
21    // Clear the terminal and move the cursor to top left corner
22    print!("\x1B[2J\x1B[1;1H");
23
24    startup::print_banner();
25    startup::print_initializing();
26
27    // Load configs from .env (only once)
28    let timer = startup::create_timer();
29    match quorum_core::utility::config::Config::load() {
30        Ok(_) => startup::print_step("Loading config", true, startup::elapsed(timer)),
31        Err(e) => {
32            startup::print_step("Loading config", false, startup::elapsed(timer));
33            eprintln!("{}", format!("  Error: {}", e).red());
34            std::process::exit(1);
35        }
36    };
37
38    // Load the config once and reuse it
39    let config = quorum_core::utility::config::Config::get();
40
41    // Load up and connect to the database
42    let timer = startup::create_timer();
43    let _db = match core_db::init().await {
44        Ok(db) => {
45            startup::print_step("Opening database", true, startup::elapsed(timer));
46            db
47        }
48        Err(e) => {
49            startup::print_step("Opening database", false, startup::elapsed(timer));
50            eprintln!("{}", format!("  Error: {}", e).red());
51            std::process::exit(1);
52        }
53    };
54
55    // Write the tables to the database if they don't exist
56    let timer = startup::create_timer();
57    match schema::init(&_db).await {
58        Ok(_) => {
59            startup::print_step("Initializing schema", true, startup::elapsed(timer));
60            let _ = core_db::queries::server_logs::log_startup(
61                &_db,
62                startup::elapsed(timer).as_millis() as i64,
63            )
64            .await;
65        }
66        Err(e) => {
67            startup::print_step("Initializing schema", false, startup::elapsed(timer));
68            eprintln!("{}", format!("  Error: {}", e).red());
69            let _ = core_db::queries::server_logs::log_error(&_db, e.to_string(), 0).await;
70            std::process::exit(1);
71        }
72    }
73
74    startup::print_ready(config.server_port);
75
76    // Tracks true server uptime from this point forward, used by server:status and server:shutdown.
77    // Intentionally created after all startup steps have completed.
78    let server_start = Instant::now();
79
80    // Shutdown channel — CLI sends true on server:shutdown, main select! receives it
81    let (shutdown_tx, mut shutdown_rx) = tokio::sync::watch::channel(false);
82
83    // Start the router
84    let app = create_router(_db.clone());
85    let host = config
86        .server_host
87        .parse::<std::net::IpAddr>()
88        .expect("Invalid SERVER_HOST IP address");
89    let addr = SocketAddr::from((host, config.server_port));
90    let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
91    let server = axum::serve(
92        listener,
93        app.into_make_service_with_connect_info::<SocketAddr>(),
94    );
95
96    let server_task = tokio::spawn(async move {
97        server
98            .with_graceful_shutdown(async move {
99                let _ = shutdown_rx.changed().await;
100            })
101            .await
102    });
103
104    //this is needed to know when tests should Run
105    //until tests for prviate hostings are known and developed, this will remain commented out
106    /*let health_url = format!(
107        "http://{}:{}/health",
108        config.server_host, config.server_port
109    );
110    let client = Client::new();
111    let mut is_ready = false;
112    let max_retries = 20;
113    let retry_delay = Duration::from_millis(50);
114
115    for _ in 0..max_retries {
116        match client.get(&health_url).send().await {
117            Ok(response) if response.status().is_success() => {
118                is_ready = true;
119                break;
120            }
121            Ok(_) => {
122                tokio::time::sleep(retry_delay).await;
123                continue;
124            }
125            Err(_) => {
126                tokio::time::sleep(retry_delay).await;
127                continue;
128            }
129        }
130    }
131
132    if !is_ready {
133        eprintln!("{}", "Server failed to start in time.".red());
134        std::process::exit(1);
135    }
136
137    // Run the tests if enabled
138    if config.enable_testing {
139        tests::run_all_tests().await;
140    }*/
141
142    // Start the CLI input loop in a background task
143    cli::spawn_cli(_db.clone(), server_start, shutdown_tx).await;
144
145    let shutdown = async {
146        let _ = tokio::signal::ctrl_c().await;
147    };
148
149    tokio::select! {
150        _ = server_task => {},
151        _ = shutdown => {
152            println!("\nShutting down...");
153            let _ = quorum_core::db::queries::server_logs::log_shutdown(&_db, server_start.elapsed().as_millis() as i64).await;
154        }
155    }
156}