grabbit 13ce6ae442 feat: implement complete edge device registration system
- Add hardware fingerprinting with cross-platform support
- Implement secure device registration flow with X.509 certificates
- Add WebSocket real-time communication for device status
- Create comprehensive device management dashboard
- Establish zero-trust security architecture with multi-layer protection
- Add database migrations for device registration entities
- Implement Rust edge client with hardware identification
- Add certificate management and automated provisioning system

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-13 08:46:25 +08:00

403 lines
14 KiB
Rust

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<String>,
/// Device location (latitude,longitude)
#[arg(long)]
location: Option<String>,
},
/// 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 <token>' 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 <token>' 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 <token>' command first.");
std::process::exit(1);
};
if !config.registered {
eprintln!("❌ Device not registered. Use 'register <token>' 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<String>, location: Option<String>) -> 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::<f64>(), coords[1].trim().parse::<f64>()) {
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(())
}