mod controllers; mod routes; mod utils; mod sync; use anyhow::Result; use axum::{ routing::{delete, get, post, put}, Router, }; use routes::{accounts, admin, auth, config, machines, setup, snapshots}; use std::path::Path; use tokio::signal; use tower_http::{ cors::CorsLayer, services::{ServeDir, ServeFile}, }; use utils::init_database; use sync::{SyncServer, server::SyncServerConfig}; #[tokio::main] async fn main() -> Result<()> { let pool = init_database().await?; let sync_pool = pool.clone(); let api_routes = Router::new() .route("/setup/status", get(setup::get_setup_status)) .route("/setup/init", post(setup::init_setup)) .route("/auth/login", post(auth::login)) .route("/auth/logout", post(auth::logout)) .route("/accounts/me", get(accounts::me)) .route("/admin/users", get(admin::get_users)) .route("/admin/users", post(admin::create_user_handler)) .route("/admin/users/{id}", put(admin::update_user_handler)) .route("/admin/users/{id}", delete(admin::delete_user_handler)) .route("/admin/config", get(config::get_all_configs)) .route("/admin/config", post(config::set_config)) .route("/admin/config/{key}", get(config::get_config)) .route("/machines/register", post(machines::register_machine)) .route("/machines/provisioning-code", post(machines::create_provisioning_code)) .route("/machines", get(machines::get_machines)) .route("/machines/{id}", get(machines::get_machine)) .route("/machines/{id}", delete(machines::delete_machine)) .route("/machines/{id}/snapshots", get(snapshots::get_machine_snapshots)) .route("/machines/{machine_id}/snapshots/{snapshot_id}", get(snapshots::get_snapshot_details)) .layer(CorsLayer::permissive()) .with_state(pool); let dist_path = "./dist"; let app = Router::new() .nest("/api", api_routes) .nest_service("/assets", ServeDir::new(format!("{}/assets", dist_path))) .route_service("/", ServeFile::new(format!("{}/index.html", dist_path))) .fallback_service(ServeFile::new(format!("{}/index.html", dist_path))) .layer(CorsLayer::permissive()); if !Path::new(dist_path).exists() { println!("Warning: dist directory not found at {}", dist_path); } let sync_config = SyncServerConfig::default(); let sync_server = SyncServer::new(sync_config.clone(), sync_pool); tokio::spawn(async move { if let Err(e) = sync_server.start().await { eprintln!("Sync server error: {}", e); } }); let listener = tokio::net::TcpListener::bind("0.0.0.0:8379").await?; println!("HTTP server running on http://0.0.0.0:8379"); println!("Sync server running on {}:{}", sync_config.bind_address, sync_config.port); axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await?; Ok(()) } async fn shutdown_signal() { let ctrl_c = async { signal::ctrl_c() .await .expect("failed to install Ctrl+C handler"); }; #[cfg(unix)] let terminate = async { signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("failed to install signal handler") .recv() .await; }; #[cfg(not(unix))] let terminate = std::future::pending::<()>(); tokio::select! { _ = ctrl_c => { println!("\nShutting down due to Ctrl+C..."); }, _ = terminate => { println!("\nShutting down due to terminate signal..."); }, } }