use clap::Parser; use std::net::{IpAddr, SocketAddr}; use std::sync::Arc; use tracing::{error, info, warn}; use tracing_subscriber::{EnvFilter, fmt}; #[derive(Debug, Clone, Parser)] #[command( name = "herocoordinator", version, about = "Hero Coordinator CLI", long_about = None )] struct Cli { #[arg( long = "mycelium-ip", short = 'i', env = "MYCELIUM_IP", default_value = "127.0.0.1", help = "IP address where Mycelium JSON-RPC is listening (default: 127.0.0.1)" )] mycelium_ip: IpAddr, #[arg( long = "mycelium-port", short = 'p', env = "MYCELIUM_PORT", default_value_t = 9651u16, help = "Port for Mycelium JSON-RPC (default: 9651)" )] mycelium_port: u16, #[arg( long = "redis-addr", short = 'r', env = "REDIS_ADDR", default_value = "127.0.0.1:6379", help = "Socket address of Redis instance (default: 127.0.0.1:6379)" )] redis_addr: SocketAddr, #[arg( long = "api-http-ip", env = "API_HTTP_IP", default_value = "127.0.0.1", help = "Bind IP for HTTP JSON-RPC server (default: 127.0.0.1)" )] api_http_ip: IpAddr, #[arg( long = "api-http-port", env = "API_HTTP_PORT", default_value_t = 9652u16, help = "Bind port for HTTP JSON-RPC server (default: 9652)" )] api_http_port: u16, #[arg( long = "api-ws-ip", env = "API_WS_IP", default_value = "127.0.0.1", help = "Bind IP for WebSocket JSON-RPC server (default: 127.0.0.1)" )] api_ws_ip: IpAddr, #[arg( long = "api-ws-port", env = "API_WS_PORT", default_value_t = 9653u16, help = "Bind port for WebSocket JSON-RPC server (default: 9653)" )] api_ws_port: u16, } #[tokio::main] async fn main() { let cli = Cli::parse(); // Initialize tracing subscriber (pretty formatter; controlled by RUST_LOG) let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); tracing_subscriber::fmt() .with_env_filter(filter) .pretty() .with_target(true) .with_level(true) .init(); let http_addr = SocketAddr::new(cli.api_http_ip, cli.api_http_port); let ws_addr = SocketAddr::new(cli.api_ws_ip, cli.api_ws_port); // Initialize Redis driver let redis = herocoordinator::storage::RedisDriver::new(cli.redis_addr.to_string()) .await .expect("Failed to connect to Redis"); // Initialize Service let service = herocoordinator::service::AppService::new(redis); let service_for_router = service.clone(); // Shared application state let state = Arc::new(herocoordinator::rpc::AppState::new(service)); // Start router workers (auto-discovered contexts) { let base_url = format!("http://{}:{}", cli.mycelium_ip, cli.mycelium_port); let cfg = herocoordinator::router::RouterConfig { context_ids: Vec::new(), // ignored by start_router_auto concurrency: 32, base_url, topic: "supervisor.rpc".to_string(), transport_poll_interval_secs: 2, transport_poll_timeout_secs: 300, }; let _auto_handle = herocoordinator::router::start_router_auto(service_for_router, cfg); } // Build RPC modules for both servers let http_module = herocoordinator::rpc::build_module(state.clone()); let ws_module = herocoordinator::rpc::build_module(state.clone()); info!(%http_addr, %ws_addr, redis_addr=%cli.redis_addr, "Starting JSON-RPC servers"); // Start servers let _http_handle = herocoordinator::rpc::start_http(http_addr, http_module) .await .expect("Failed to start HTTP server"); let _ws_handle = herocoordinator::rpc::start_ws(ws_addr, ws_module) .await .expect("Failed to start WS server"); // Wait for Ctrl+C to terminate if let Err(e) = tokio::signal::ctrl_c().await { error!(error=%e, "Failed to listen for shutdown signal"); } info!("Shutdown signal received, exiting."); }