use clap::{Parser, Subcommand}; use anyhow::Result; mod hardware; mod config; mod api; mod events; mod app; mod camera; mod detection; mod storage; mod communication; mod hardware_fingerprint; mod device_registration; mod websocket_client; use hardware::get_hardware_id; use config::{Config, ConfigManager}; use api::ApiClient; use app::Application; #[derive(Parser)] #[command(name = "meteor-edge-client")] #[command(about = "Meteor Edge Client - Running on edge hardware")] #[command(version = "0.1.0")] struct Cli { #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { /// Show version information Version, /// Register device with JWT token Register { /// JWT token for device registration token: String, /// Backend API URL (optional, defaults to http://localhost:3000) #[arg(long, default_value = "http://localhost:3000")] api_url: String, }, /// Interactive device registration RegisterDevice { /// Backend API URL #[arg(long, default_value = "http://localhost:3000")] api_url: String, /// Device name (optional) #[arg(long)] device_name: Option, /// Device location (latitude,longitude) #[arg(long)] location: Option, }, /// Test hardware fingerprinting TestFingerprint, /// Show device status and configuration Status, /// Check backend connectivity Health { /// Backend API URL (optional, defaults to http://localhost:3000) #[arg(long, default_value = "http://localhost:3000")] api_url: String, }, /// Run the edge client application Run, } #[tokio::main] async fn main() -> Result<()> { let cli = Cli::parse(); match &cli.command { Commands::Version => { println!("meteor-edge-client v{}", env!("CARGO_PKG_VERSION")); } Commands::Register { token, api_url } => { if let Err(e) = register_device(token.clone(), api_url.clone()).await { eprintln!("❌ Registration failed: {}", e); std::process::exit(1); } } Commands::RegisterDevice { api_url, device_name, location } => { if let Err(e) = register_device_interactive(api_url.clone(), device_name.clone(), location.clone()).await { eprintln!("❌ Device registration failed: {}", e); std::process::exit(1); } } Commands::TestFingerprint => { if let Err(e) = test_hardware_fingerprint().await { eprintln!("❌ Fingerprint test failed: {}", e); std::process::exit(1); } } Commands::Status => { show_status().await?; } Commands::Health { api_url } => { if let Err(e) = check_health(api_url.clone()).await { eprintln!("❌ Health check failed: {}", e); std::process::exit(1); } } Commands::Run => { if let Err(e) = run_application().await { eprintln!("❌ Application failed: {}", e); std::process::exit(1); } } } Ok(()) } /// Registers the device with the backend using the provided JWT token async fn register_device(jwt_token: String, api_url: String) -> Result<()> { println!("🚀 Starting device registration process..."); let config_manager = ConfigManager::new(); // Check if device is already registered if config_manager.config_exists() { match config_manager.load_config() { Ok(config) if config.registered => { println!("✅ Device is already registered!"); println!(" Hardware ID: {}", config.hardware_id); if let Some(user_id) = &config.user_profile_id { println!(" Device ID: {}", config.device_id); println!(" User Profile ID: {}", user_id); } if let Some(registered_at) = &config.registered_at { println!(" Registered at: {}", registered_at); } println!(" Config file: {:?}", config_manager.get_config_path()); return Ok(()); } _ => { println!("📝 Found existing config file, but device not fully registered. Continuing..."); } } } // Get hardware ID println!("🔍 Reading hardware identifier..."); let hardware_id = get_hardware_id()?; println!(" Hardware ID: {}", hardware_id); // Create API client let api_client = ApiClient::new(api_url); // Verify backend connectivity first println!("🏥 Checking backend connectivity..."); api_client.health_check().await?; // Attempt registration println!("📡 Registering device with backend..."); let registration_response = api_client .register_device(hardware_id.clone(), jwt_token.clone()) .await?; // Save configuration println!("💾 Saving registration configuration..."); let mut config = Config::new(hardware_id); config.mark_registered( registration_response.device.user_profile_id, registration_response.device.id, jwt_token, ); config_manager.save_config(&config)?; println!("🎉 Device registration completed successfully!"); println!(" Device ID: {}", config.device_id); println!(" Config saved to: {:?}", config_manager.get_config_path()); Ok(()) } /// Shows the current device status and configuration async fn show_status() -> Result<()> { println!("📊 Meteor Edge Client Status"); println!("============================"); // Show hardware information match get_hardware_id() { Ok(hardware_id) => { println!("🔧 Hardware ID: {}", hardware_id); } Err(e) => { println!("❌ Could not read hardware ID: {}", e); } } // Show configuration status let config_manager = ConfigManager::new(); println!("📁 Config file: {:?}", config_manager.get_config_path()); if config_manager.config_exists() { match config_manager.load_config() { Ok(config) => { if config.registered { println!("✅ Registration Status: REGISTERED"); println!(" Device ID: {}", config.device_id); if let Some(user_id) = &config.user_profile_id { println!(" User Profile ID: {}", user_id); } if let Some(registered_at) = &config.registered_at { println!(" Registered at: {}", registered_at); } } else { println!("⚠️ Registration Status: NOT REGISTERED"); println!(" Use 'register ' command to register this device"); } } Err(e) => { println!("❌ Could not load config: {}", e); } } } else { println!("⚠️ Registration Status: NOT REGISTERED"); println!(" No configuration file found"); println!(" Use 'register ' command to register this device"); } Ok(()) } /// Checks backend health and connectivity async fn check_health(api_url: String) -> Result<()> { println!("🏥 Checking backend health at: {}", api_url); let api_client = ApiClient::new(api_url); api_client.health_check().await?; println!("✅ Backend is healthy and reachable!"); Ok(()) } /// Run the main application async fn run_application() -> Result<()> { // Load configuration first let config_manager = ConfigManager::new(); let config = if config_manager.config_exists() { config_manager.load_config()? } else { eprintln!("❌ Device not registered. Use 'register ' command first."); std::process::exit(1); }; if !config.registered { eprintln!("❌ Device not registered. Use 'register ' command first."); std::process::exit(1); } println!("🎯 Initializing Meteor Edge Client..."); // Create the application let mut app = Application::new(1000); println!("📊 Application Statistics:"); println!(" Event Bus Capacity: 1000"); println!(" Initial Subscribers: {}", app.subscriber_count()); // Run the application app.run().await } /// Interactive device registration process async fn register_device_interactive(api_url: String, device_name: Option, location: Option) -> Result<()> { use device_registration::{DeviceRegistrationClient, RegistrationConfig, Location}; println!("🚀 Starting interactive device registration..."); // Parse location if provided let parsed_location = if let Some(loc_str) = location { let coords: Vec<&str> = loc_str.split(',').collect(); if coords.len() >= 2 { if let (Ok(lat), Ok(lon)) = (coords[0].trim().parse::(), coords[1].trim().parse::()) { Some(Location { latitude: lat, longitude: lon, altitude: None, accuracy: None }) } else { eprintln!("⚠️ Invalid location format. Use: latitude,longitude"); None } } else { eprintln!("⚠️ Invalid location format. Use: latitude,longitude"); None } } else { None }; // Create registration configuration let mut config = RegistrationConfig::default(); config.api_url = api_url; config.device_name = device_name; config.location = parsed_location; // Create registration client let mut client = DeviceRegistrationClient::new(config); // Add state change callback client.on_state_change(|state| { println!("📍 Registration State: {:?}", state); }); // Start registration process match client.start_registration().await { Ok(()) => { println!("🎉 Device registration completed successfully!"); if let Some(credentials) = client.credentials() { println!(" Device ID: {}", credentials.device_id); println!(" Token: {}...", &credentials.device_token[..20]); println!(" Certificate generated and stored"); // Save credentials to config file let config_data = client.export_config()?; let config_path = dirs::config_dir() .unwrap_or_else(|| std::path::PathBuf::from(".")) .join("meteor-edge-client") .join("registration.json"); std::fs::create_dir_all(config_path.parent().unwrap())?; std::fs::write(&config_path, serde_json::to_string_pretty(&config_data)?)?; println!(" Configuration saved to: {:?}", config_path); } } Err(e) => { eprintln!("❌ Registration failed: {}", e); return Err(e); } } Ok(()) } /// Tests hardware fingerprinting functionality async fn test_hardware_fingerprint() -> Result<()> { use hardware_fingerprint::HardwareFingerprintService; println!("🔍 Testing hardware fingerprinting..."); let mut service = HardwareFingerprintService::new(); let fingerprint = service.generate_fingerprint().await?; println!("✅ Hardware fingerprint generated successfully!"); println!(""); println!("Hardware Information:"); println!(" CPU ID: {}", fingerprint.cpu_id); println!(" Board Serial: {}", fingerprint.board_serial); println!(" MAC Addresses: {}", fingerprint.mac_addresses.join(", ")); println!(" Disk UUID: {}", fingerprint.disk_uuid); if let Some(tmp) = &fingerprint.tmp_attestation { println!(" TPM Attestation: {}...", &tmp[..20]); } else { println!(" TPM Attestation: Not available"); } println!(" Computed Hash: {}", fingerprint.computed_hash); println!(""); println!("System Information:"); println!(" Hostname: {}", fingerprint.system_info.hostname); println!(" OS: {} {}", fingerprint.system_info.os_name, fingerprint.system_info.os_version); println!(" Kernel: {}", fingerprint.system_info.kernel_version); println!(" Architecture: {}", fingerprint.system_info.architecture); println!(" Memory: {} MB total, {} MB available", fingerprint.system_info.total_memory / 1024 / 1024, fingerprint.system_info.available_memory / 1024 / 1024); println!(" CPU: {} cores, {}", fingerprint.system_info.cpu_count, fingerprint.system_info.cpu_brand); println!(" Disks: {} mounted", fingerprint.system_info.disk_info.len()); // Test fingerprint validation println!(""); println!("🔐 Testing fingerprint validation..."); let is_valid = service.validate_fingerprint(&fingerprint)?; if is_valid { println!("✅ Fingerprint validation: PASSED"); } else { println!("❌ Fingerprint validation: FAILED"); } // Test consistency println!(""); println!("🔄 Testing fingerprint consistency..."); let fingerprint2 = service.generate_fingerprint().await?; if fingerprint.computed_hash == fingerprint2.computed_hash { println!("✅ Fingerprint consistency: PASSED"); } else { println!("❌ Fingerprint consistency: FAILED"); println!(" First: {}", fingerprint.computed_hash); println!(" Second: {}", fingerprint2.computed_hash); } Ok(()) }