This commit is contained in:
Maxime Van Hees
2025-08-14 14:14:34 +02:00
parent 04a1af2423
commit 0ebda7c1aa
59 changed files with 6950 additions and 354 deletions

View File

@@ -265,11 +265,11 @@
"params": [],
"result": {
"name": "jobList",
"description": "List of all jobs.",
"description": "List of all job IDs.",
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Job"
"type": "string"
}
}
}
@@ -343,7 +343,7 @@
},
"ScriptType": {
"type": "string",
"enum": ["HeroScript", "RhaiSAL", "RhaiDSL"],
"enum": ["OSIS", "SAL", "V", "Python"],
"description": "The type of script to execute."
},
"JobStatus": {

View File

@@ -38,12 +38,6 @@ enum Commands {
#[arg(long, default_value = "ws://127.0.0.1:9944")]
url: String,
},
/// Connect to Unix socket server
Unix {
/// Unix socket path
#[arg(long, default_value = "/tmp/hero-openrpc.sock")]
socket_path: PathBuf,
},
}
/// Available RPC methods with descriptions
@@ -161,10 +155,6 @@ async fn main() -> Result<()> {
println!("{} {}", "Connecting to WebSocket server:".green(), url.cyan());
ClientTransport::WebSocket(url)
}
Commands::Unix { socket_path } => {
println!("{} {:?}", "Connecting to Unix socket server:".green(), socket_path);
ClientTransport::Unix(socket_path)
}
};
// Connect to the server
@@ -282,15 +272,18 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
.with_prompt("Signature (hex)")
.interact_text()?;
let nonce: String = Input::new()
.with_prompt("Nonce (hex) - fetch via fetch_nonce first")
.interact_text()?;
let result = client.authenticate(pubkey, signature, nonce).await?;
println!("{} {}", "Authentication result:".green().bold(),
println!("{} {}", "Authentication result:".green().bold(),
if result { "Success".green() } else { "Failed".red() });
}
"whoami" => {
let result = client.whoami().await?;
println!("{} {}", "User info:".green().bold(),
serde_json::to_string_pretty(&result)?.cyan());
println!("{} {}", "User info:".green().bold(), result.cyan());
}
"play" => {
@@ -307,7 +300,7 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
.with_prompt("Script content")
.interact_text()?;
let script_types = ["HeroScript", "RhaiSAL", "RhaiDSL"];
let script_types = ["OSIS", "SAL", "V", "Python"];
let script_type_selection = Select::new()
.with_prompt("Script type")
.items(&script_types)
@@ -315,10 +308,10 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
.interact()?;
let script_type = match script_type_selection {
0 => ScriptType::HeroScript,
1 => ScriptType::RhaiSAL,
2 => ScriptType::RhaiDSL,
_ => ScriptType::HeroScript,
0 => ScriptType::OSIS,
1 => ScriptType::SAL,
2 => ScriptType::V,
_ => ScriptType::Python,
};
let add_prerequisites = Confirm::new()
@@ -335,9 +328,34 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
None
};
let caller_id: String = Input::new()
.with_prompt("Caller ID")
.interact_text()?;
let context_id: String = Input::new()
.with_prompt("Context ID")
.interact_text()?;
let specify_timeout = Confirm::new()
.with_prompt("Specify timeout (seconds)?")
.default(false)
.interact()?;
let timeout = if specify_timeout {
let t: u64 = Input::new()
.with_prompt("Timeout (seconds)")
.interact_text()?;
Some(t)
} else {
None
};
let job_params = JobParams {
script,
script_type,
caller_id,
context_id,
timeout,
prerequisites,
};
@@ -360,7 +378,7 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
.with_prompt("Script content")
.interact_text()?;
let script_types = ["HeroScript", "RhaiSAL", "RhaiDSL"];
let script_types = ["OSIS", "SAL", "V", "Python"];
let script_type_selection = Select::new()
.with_prompt("Script type")
.items(&script_types)
@@ -368,10 +386,10 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
.interact()?;
let script_type = match script_type_selection {
0 => ScriptType::HeroScript,
1 => ScriptType::RhaiSAL,
2 => ScriptType::RhaiDSL,
_ => ScriptType::HeroScript,
0 => ScriptType::OSIS,
1 => ScriptType::SAL,
2 => ScriptType::V,
_ => ScriptType::Python,
};
let add_prerequisites = Confirm::new()
@@ -416,18 +434,17 @@ async fn execute_method(client: &HeroOpenRpcClient, method_name: &str) -> Result
.interact_text()?;
let result = client.get_job_logs(job_id).await?;
println!("{} {}", "Job logs:".green().bold(), result.logs.cyan());
match result.logs {
Some(logs) => println!("{} {}", "Job logs:".green().bold(), logs.cyan()),
None => println!("{} {}", "Job logs:".green().bold(), "(no logs)".yellow()),
}
}
"list_jobs" => {
let result = client.list_jobs().await?;
println!("{}", "Jobs:".green().bold());
for job in result {
println!(" {} - {} ({:?})",
job.id().yellow(),
job.script_type(),
job.status()
);
println!("{}", "Job IDs:".green().bold());
for id in result {
println!(" {}", id.yellow());
}
}

View File

@@ -1,6 +1,6 @@
use anyhow::Result;
use async_trait::async_trait;
use hero_job::{Job, JobStatus, ScriptType};
use hero_job::{JobStatus, ScriptType};
use jsonrpsee::core::client::ClientT;
use jsonrpsee::core::ClientError;
use jsonrpsee::proc_macros::rpc;
@@ -37,7 +37,7 @@ pub trait OpenRpcClient {
) -> Result<bool, ClientError>;
#[method(name = "whoami")]
async fn whoami(&self) -> Result<serde_json::Value, ClientError>;
async fn whoami(&self) -> Result<String, ClientError>;
// Script execution
#[method(name = "play")]
@@ -68,7 +68,7 @@ pub trait OpenRpcClient {
async fn get_job_logs(&self, job_id: String) -> Result<JobLogsResult, ClientError>;
#[method(name = "list_jobs")]
async fn list_jobs(&self) -> Result<Vec<Job>, ClientError>;
async fn list_jobs(&self) -> Result<Vec<String>, ClientError>;
#[method(name = "stop_job")]
async fn stop_job(&self, job_id: String) -> Result<(), ClientError>;
@@ -146,7 +146,7 @@ impl HeroOpenRpcClient {
}
/// Delegate to whoami on the underlying client
pub async fn whoami(&self) -> Result<serde_json::Value, ClientError> {
pub async fn whoami(&self) -> Result<String, ClientError> {
self.client.whoami().await
}
@@ -191,7 +191,7 @@ impl HeroOpenRpcClient {
}
/// Delegate to list_jobs on the underlying client
pub async fn list_jobs(&self) -> Result<Vec<Job>, ClientError> {
pub async fn list_jobs(&self) -> Result<Vec<String>, ClientError> {
self.client.list_jobs().await
}

View File

@@ -1,11 +1,14 @@
use hero_job::ScriptType;
use serde::{Deserialize, Serialize};
/// Parameters for creating a job
/** Parameters for creating a job (must mirror server DTO) */
#[derive(Debug, Serialize, Deserialize)]
pub struct JobParams {
pub script: String,
pub script_type: ScriptType,
pub caller_id: String,
pub context_id: String,
pub timeout: Option<u64>, // seconds
pub prerequisites: Option<Vec<String>>,
}
@@ -21,8 +24,8 @@ pub struct StartJobResult {
pub success: bool,
}
/// Result of getting job logs
/** Result of getting job logs */
#[derive(Debug, Serialize, Deserialize)]
pub struct JobLogsResult {
pub logs: String,
pub logs: Option<String>,
}

View File

@@ -19,10 +19,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
clap = { version = "4.0", features = ["derive"] }
# JSON-RPC dependencies
jsonrpsee = { version = "0.21", features = [
"server",
"macros"
] }
jsonrpsee = { version = "0.21", features = ["server", "macros"] }
jsonrpsee-types = "0.21"
uuid = { version = "1.6", features = ["v4", "serde"] }
chrono = { version = "0.4", features = ["serde"] }

View File

@@ -8,7 +8,7 @@ use tracing_subscriber;
#[derive(Parser)]
#[command(name = "hero-openrpc-server")]
#[command(about = "Hero OpenRPC Server - WebSocket and Unix socket JSON-RPC server")]
#[command(about = "Hero OpenRPC Server - JSON-RPC over HTTP/WS")]
struct Cli {
#[command(subcommand)]
command: Commands,
@@ -34,12 +34,6 @@ enum Commands {
#[arg(long, default_value = "127.0.0.1:9944")]
addr: SocketAddr,
},
/// Start Unix socket server
Unix {
/// Unix socket path
#[arg(long, default_value = "/tmp/hero-openrpc.sock")]
socket_path: PathBuf,
},
}
#[tokio::main]
@@ -65,14 +59,6 @@ async fn main() -> Result<()> {
info!("Starting WebSocket server on {}", addr);
Transport::WebSocket(addr)
}
Commands::Unix { socket_path } => {
info!("Starting Unix socket server on {:?}", socket_path);
// Remove existing socket file if it exists
if socket_path.exists() {
std::fs::remove_file(&socket_path)?;
}
Transport::Unix(socket_path)
}
};
let config = OpenRpcServerConfig {

View File

@@ -1,6 +1,6 @@
use anyhow::Result;
use hero_job::{Job, JobBuilder, JobStatus, ScriptType};
use hero_supervisor::{Supervisor, SupervisorBuilder};
use hero_supervisor::{Supervisor, SupervisorBuilder, SupervisorError};
use jsonrpsee::core::async_trait;
use jsonrpsee::proc_macros::rpc;
use jsonrpsee::server::{ServerBuilder, ServerHandle};
@@ -12,17 +12,24 @@ use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::error;
fn map_sup_error_to_rpc(e: &SupervisorError) -> ErrorCode {
match e {
SupervisorError::InvalidInput(_) | SupervisorError::JobError(_) => ErrorCode::InvalidParams,
SupervisorError::Timeout(_) => ErrorCode::ServerError(-32002),
_ => ErrorCode::InternalError,
}
}
mod auth;
pub mod types;
pub use auth::*;
pub use types::*;
/// Transport type for the OpenRPC server
/** Transport type for the OpenRPC server */
#[derive(Debug, Clone)]
pub enum Transport {
WebSocket(SocketAddr),
Unix(PathBuf),
}
/// OpenRPC server configuration
@@ -82,7 +89,7 @@ pub trait OpenRpcApi {
async fn get_job_logs(&self, job_id: String) -> Result<JobLogsResult, ErrorCode>;
#[method(name = "list_jobs")]
async fn list_jobs(&self) -> Result<Vec<Job>, ErrorCode>;
async fn list_jobs(&self) -> Result<Vec<String>, ErrorCode>;
#[method(name = "stop_job")]
async fn stop_job(&self, job_id: String) -> Result<(), ErrorCode>;
@@ -114,8 +121,8 @@ impl OpenRpcServer {
})
}
/// Start the OpenRPC server
pub async fn start(self, config: OpenRpcServerConfig) -> Result<ServerHandle> {
/// Start the OpenRPC server on the given SocketAddr (HTTP/WS only)
pub async fn start_on(self, addr: SocketAddr) -> Result<ServerHandle> {
let mut module = RpcModule::new(());
// Register all the RPC methods
@@ -244,18 +251,17 @@ impl OpenRpcServer {
}
})?;
let server = ServerBuilder::default()
.build(addr)
.await?;
let handle = server.start(module);
Ok(handle)
}
/// Start the OpenRPC server (config wrapper)
pub async fn start(self, config: OpenRpcServerConfig) -> Result<ServerHandle> {
match config.transport {
Transport::WebSocket(addr) => {
let server = ServerBuilder::default()
.build(addr)
.await?;
let handle = server.start(module);
Ok(handle)
}
Transport::Unix(_path) => {
// Unix socket transport not yet implemented in jsonrpsee 0.21
return Err(anyhow::anyhow!("Unix socket transport not yet supported").into());
}
Transport::WebSocket(addr) => self.start_on(addr).await,
}
}
}
@@ -295,12 +301,8 @@ impl OpenRpcApiServer for OpenRpcServer {
}
async fn play(&self, script: String) -> Result<PlayResult, ErrorCode> {
let _supervisor = self.supervisor.read().await;
// For now, return a simple result since we need to implement execute_script method
Ok(PlayResult {
output: format!("Script executed: {}", script)
})
let output = self.run_job(script, ScriptType::SAL, None).await?;
Ok(PlayResult { output })
}
async fn create_job(&self, job_params: JobParams) -> Result<String, ErrorCode> {
@@ -360,10 +362,37 @@ impl OpenRpcApiServer for OpenRpcServer {
&self,
script: String,
script_type: ScriptType,
_prerequisites: Option<Vec<String>>,
prerequisites: Option<Vec<String>>,
) -> Result<String, ErrorCode> {
// For now, return a simple result
Ok(format!("Job executed with script: {} (type: {:?})", script, script_type))
let supervisor = self.supervisor.read().await;
// Build job with defaults and optional prerequisites
let mut builder = JobBuilder::new()
.caller_id("rpc-caller")
.context_id("rpc-context")
.script(&script)
.script_type(script_type)
.timeout(std::time::Duration::from_secs(30));
if let Some(prs) = prerequisites {
builder = builder.prerequisites(prs);
}
let job = match builder.build() {
Ok(j) => j,
Err(e) => {
error!("Failed to build job in run_job: {}", e);
return Err(ErrorCode::InvalidParams);
}
};
match supervisor.run_job_and_await_result(&job).await {
Ok(output) => Ok(output),
Err(e) => {
error!("run_job failed: {}", e);
Err(map_sup_error_to_rpc(&e))
}
}
}
async fn get_job_status(&self, job_id: String) -> Result<JobStatus, ErrorCode> {
@@ -373,7 +402,7 @@ impl OpenRpcApiServer for OpenRpcServer {
Ok(status) => Ok(status),
Err(e) => {
error!("Failed to get job status for {}: {}", job_id, e);
Err(ErrorCode::InvalidParams)
Err(map_sup_error_to_rpc(&e))
}
}
}
@@ -385,50 +414,29 @@ impl OpenRpcApiServer for OpenRpcServer {
Ok(output) => Ok(output.unwrap_or_else(|| "No output available".to_string())),
Err(e) => {
error!("Failed to get job output for {}: {}", job_id, e);
Err(ErrorCode::InvalidParams)
Err(map_sup_error_to_rpc(&e))
}
}
}
async fn get_job_logs(&self, job_id: String) -> Result<JobLogsResult, ErrorCode> {
// For now, return mock logs
Ok(JobLogsResult {
logs: format!("Logs for job {}", job_id),
})
let supervisor = self.supervisor.read().await;
match supervisor.get_job_logs(&job_id).await {
Ok(logs_opt) => Ok(JobLogsResult { logs: logs_opt }),
Err(e) => {
error!("Failed to get job logs for {}: {}", job_id, e);
Err(map_sup_error_to_rpc(&e))
}
}
}
async fn list_jobs(&self) -> Result<Vec<Job>, ErrorCode> {
async fn list_jobs(&self) -> Result<Vec<String>, ErrorCode> {
let supervisor = self.supervisor.read().await;
match supervisor.list_jobs().await {
Ok(job_ids) => {
// For now, create minimal Job objects with just the IDs
// In a real implementation, we'd need a supervisor.get_job() method
let jobs: Vec<Job> = job_ids.into_iter().map(|job_id| {
// Create a minimal job object - this is a temporary solution
// until supervisor.get_job() is implemented
Job {
id: job_id,
caller_id: "unknown".to_string(),
context_id: "unknown".to_string(),
script: "unknown".to_string(),
script_type: ScriptType::OSIS,
timeout: std::time::Duration::from_secs(30),
retries: 0,
concurrent: false,
log_path: None,
env_vars: std::collections::HashMap::new(),
prerequisites: Vec::new(),
dependents: Vec::new(),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
}
}).collect();
Ok(jobs)
},
Ok(job_ids) => Ok(job_ids),
Err(e) => {
error!("Failed to list jobs: {}", e);
Err(ErrorCode::InternalError)
Err(map_sup_error_to_rpc(&e))
}
}
}
@@ -440,7 +448,7 @@ impl OpenRpcApiServer for OpenRpcServer {
Ok(_) => Ok(()),
Err(e) => {
error!("Failed to stop job {}: {}", job_id, e);
Err(ErrorCode::InvalidParams)
Err(map_sup_error_to_rpc(&e))
}
}
}
@@ -452,7 +460,7 @@ impl OpenRpcApiServer for OpenRpcServer {
Ok(_) => Ok(()),
Err(e) => {
error!("Failed to delete job {}: {}", job_id, e);
Err(ErrorCode::InvalidParams)
Err(map_sup_error_to_rpc(&e))
}
}
}
@@ -464,7 +472,7 @@ impl OpenRpcApiServer for OpenRpcServer {
Ok(_) => Ok(()),
Err(e) => {
error!("Failed to clear all jobs: {}", e);
Err(ErrorCode::InternalError)
Err(map_sup_error_to_rpc(&e))
}
}
}

