//! PlasmaVMC control plane server binary use clap::Parser; use metrics_exporter_prometheus::PrometheusBuilder; use plasmavmc_api::proto::vm_service_server::VmServiceServer; use plasmavmc_hypervisor::HypervisorRegistry; use plasmavmc_kvm::KvmBackend; use plasmavmc_firecracker::FireCrackerBackend; use plasmavmc_server::config::ServerConfig; use plasmavmc_server::VmServiceImpl; use std::net::SocketAddr; use std::path::PathBuf; use std::sync::Arc; use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig}; use tonic_health::server::health_reporter; use tracing_subscriber::EnvFilter; /// PlasmaVMC control plane server #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { /// Configuration file path #[arg(short, long, default_value = "plasmavmc.toml")] config: PathBuf, /// Address to listen on (overrides config) #[arg(short, long)] addr: Option, /// Log level (overrides config) #[arg(short, long)] log_level: Option, /// Path to the Firecracker kernel image (overrides config) #[arg(long)] firecracker_kernel_path: Option, /// Path to the Firecracker rootfs image (overrides config) #[arg(long)] firecracker_rootfs_path: Option, /// Metrics port for Prometheus scraping #[arg(long, default_value = "9095")] metrics_port: u16, } #[tokio::main] async fn main() -> Result<(), Box> { let args = Args::parse(); // Load configuration from file or use defaults let mut config = if args.config.exists() { let contents = tokio::fs::read_to_string(&args.config).await?; toml::from_str(&contents)? } else { tracing::info!("Config file not found: {}, using defaults", args.config.display()); ServerConfig::default() }; // Apply command line overrides if let Some(addr_str) = args.addr { config.addr = addr_str.parse()?; } if let Some(log_level) = args.log_level { config.log_level = log_level; } if let Some(kernel_path) = args.firecracker_kernel_path { config.firecracker.kernel_path = Some(kernel_path); } if let Some(rootfs_path) = args.firecracker_rootfs_path { config.firecracker.rootfs_path = Some(rootfs_path); } // Initialize tracing tracing_subscriber::fmt() .with_env_filter( EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(&config.log_level)), ) .init(); tracing::info!("Starting PlasmaVMC server on {}", config.addr); // Initialize Prometheus metrics exporter let metrics_addr = format!("0.0.0.0:{}", args.metrics_port); let builder = PrometheusBuilder::new(); builder .with_http_listener(metrics_addr.parse::()?) .install() .expect("Failed to install Prometheus metrics exporter"); tracing::info!( "Prometheus metrics available at http://{}/metrics", metrics_addr ); // Create hypervisor registry and register backends let registry = Arc::new(HypervisorRegistry::new()); // Register KVM backend (always available) let kvm_backend = Arc::new(KvmBackend::with_defaults()); registry.register(kvm_backend); // Register FireCracker backend if kernel/rootfs paths are configured if let (Some(kernel_path), Some(rootfs_path)) = (&config.firecracker.kernel_path, &config.firecracker.rootfs_path) { let firecracker_backend = FireCrackerBackend::with_defaults(kernel_path, rootfs_path); registry.register(Arc::new(firecracker_backend)); tracing::info!("Registered FireCracker backend"); } else { tracing::debug!("FireCracker backend not available (missing kernel/rootfs paths)"); } tracing::info!( "Registered hypervisors: {:?}", registry.available() ); // Create services let vm_service = VmServiceImpl::new(registry).await?; // Setup health service let (mut health_reporter, health_service) = health_reporter(); health_reporter .set_serving::>() .await; // Parse address let addr: SocketAddr = config.addr; tracing::info!("PlasmaVMC server listening on {}", addr); // Configure TLS if enabled let mut server = Server::builder(); if let Some(tls_config) = &config.tls { tracing::info!("TLS enabled, loading certificates..."); let cert = tokio::fs::read(&tls_config.cert_file).await?; let key = tokio::fs::read(&tls_config.key_file).await?; let server_identity = Identity::from_pem(cert, key); let tls = if tls_config.require_client_cert { tracing::info!("mTLS enabled"); let ca_cert = tokio::fs::read( tls_config .ca_file .as_ref() .ok_or("ca_file required for mTLS")?, ) .await?; let ca = Certificate::from_pem(ca_cert); ServerTlsConfig::new() .identity(server_identity) .client_ca_root(ca) } else { ServerTlsConfig::new().identity(server_identity) }; server = server.tls_config(tls)?; } // Start server server .add_service(health_service) .add_service(VmServiceServer::new(vm_service)) .serve(addr) .await?; Ok(()) }