//! Hero Supervisor Binary use hero_supervisor::SupervisorBuilder; use clap::Parser; use log::{error, info}; /// Hero Supervisor - manages actors and dispatches jobs #[derive(Parser, Debug)] #[command(name = "supervisor")] #[command(about = "Hero Supervisor - manages actors and dispatches jobs")] struct Args { /// Redis URL for job queue #[arg(long, default_value = "redis://127.0.0.1:6379")] redis_url: String, /// Namespace for Redis keys #[arg(long, default_value = "")] namespace: String, /// Admin secrets (required, can be specified multiple times) #[arg(long = "admin-secret", value_name = "SECRET", required = true)] admin_secrets: Vec, /// User secrets (can be specified multiple times) #[arg(long = "user-secret", value_name = "SECRET")] user_secrets: Vec, /// Register secrets (can be specified multiple times) #[arg(long = "register-secret", value_name = "SECRET")] register_secrets: Vec, /// Port for OpenRPC HTTP server #[arg(long, default_value = "3030")] port: u16, /// Bind address for OpenRPC HTTP server #[arg(long, default_value = "127.0.0.1")] bind_address: String, /// Pre-configured runner names (comma-separated) #[arg(long, value_name = "NAMES", value_delimiter = ',')] runners: Vec, } #[tokio::main] async fn main() -> Result<(), Box> { env_logger::init(); let args = Args::parse(); // Build supervisor let mut builder = SupervisorBuilder::new() .admin_secrets(args.admin_secrets); if !args.user_secrets.is_empty() { builder = builder.user_secrets(args.user_secrets); } if !args.register_secrets.is_empty() { builder = builder.register_secrets(args.register_secrets); } let mut supervisor = builder.build().await?; // Register pre-configured runners if !args.runners.is_empty() { for runner_name in &args.runners { match supervisor.runner_create(runner_name.clone()).await { Ok(_) => {}, Err(e) => error!("Failed to register runner '{}': {}", runner_name, e), } } } // Start OpenRPC server use hero_supervisor::openrpc::start_http_openrpc_server; let supervisor_clone = supervisor.clone(); let bind_addr = args.bind_address.clone(); let port = args.port; tokio::spawn(async move { match start_http_openrpc_server(supervisor_clone, &bind_addr, port).await { Ok(handle) => { handle.stopped().await; error!("OpenRPC server stopped unexpectedly"); } Err(e) => { error!("OpenRPC server error: {}", e); } } }); tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; // Print startup info println!("📡 http://{}:{}", args.bind_address, args.port); info!("Hero Supervisor is running. Press Ctrl+C to shutdown."); // Set up graceful shutdown tokio::spawn(async move { tokio::signal::ctrl_c().await.expect("Failed to listen for ctrl+c"); info!("Received shutdown signal"); std::process::exit(0); }); // Keep the application running loop { tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; } }