//! PlasmaVMC control plane server binary use clap::Parser; use plasmavmc_api::proto::vm_service_server::VmServiceServer; use plasmavmc_hypervisor::HypervisorRegistry; use plasmavmc_kvm::KvmBackend; use plasmavmc_firecracker::FireCrackerBackend; use plasmavmc_server::config::{self, ServerConfig}; use plasmavmc_server::VmServiceImpl; use std::net::SocketAddr; use std::path::PathBuf; use std::sync::Arc; use tonic::transport::Server; 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, } #[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); // 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(firecracker_backend) = FireCrackerBackend::new_from_config(&config.firecracker) { 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); // Start server Server::builder() .add_service(health_service) .add_service(VmServiceServer::new(vm_service)) .serve(addr) .await?; Ok(()) }