//! GuruRMM Server - RMM Management Server //! //! Provides the backend API and WebSocket endpoint for GuruRMM agents. //! Features: //! - Agent registration and management //! - Real-time WebSocket communication with agents //! - Metrics storage and retrieval //! - Command execution via agents //! - Dashboard authentication mod api; mod auth; mod config; mod db; mod updates; mod ws; use std::sync::Arc; use anyhow::Result; use axum::{ routing::{get, post}, Router, }; use sqlx::postgres::PgPoolOptions; use tokio::sync::RwLock; use http::HeaderValue; use tower_http::cors::{AllowOrigin, CorsLayer}; use tower_http::trace::TraceLayer; use tracing::info; use crate::config::ServerConfig; use crate::updates::UpdateManager; use crate::ws::AgentConnections; /// Shared application state #[derive(Clone)] pub struct AppState { /// Database connection pool pub db: sqlx::PgPool, /// Server configuration pub config: Arc, /// Connected agents (WebSocket connections) pub agents: Arc>, /// Agent update manager pub updates: Arc, } #[tokio::main] async fn main() -> Result<()> { // Load environment variables from .env file dotenvy::dotenv().ok(); // Initialize logging tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::from_default_env() .add_directive("gururmm_server=info".parse()?) .add_directive("tower_http=debug".parse()?) .add_directive("info".parse()?), ) .init(); info!("GuruRMM Server starting..."); // Load configuration let config = ServerConfig::from_env()?; info!("Server configuration loaded"); // Connect to database info!("Connecting to database..."); let db = PgPoolOptions::new() .max_connections(config.database.max_connections) .connect(&config.database.url) .await?; info!("Database connected"); // Run migrations info!("Running database migrations..."); sqlx::migrate!("./migrations").run(&db).await?; info!("Migrations complete"); // Initialize update manager let update_manager = UpdateManager::new( config.updates.downloads_dir.clone(), config.updates.downloads_base_url.clone(), config.updates.auto_update_enabled, config.updates.update_timeout_secs, ); // Initial scan for available versions info!("Scanning for available agent versions..."); if let Err(e) = update_manager.scan_versions().await { tracing::warn!("Failed to scan agent versions: {} (continuing without auto-update)", e); } let update_manager = Arc::new(update_manager); // Spawn background scanner (handle is intentionally not awaited - runs until server shutdown) let _scanner_handle = update_manager.spawn_scanner(config.updates.scan_interval_secs); info!( "Auto-update: {} (scan interval: {}s)", if config.updates.auto_update_enabled { "enabled" } else { "disabled" }, config.updates.scan_interval_secs ); // Create shared state let state = AppState { db, config: Arc::new(config.clone()), agents: Arc::new(RwLock::new(AgentConnections::new())), updates: update_manager, }; // Build router let app = build_router(state); // Start server let addr = format!("{}:{}", config.server.host, config.server.port); info!("Starting server on {}", addr); let listener = tokio::net::TcpListener::bind(&addr).await?; axum::serve(listener, app).await?; Ok(()) } /// Build the application router fn build_router(state: AppState) -> Router { // TODO: Add rate limiting for registration endpoints using tower-governor // Currently, registration is protected by AuthUser authentication. // For additional protection against brute-force attacks, consider adding: // - tower-governor crate for per-IP rate limiting on /api/agents/register // - Configurable limits via environment variables // Reference: https://docs.rs/tower-governor/latest/tower_governor/ // CORS configuration - restrict to specific dashboard origin let dashboard_origin = std::env::var("DASHBOARD_URL") .unwrap_or_else(|_| "https://rmm.azcomputerguru.com".to_string()); let cors = CorsLayer::new() .allow_origin(AllowOrigin::exact( HeaderValue::from_str(&dashboard_origin).expect("Invalid DASHBOARD_URL"), )) .allow_methods([ http::Method::GET, http::Method::POST, http::Method::PUT, http::Method::DELETE, http::Method::OPTIONS, ]) .allow_headers([ http::header::AUTHORIZATION, http::header::CONTENT_TYPE, http::header::ACCEPT, ]) .allow_credentials(true); Router::new() // Health check .route("/health", get(health_check)) // WebSocket endpoint for agents .route("/ws", get(ws::ws_handler)) // API routes .nest("/api", api::routes()) // Middleware .layer(TraceLayer::new_for_http()) .layer(cors) // State .with_state(state) } /// Health check endpoint async fn health_check() -> &'static str { "OK" }