- netboot-base.nix with SSH key auth - Launch scripts for node01/02/03 - Node configuration.nix and disko.nix - Nix modules for first-boot automation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
168 lines
5.4 KiB
Rust
168 lines
5.4 KiB
Rust
//! 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<String>,
|
|
|
|
/// Log level (overrides config)
|
|
#[arg(short, long)]
|
|
log_level: Option<String>,
|
|
|
|
/// Path to the Firecracker kernel image (overrides config)
|
|
#[arg(long)]
|
|
firecracker_kernel_path: Option<PathBuf>,
|
|
|
|
/// Path to the Firecracker rootfs image (overrides config)
|
|
#[arg(long)]
|
|
firecracker_rootfs_path: Option<PathBuf>,
|
|
|
|
/// Metrics port for Prometheus scraping
|
|
#[arg(long, default_value = "9095")]
|
|
metrics_port: u16,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
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::<std::net::SocketAddr>()?)
|
|
.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::<VmServiceServer<VmServiceImpl>>()
|
|
.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(())
|
|
}
|