From 0245c0f9e99eb4b5189fa9e77f034174c72c7e42 Mon Sep 17 00:00:00 2001 From: smkrv <17809065+smkrv@users.noreply.github.com> Date: Mon, 16 Mar 2026 10:58:48 +0300 Subject: [PATCH] feat(orchestrator): read ORCHESTRATOR_PORT env var for configurable API port (#1113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(orchestrator): read ORCHESTRATOR_PORT env var for configurable API port The orchestrator internal API port was hardcoded to 50051 in two places (ContainerJobConfig and OrchestratorApi::start call), making it impossible to run multiple IronClaw instances on the same host — the second instance fails with "Address already in use". NETWORK_SECURITY.md already documents ORCHESTRATOR_PORT as configurable, and ContainerJobConfig.orchestrator_port is propagated to worker containers via IRONCLAW_ORCHESTRATOR_URL, but the env var was never actually read. Extract resolve_orchestrator_port() that reads ORCHESTRATOR_PORT and falls back to 50051. Includes tests for valid, invalid, and out-of-range values. * test: add ENV_LOCK mutex for env-var test serialization Address Gemini review: add std::sync::Mutex to serialize env var access across test threads. Keep unsafe blocks — required in Rust edition 2024 where std::env::set_var/remove_var are unsafe functions. --------- Co-authored-by: SMKRV --- src/orchestrator/mod.rs | 51 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/src/orchestrator/mod.rs b/src/orchestrator/mod.rs index 5e750ddf..b72f90ee 100644 --- a/src/orchestrator/mod.rs +++ b/src/orchestrator/mod.rs @@ -51,6 +51,15 @@ use crate::db::Database; use crate::llm::LlmProvider; use crate::secrets::SecretsStore; +/// Resolve the orchestrator port from the `ORCHESTRATOR_PORT` environment +/// variable, falling back to 50051. +fn resolve_orchestrator_port() -> u16 { + std::env::var("ORCHESTRATOR_PORT") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(50051) +} + /// Result of orchestrator setup, containing all handles needed by the agent. pub struct OrchestratorSetup { pub container_job_manager: Option>, @@ -101,11 +110,12 @@ pub async fn setup_orchestrator( let job_event_tx = Some(tx); let token_store = TokenStore::new(); + let orchestrator_port = resolve_orchestrator_port(); let job_config = ContainerJobConfig { image: config.sandbox.image.clone(), memory_limit_mb: config.sandbox.memory_limit_mb, cpu_shares: config.sandbox.cpu_shares, - orchestrator_port: 50051, + orchestrator_port, claude_code_api_key: std::env::var("ANTHROPIC_API_KEY").ok(), claude_code_oauth_token: crate::config::ClaudeCodeConfig::extract_oauth_token(), claude_code_model: config.claude_code.model.clone(), @@ -127,7 +137,7 @@ pub async fn setup_orchestrator( }; tokio::spawn(async move { - if let Err(e) = OrchestratorApi::start(orchestrator_state, 50051).await { + if let Err(e) = OrchestratorApi::start(orchestrator_state, orchestrator_port).await { tracing::error!("Orchestrator API failed: {}", e); } }); @@ -151,3 +161,40 @@ pub async fn setup_orchestrator( docker_status, } } + +#[cfg(test)] +mod tests { + use std::sync::Mutex; + + use super::*; + + /// Serialize access to `ORCHESTRATOR_PORT` env var across test threads. + static ENV_LOCK: Mutex<()> = Mutex::new(()); + + #[test] + fn resolve_orchestrator_port_from_env() { + let _guard = ENV_LOCK.lock().unwrap(); + + // Safety: env-var mutation requires unsafe in edition 2024; + // ENV_LOCK serializes concurrent access from other test threads. + + // Absent env var → default 50051 + unsafe { std::env::remove_var("ORCHESTRATOR_PORT") }; + assert_eq!(resolve_orchestrator_port(), 50051); + + // Valid custom port + unsafe { std::env::set_var("ORCHESTRATOR_PORT", "50052") }; + assert_eq!(resolve_orchestrator_port(), 50052); + + // Non-numeric value → fallback to default + unsafe { std::env::set_var("ORCHESTRATOR_PORT", "not_a_port") }; + assert_eq!(resolve_orchestrator_port(), 50051); + + // Out of u16 range → fallback to default + unsafe { std::env::set_var("ORCHESTRATOR_PORT", "99999") }; + assert_eq!(resolve_orchestrator_port(), 50051); + + // Cleanup + unsafe { std::env::remove_var("ORCHESTRATOR_PORT") }; + } +}