View File

@@ -24,8 +24,8 @@ pub struct StartJobResult {
pub success: bool,
}
/// Result of getting job logs
/** Result of getting job logs */
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JobLogsResult {
pub logs: String,
pub logs: Option<String>,
}

View File

@@ -204,13 +204,13 @@ async fn test_list_jobs() {
let result = server.list_jobs().await;
assert!(result.is_ok());
let jobs = result.unwrap();
assert!(jobs.len() >= 3); // Should have at least the 3 jobs we created
let job_ids = result.unwrap();
assert!(job_ids.len() >= 3); // Should have at least the 3 jobs we created
// Verify job structure
for job in jobs {
assert!(!job.id.is_empty());
assert!(uuid::Uuid::parse_str(&job.id).is_ok());
// Verify job IDs are valid UUIDs
for id in job_ids {
assert!(!id.is_empty());
assert!(uuid::Uuid::parse_str(&id).is_ok());
}
}
@@ -337,7 +337,10 @@ async fn test_get_job_logs() {
assert!(result.is_ok());
let logs_result = result.unwrap();
assert!(!logs_result.logs.is_empty());
match logs_result.logs {
Some(ref logs) => assert!(!logs.is_empty()),
None => {} // acceptable when no logs are available
}
}
#[tokio::test]

View File

@@ -1,6 +1,18 @@
[package]
name = "hero-client-unix"
version = "0.1.0"
edition = "2024"
edition = "2021"
[dependencies]
anyhow = "1.0"
clap = { version = "4.5", features = ["derive"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread", "time"] }
# JSON-RPC async client and params types
jsonrpsee = { version = "0.21", features = ["macros", "async-client"] }
jsonrpsee-types = "0.21"
# IPC transport
reth-ipc = { git = "https://github.com/paradigmxyz/reth", package = "reth-ipc" }

View File

@@ -1,3 +1,124 @@
fn main() {
println!("Hello, world!");
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use jsonrpsee::core::client::ClientT;
use jsonrpsee::rpc_params;
use reth_ipc::client::IpcClientBuilder;
use serde_json::Value;
use tracing_subscriber::EnvFilter;
/// Simple IPC (Unix socket) JSON-RPC client for manual testing.
///
/// Examples:
/// - Call method without params:
/// hero-client-unix --socket /tmp/baobab.ipc --method whoami
///
/// - Call method with positional params (as JSON array):
/// hero-client-unix --socket /tmp/baobab.ipc --method authenticate --params '["pubkey","signature","nonce"]'
///
/// - Call method with single object param:
/// hero-client-unix --socket /tmp/baobab.ipc --method create_job --params '{"job_id":"abc"}'
#[derive(Parser, Debug)]
#[command(name = "hero-client-unix", version, about = "IPC JSON-RPC client")]
struct Args {
/// Filesystem path to the Unix domain socket
#[arg(long, default_value = "/tmp/baobab.ipc", env = "HERO_IPC_SOCKET")]
socket: PathBuf,
/// JSON-RPC method name to call
#[arg(long)]
method: String,
/// JSON string for params. Either an array for positional params or an object for named params.
/// Defaults to [] (no params).
#[arg(long, default_value = "[]")]
params: String,
/// Log filter (e.g., info, debug, trace)
#[arg(long, default_value = "info", env = "RUST_LOG")]
log: String,
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(EnvFilter::new(args.log.clone()))
.try_init()
.expect("setting default subscriber failed");
let socket_str = args.socket.to_string_lossy().to_string();
let client = IpcClientBuilder::default().build(&socket_str).await?;
let params_value: Value = serde_json::from_str(&args.params)?;
// We deserialize responses to serde_json::Value for generality.
// You can set a concrete type instead if needed.
let result: Value = match params_value {
Value::Array(arr) => match arr.len() {
0 => client.request(&args.method, rpc_params![]).await?,
1 => client.request(&args.method, rpc_params![arr[0].clone()]).await?,
2 => client.request(&args.method, rpc_params![arr[0].clone(), arr[1].clone()]).await?,
3 => client
.request(&args.method, rpc_params![arr[0].clone(), arr[1].clone(), arr[2].clone()])
.await?,
4 => client
.request(
&args.method,
rpc_params![arr[0].clone(), arr[1].clone(), arr[2].clone(), arr[3].clone()],
)
.await?,
5 => client
.request(
&args.method,
rpc_params![
arr[0].clone(),
arr[1].clone(),
arr[2].clone(),
arr[3].clone(),
arr[4].clone()
],
)
.await?,
6 => client
.request(
&args.method,
rpc_params![
arr[0].clone(),
arr[1].clone(),
arr[2].clone(),
arr[3].clone(),
arr[4].clone(),
arr[5].clone()
],
)
.await?,
7 => client
.request(
&args.method,
rpc_params![
arr[0].clone(),
arr[1].clone(),
arr[2].clone(),
arr[3].clone(),
arr[4].clone(),
arr[5].clone(),
arr[6].clone()
],
)
.await?,
_ => {
// Fallback: send entire array as a single param to avoid combinatorial explosion.
// Adjust if your server expects strictly positional expansion beyond 7 items.
client.request(&args.method, rpc_params![Value::Array(arr)]).await?
}
},
// Single non-array param (object, string, number, etc.)
other => client.request(&args.method, rpc_params![other]).await?,
};
println!("{}", serde_json::to_string_pretty(&result)?);
Ok(())
}

View File

@@ -1,6 +1,14 @@
[package]
name = "hero-server-unix"
version = "0.1.0"
edition = "2024"
edition = "2021"
[dependencies]
anyhow = "1.0"
clap = { version = "4.5", features = ["derive"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "signal"] }
# Reuse the OpenRPC server crate that registers all methods and now supports IPC
hero-openrpc-server = { path = "../../openrpc/server" }

View File

@@ -1,3 +1,64 @@
fn main() {
println!("Hello, world!");
use std::path::PathBuf;
use clap::Parser;
use tracing_subscriber::EnvFilter;
use hero_openrpc_server::{OpenRpcServer, OpenRpcServerConfig, Transport};
/// IPC (Unix socket) JSON-RPC server launcher.
///
/// This binary starts the OpenRPC server over a Unix domain socket using the reth-ipc transport.
#[derive(Parser, Debug)]
#[command(name = "hero-server-unix", version, about = "Start the JSON-RPC IPC server")]
struct Args {
/// Filesystem path to the Unix domain socket
#[arg(long, default_value = "/tmp/baobab.ipc", env = "HERO_IPC_SOCKET")]
socket_path: PathBuf,
/// Optional path to a supervisor configuration file
#[arg(long)]
supervisor_config: Option<PathBuf>,
/// Database path (reserved for future use)
#[arg(long, default_value = "./db", env = "HERO_DB_PATH")]
db_path: PathBuf,
/// Log filter (e.g., info, debug, trace)
#[arg(long, default_value = "info", env = "RUST_LOG")]
log: String,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let args = Args::parse();
// Initialize tracing with provided log filter
tracing_subscriber::FmtSubscriber::builder()
.with_env_filter(EnvFilter::new(args.log.clone()))
.try_init()
.expect("setting default subscriber failed");
let cfg = OpenRpcServerConfig {
transport: Transport::Unix(args.socket_path.clone()),
supervisor_config_path: args.supervisor_config.clone(),
db_path: args.db_path.clone(),
};
// Build server state
let server = OpenRpcServer::new(cfg.clone()).await?;
// Start IPC server
let handle = server.start(cfg).await?;
tracing::info!(
"IPC server started on {} (press Ctrl+C to stop)",
args.socket_path.display()
);
// Run until stopped
tokio::spawn(handle.stopped());
tokio::signal::ctrl_c().await?;
tracing::info!("Shutting down IPC server");
Ok(())
}