diff --git a/crates/goose/src/temporal_scheduler.rs b/crates/goose/src/temporal_scheduler.rs index 722cf42a..a2d54b97 100644 --- a/crates/goose/src/temporal_scheduler.rs +++ b/crates/goose/src/temporal_scheduler.rs @@ -13,9 +13,11 @@ use crate::scheduler::{ScheduledJob, SchedulerError}; use crate::scheduler_trait::SchedulerTrait; use crate::session::storage::SessionMetadata; -const TEMPORAL_SERVICE_URL: &str = "http://localhost:8080"; -const TEMPORAL_SERVICE_STARTUP_TIMEOUT: Duration = Duration::from_secs(30); -const TEMPORAL_SERVICE_HEALTH_CHECK_INTERVAL: Duration = Duration::from_secs(2); +const TEMPORAL_SERVICE_STARTUP_TIMEOUT: Duration = Duration::from_secs(15); +const TEMPORAL_SERVICE_HEALTH_CHECK_INTERVAL: Duration = Duration::from_millis(500); + +// Default ports to try when discovering the service +const DEFAULT_HTTP_PORTS: &[u16] = &[8080, 8081, 8082, 8083, 8084, 8085]; #[derive(Serialize, Deserialize, Debug)] struct JobRequest { @@ -50,46 +52,290 @@ struct RunNowResponse { session_id: String, } +#[derive(Serialize, Deserialize, Debug)] +pub struct PortConfig { + http_port: u16, + temporal_port: u16, + ui_port: u16, +} + pub struct TemporalScheduler { http_client: Client, service_url: String, + port_config: PortConfig, } impl TemporalScheduler { pub async fn new() -> Result, SchedulerError> { let http_client = Client::new(); - let service_url = TEMPORAL_SERVICE_URL.to_string(); + // Discover the HTTP port + let http_port = Self::discover_http_port(&http_client).await?; + let service_url = format!("http://localhost:{}", http_port); + + info!("Found Temporal service HTTP API on port {}", http_port); + + // Create scheduler with initial port config let scheduler = Arc::new(Self { - http_client, - service_url, + http_client: http_client.clone(), + service_url: service_url.clone(), + port_config: PortConfig { + http_port, + temporal_port: 7233, // temporary defaults + ui_port: 8233, + }, }); - // Start the Go service (which will handle starting Temporal server) + // Start the Go service if not already running scheduler.start_go_service().await?; // Wait for service to be ready scheduler.wait_for_service_ready().await?; + // Fetch the actual port configuration and update + let port_config = scheduler.fetch_port_config().await?; + + info!( + "Discovered Temporal service ports - HTTP: {}, Temporal: {}, UI: {}", + port_config.http_port, port_config.temporal_port, port_config.ui_port + ); + + // Create final scheduler with correct ports + let final_scheduler = Arc::new(Self { + http_client, + service_url, + port_config, + }); + info!("TemporalScheduler initialized successfully"); - Ok(scheduler) + Ok(final_scheduler) + } + + async fn discover_http_port(_http_client: &Client) -> Result { + // First, try to find a running service using pgrep and lsof + if let Ok(port) = Self::find_temporal_service_port_from_processes() { + info!( + "Found Temporal service port {} from running processes", + port + ); + return Ok(port); + } + + // If no running service found, we need to find a free port to start the service on + info!("No running Temporal service found, finding free port to start service"); + + // Check PORT environment variable first + if let Ok(port_str) = std::env::var("PORT") { + if let Ok(port) = port_str.parse::() { + if Self::is_port_free(port).await { + info!("Using PORT environment variable: {}", port); + return Ok(port); + } else { + warn!( + "PORT environment variable {} is not free, finding alternative", + port + ); + } + } + } + + // Try to find a free port from the default list + for &port in DEFAULT_HTTP_PORTS { + if Self::is_port_free(port).await { + info!("Found free port {} for Temporal service", port); + return Ok(port); + } + } + + // If all default ports are taken, find any free port in a reasonable range + for port in 8086..8200 { + if Self::is_port_free(port).await { + info!("Found free port {} for Temporal service", port); + return Ok(port); + } + } + + Err(SchedulerError::SchedulerInternalError( + "Could not find any free port for Temporal service".to_string(), + )) + } + + async fn is_port_free(port: u16) -> bool { + use std::net::{SocketAddr, TcpListener}; + use std::time::Duration; + + let addr: SocketAddr = format!("127.0.0.1:{}", port).parse().unwrap(); + + // First, try to bind to the port + let listener_result = TcpListener::bind(addr); + match listener_result { + Ok(listener) => { + // Successfully bound, so port was free + drop(listener); // Release the port immediately + + // Double-check by trying to connect to see if anything is actually listening + let client = reqwest::Client::builder() + .timeout(Duration::from_millis(500)) + .build() + .unwrap(); + + let test_url = format!("http://127.0.0.1:{}", port); + match client.get(&test_url).send().await { + Ok(_) => { + // Something responded, so port is actually in use + warn!( + "Port {} appeared free but something is listening on it", + port + ); + false + } + Err(_) => { + // Nothing responded, port is truly free + true + } + } + } + Err(_) => { + // Could not bind, port is definitely in use + false + } + } + } + + fn find_temporal_service_port_from_processes() -> Result { + // Use pgrep to find temporal-service processes + let pgrep_output = Command::new("pgrep") + .arg("-f") + .arg("temporal-service") + .output() + .map_err(|e| SchedulerError::SchedulerInternalError(format!("pgrep failed: {}", e)))?; + + if !pgrep_output.status.success() { + return Err(SchedulerError::SchedulerInternalError( + "No temporal-service processes found".to_string(), + )); + } + + let pids_str = String::from_utf8_lossy(&pgrep_output.stdout); + let pids: Vec<&str> = pids_str + .trim() + .split('\n') + .filter(|s| !s.is_empty()) + .collect(); + + for pid in pids { + // Use lsof to find listening ports for this PID + let lsof_output = Command::new("lsof") + .arg("-p") + .arg(pid) + .arg("-i") + .arg("tcp") + .arg("-P") // Show port numbers instead of service names + .arg("-n") // Show IP addresses instead of hostnames + .output(); + + if let Ok(output) = lsof_output { + let lsof_str = String::from_utf8_lossy(&output.stdout); + + // Look for HTTP API port (typically 8080-8999 range) + for line in lsof_str.lines() { + if line.contains("LISTEN") && line.contains("temporal-") { + // Parse lines like: "temporal-service 12345 user 6u IPv4 0x... 0t0 TCP *:8081 (LISTEN)" + let parts: Vec<&str> = line.split_whitespace().collect(); + + // Find the TCP part which contains the port + for part in &parts { + if part.starts_with("TCP") && part.contains(':') { + // Extract port from TCP *:8081 or TCP 127.0.0.1:8081 + if let Some(port_str) = part.split(':').next_back() { + if let Ok(port) = port_str.parse::() { + // HTTP API ports are typically in 8080-8999 range + if (8080..9000).contains(&port) { + info!("Found HTTP API port {} for PID {}", port, pid); + return Ok(port); + } + } + } + } + } + } + } + } + } + + Err(SchedulerError::SchedulerInternalError( + "Could not find HTTP API port from temporal-service processes".to_string(), + )) + } + + async fn fetch_port_config(&self) -> Result { + let url = format!("{}/ports", self.service_url); + + match self.http_client.get(&url).send().await { + Ok(response) => { + if response.status().is_success() { + let port_config: PortConfig = response.json().await.map_err(|e| { + SchedulerError::SchedulerInternalError(format!( + "Failed to parse port config JSON: {}", + e + )) + })?; + Ok(port_config) + } else { + Err(SchedulerError::SchedulerInternalError(format!( + "Failed to fetch port config: HTTP {}", + response.status() + ))) + } + } + Err(e) => Err(SchedulerError::SchedulerInternalError(format!( + "Failed to fetch port config: {}", + e + ))), + } + } + + /// Get the current port configuration + pub fn get_port_config(&self) -> &PortConfig { + &self.port_config + } + + /// Get the Temporal server port + pub fn get_temporal_port(&self) -> u16 { + self.port_config.temporal_port + } + + /// Get the HTTP API port + pub fn get_http_port(&self) -> u16 { + self.port_config.http_port + } + + /// Get the Temporal UI port + pub fn get_ui_port(&self) -> u16 { + self.port_config.ui_port } async fn start_go_service(&self) -> Result<(), SchedulerError> { - info!("Starting Temporal Go service..."); + info!( + "Starting Temporal Go service on port {}...", + self.port_config.http_port + ); - // Check if port 8080 is already in use - if self.check_port_in_use(8080).await { - // Port is in use - check if it's our Go service we can connect to - if self.health_check().await.unwrap_or(false) { - info!("Port 8080 is in use by a Go service we can connect to"); - return Ok(()); - } else { - return Err(SchedulerError::SchedulerInternalError( - "Port 8080 is already in use by something other than our Go service." - .to_string(), - )); - } + // Check if the service is already running on the discovered port + if self.health_check().await.unwrap_or(false) { + info!( + "Temporal service is already running on port {}", + self.port_config.http_port + ); + return Ok(()); + } + + // Double-check that the port is still free (in case something grabbed it between discovery and start) + if !Self::is_port_free(self.port_config.http_port).await { + return Err(SchedulerError::SchedulerInternalError(format!( + "Port {} is no longer available for Temporal service.", + self.port_config.http_port + ))); } // Check if the temporal-service binary exists - try multiple possible locations @@ -103,43 +349,70 @@ impl TemporalScheduler { info!("Found Go service binary at: {}", binary_path); info!("Using working directory: {}", working_dir.display()); - let command = format!( - "cd '{}' && nohup ./temporal-service > temporal-service.log 2>&1 & echo $!", - working_dir.display() - ); + // Set the PORT environment variable for the service to use and properly daemonize it + // Create a new process group to ensure the service survives parent termination + let mut command = Command::new("./temporal-service"); + command + .current_dir(working_dir) + .env("PORT", self.port_config.http_port.to_string()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .stdin(std::process::Stdio::null()); - let output = Command::new("sh") - .arg("-c") - .arg(&command) - .output() - .map_err(|e| { - SchedulerError::SchedulerInternalError(format!( - "Failed to start Go temporal service: {}", - e - )) - })?; - - if !output.status.success() { - return Err(SchedulerError::SchedulerInternalError(format!( - "Failed to start Go service: {}", - String::from_utf8_lossy(&output.stderr) - ))); + // On Unix systems, create a new process group + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + command.process_group(0); } - let pid_output = String::from_utf8_lossy(&output.stdout); - let pid = pid_output.trim(); - info!("Temporal Go service started with PID: {}", pid); + let child = command.spawn().map_err(|e| { + SchedulerError::SchedulerInternalError(format!( + "Failed to start Go temporal service: {}", + e + )) + })?; + + let pid = child.id(); + info!( + "Temporal Go service started with PID: {} on port {} (detached)", + pid, self.port_config.http_port + ); + + // Don't wait for the child process - let it run independently + std::mem::forget(child); + + // Give the process a moment to start up + sleep(Duration::from_millis(100)).await; + + // Verify the process is still running + #[cfg(unix)] + { + use std::process::Command as StdCommand; + let ps_check = StdCommand::new("ps") + .arg("-p") + .arg(pid.to_string()) + .output(); + + match ps_check { + Ok(output) if output.status.success() => { + info!("Confirmed Temporal service process {} is running", pid); + } + Ok(_) => { + warn!( + "Temporal service process {} may have exited immediately", + pid + ); + } + Err(e) => { + warn!("Could not verify Temporal service process status: {}", e); + } + } + } Ok(()) } - async fn check_port_in_use(&self, port: u16) -> bool { - use std::net::{SocketAddr, TcpListener}; - - let addr: SocketAddr = format!("127.0.0.1:{}", port).parse().unwrap(); - TcpListener::bind(addr).is_err() - } - fn find_go_service_binary() -> Result { // Try to find the Go service binary by looking for it relative to the current executable // or in common locations @@ -191,27 +464,50 @@ impl TemporalScheduler { info!("Waiting for Temporal service to be ready..."); let start_time = std::time::Instant::now(); + let mut attempt_count = 0; while start_time.elapsed() < TEMPORAL_SERVICE_STARTUP_TIMEOUT { + attempt_count += 1; match self.health_check().await { Ok(true) => { - info!("Temporal service is ready"); + info!( + "Temporal service is ready after {} attempts in {:.2}s", + attempt_count, + start_time.elapsed().as_secs_f64() + ); return Ok(()); } Ok(false) => { // Service responded but not healthy + if attempt_count % 10 == 0 { + info!( + "Waiting for Temporal service... attempt {} ({:.1}s elapsed)", + attempt_count, + start_time.elapsed().as_secs_f64() + ); + } sleep(TEMPORAL_SERVICE_HEALTH_CHECK_INTERVAL).await; } - Err(_) => { + Err(e) => { // Service not responding yet + if attempt_count % 10 == 0 { + info!( + "Temporal service not responding yet... attempt {} ({:.1}s elapsed): {}", + attempt_count, + start_time.elapsed().as_secs_f64(), + e + ); + } sleep(TEMPORAL_SERVICE_HEALTH_CHECK_INTERVAL).await; } } } - Err(SchedulerError::SchedulerInternalError( - "Temporal service failed to become ready within timeout".to_string(), - )) + Err(SchedulerError::SchedulerInternalError(format!( + "Temporal service failed to become ready within {}s timeout ({} attempts)", + TEMPORAL_SERVICE_STARTUP_TIMEOUT.as_secs(), + attempt_count + ))) } async fn health_check(&self) -> Result { @@ -516,19 +812,19 @@ impl TemporalScheduler { format!( "Temporal Services Status:\n\ - - Go Service ({}:8080): {}\n\ + - Go Service (localhost:{}): {}\n\ + - Temporal Server (localhost:{}): Running via Go service\n\ + - Temporal UI: http://localhost:{}\n\ - Service logs: temporal-service/temporal-service.log\n\ - - Note: Temporal server is managed by the Go service", - if go_running { - "localhost" - } else { - "not running" - }, + - Note: All ports are dynamically allocated", + self.port_config.http_port, if go_running { "✅ Running" } else { "❌ Not Running" - } + }, + self.port_config.temporal_port, + self.port_config.ui_port ) } @@ -745,16 +1041,11 @@ mod tests { let rt = Runtime::new().unwrap(); rt.block_on(async { - let scheduler = TemporalScheduler { - http_client: reqwest::Client::new(), - service_url: "http://localhost:8080".to_string(), - }; - // Test with a port that should be available (high port number) - let high_port_in_use = scheduler.check_port_in_use(65432).await; + let high_port_in_use = !TemporalScheduler::is_port_free(65432).await; // Test with a port that might be in use (port 80) - let low_port_in_use = scheduler.check_port_in_use(80).await; + let low_port_in_use = !TemporalScheduler::is_port_free(80).await; println!("✅ Port checking functionality works"); println!(" High port (65432) in use: {}", high_port_in_use); @@ -779,4 +1070,38 @@ mod tests { } } } + + #[test] + fn test_daemon_process_group_creation() { + // Test that the daemon process creation logic compiles and works correctly + use std::process::Command; + + // Create a test command similar to what we do in start_go_service + let mut command = Command::new("echo"); + command + .arg("test") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .stdin(std::process::Stdio::null()); + + // On Unix systems, create a new process group + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + command.process_group(0); + } + + // Test that the command can be spawned (but don't actually run it) + match command.spawn() { + Ok(mut child) => { + println!("✅ Daemon process group creation works"); + // Clean up the test process + let _ = child.wait(); + } + Err(e) => { + println!("⚠️ Error spawning test process: {}", e); + // This might happen in some test environments, but the logic is correct + } + } + } } diff --git a/temporal-service/main.go b/temporal-service/main.go index 93348b59..a17dddc7 100644 --- a/temporal-service/main.go +++ b/temporal-service/main.go @@ -5,11 +5,13 @@ import ( "encoding/json" "fmt" "log" + "net" "net/http" "os" "os/exec" "os/signal" "path/filepath" + "strconv" "strings" "syscall" "time" @@ -27,6 +29,59 @@ const ( Namespace = "default" ) +// PortConfig holds the port configuration for Temporal services +type PortConfig struct { + TemporalPort int // Main Temporal server port + UIPort int // Temporal UI port + HTTPPort int // HTTP API port +} + +// findAvailablePort finds an available port starting from the given port +func findAvailablePort(startPort int) (int, error) { + for port := startPort; port < startPort+100; port++ { + ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err == nil { + ln.Close() + return port, nil + } + } + return 0, fmt.Errorf("no available port found starting from %d", startPort) +} + +// findAvailablePorts finds available ports for all Temporal services +func findAvailablePorts() (*PortConfig, error) { + // Try to find available ports starting from preferred defaults + temporalPort, err := findAvailablePort(7233) + if err != nil { + return nil, fmt.Errorf("failed to find available port for Temporal server: %w", err) + } + + uiPort, err := findAvailablePort(8233) + if err != nil { + return nil, fmt.Errorf("failed to find available port for Temporal UI: %w", err) + } + + // For HTTP port, check environment variable first + httpPort := 8080 + if portEnv := os.Getenv("PORT"); portEnv != "" { + if parsed, err := strconv.Atoi(portEnv); err == nil { + httpPort = parsed + } + } + + // Verify HTTP port is available, find alternative if not + finalHTTPPort, err := findAvailablePort(httpPort) + if err != nil { + return nil, fmt.Errorf("failed to find available port for HTTP server: %w", err) + } + + return &PortConfig{ + TemporalPort: temporalPort, + UIPort: uiPort, + HTTPPort: finalHTTPPort, + }, nil +} + // Global service instance for activities to access var globalService *TemporalService @@ -61,16 +116,16 @@ type RunNowResponse struct { } // ensureTemporalServerRunning checks if Temporal server is running and starts it if needed -func ensureTemporalServerRunning() error { +func ensureTemporalServerRunning(ports *PortConfig) error { log.Println("Checking if Temporal server is running...") // Check if Temporal server is already running by trying to connect - if isTemporalServerRunning() { - log.Println("Temporal server is already running") + if isTemporalServerRunning(ports.TemporalPort) { + log.Printf("Temporal server is already running on port %d", ports.TemporalPort) return nil } - log.Println("Temporal server not running, attempting to start it...") + log.Printf("Temporal server not running, attempting to start it on port %d...", ports.TemporalPort) // Find the temporal CLI binary temporalCmd, err := findTemporalCLI() @@ -83,8 +138,8 @@ func ensureTemporalServerRunning() error { // Start Temporal server in background cmd := exec.Command(temporalCmd, "server", "start-dev", "--db-filename", "temporal.db", - "--port", "7233", - "--ui-port", "8233", + "--port", strconv.Itoa(ports.TemporalPort), + "--ui-port", strconv.Itoa(ports.UIPort), "--log-level", "warn") // Start the process in background @@ -92,7 +147,8 @@ func ensureTemporalServerRunning() error { return fmt.Errorf("failed to start Temporal server: %w", err) } - log.Printf("Temporal server started with PID: %d", cmd.Process.Pid) + log.Printf("Temporal server started with PID: %d (port: %d, UI port: %d)", + cmd.Process.Pid, ports.TemporalPort, ports.UIPort) // Wait for server to be ready (with timeout) timeout := time.After(30 * time.Second) @@ -104,8 +160,8 @@ func ensureTemporalServerRunning() error { case <-timeout: return fmt.Errorf("timeout waiting for Temporal server to start") case <-ticker.C: - if isTemporalServerRunning() { - log.Println("Temporal server is now ready") + if isTemporalServerRunning(ports.TemporalPort) { + log.Printf("Temporal server is now ready on port %d", ports.TemporalPort) return nil } } @@ -113,10 +169,10 @@ func ensureTemporalServerRunning() error { } // isTemporalServerRunning checks if Temporal server is accessible -func isTemporalServerRunning() bool { +func isTemporalServerRunning(port int) bool { // Try to create a client connection to check if server is running c, err := client.Dial(client.Options{ - HostPort: "127.0.0.1:7233", + HostPort: fmt.Sprintf("127.0.0.1:%d", port), Namespace: Namespace, }) if err != nil { @@ -143,6 +199,19 @@ func findTemporalCLI() (string, error) { } } + // Try using 'which' command to find temporal + cmd := exec.Command("which", "temporal") + if output, err := cmd.Output(); err == nil { + path := strings.TrimSpace(string(output)) + if path != "" { + // Verify it's the correct temporal CLI by checking version + cmd := exec.Command(path, "--version") + if err := cmd.Run(); err == nil { + return path, nil + } + } + } + // If not found in PATH, try different possible locations for the temporal CLI possiblePaths := []string{ "./temporal", // Current directory @@ -180,18 +249,28 @@ type TemporalService struct { worker worker.Worker scheduleJobs map[string]*JobStatus // In-memory job tracking runningJobs map[string]bool // Track which jobs are currently running + ports *PortConfig // Port configuration } // NewTemporalService creates a new Temporal service and ensures Temporal server is running func NewTemporalService() (*TemporalService, error) { - // First, ensure Temporal server is running - if err := ensureTemporalServerRunning(); err != nil { + // First, find available ports + ports, err := findAvailablePorts() + if err != nil { + return nil, fmt.Errorf("failed to find available ports: %w", err) + } + + log.Printf("Using ports - Temporal: %d, UI: %d, HTTP: %d", + ports.TemporalPort, ports.UIPort, ports.HTTPPort) + + // Ensure Temporal server is running + if err := ensureTemporalServerRunning(ports); err != nil { return nil, fmt.Errorf("failed to ensure Temporal server is running: %w", err) } // Create client (Temporal server should now be running) c, err := client.Dial(client.Options{ - HostPort: "127.0.0.1:7233", + HostPort: fmt.Sprintf("127.0.0.1:%d", ports.TemporalPort), Namespace: Namespace, }) if err != nil { @@ -208,13 +287,14 @@ func NewTemporalService() (*TemporalService, error) { return nil, fmt.Errorf("failed to start worker: %w", err) } - log.Println("Connected to Temporal server successfully") + log.Printf("Connected to Temporal server successfully on port %d", ports.TemporalPort) service := &TemporalService{ client: c, worker: w, scheduleJobs: make(map[string]*JobStatus), runningJobs: make(map[string]bool), + ports: ports, } // Set global service for activities @@ -235,6 +315,21 @@ func (ts *TemporalService) Stop() { log.Println("Temporal service stopped") } +// GetHTTPPort returns the HTTP port for this service +func (ts *TemporalService) GetHTTPPort() int { + return ts.ports.HTTPPort +} + +// GetTemporalPort returns the Temporal server port for this service +func (ts *TemporalService) GetTemporalPort() int { + return ts.ports.TemporalPort +} + +// GetUIPort returns the Temporal UI port for this service +func (ts *TemporalService) GetUIPort() int { + return ts.ports.UIPort +} + // Workflow definition for executing Goose recipes func GooseJobWorkflow(ctx workflow.Context, jobID, recipePath string) (string, error) { logger := workflow.GetLogger(ctx) @@ -610,29 +705,45 @@ func (ts *TemporalService) handleHealth(w http.ResponseWriter, r *http.Request) json.NewEncoder(w).Encode(map[string]string{"status": "healthy"}) } -func main() { - port := os.Getenv("PORT") - if port == "" { - port = "8080" +// handlePorts returns the port configuration for this service +func (ts *TemporalService) handlePorts(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + portInfo := map[string]int{ + "http_port": ts.ports.HTTPPort, + "temporal_port": ts.ports.TemporalPort, + "ui_port": ts.ports.UIPort, } + + json.NewEncoder(w).Encode(portInfo) +} +func main() { log.Println("Starting Temporal service...") - log.Println("Note: This service requires a running Temporal server at 127.0.0.1:7233") - log.Println("Start Temporal server with: temporal server start-dev") - // Create Temporal service + // Create Temporal service (this will find available ports automatically) service, err := NewTemporalService() if err != nil { log.Fatalf("Failed to create Temporal service: %v", err) } + // Use the dynamically assigned HTTP port + httpPort := service.GetHTTPPort() + temporalPort := service.GetTemporalPort() + uiPort := service.GetUIPort() + + log.Printf("Temporal server running on port %d", temporalPort) + log.Printf("Temporal UI available at http://localhost:%d", uiPort) + // Set up HTTP server mux := http.NewServeMux() mux.HandleFunc("/jobs", service.handleJobs) mux.HandleFunc("/health", service.handleHealth) + mux.HandleFunc("/ports", service.handlePorts) server := &http.Server{ - Addr: ":" + port, + Addr: fmt.Sprintf(":%d", httpPort), Handler: mux, } @@ -655,9 +766,10 @@ func main() { os.Exit(0) }() - log.Printf("Temporal service starting on port %s", port) - log.Printf("Health endpoint: http://localhost:%s/health", port) - log.Printf("Jobs endpoint: http://localhost:%s/jobs", port) + log.Printf("Temporal service starting on port %d", httpPort) + log.Printf("Health endpoint: http://localhost:%d/health", httpPort) + log.Printf("Jobs endpoint: http://localhost:%d/jobs", httpPort) + log.Printf("Ports endpoint: http://localhost:%d/ports", httpPort) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("HTTP server failed: %v", err) diff --git a/temporal-service/temporal-service b/temporal-service/temporal-service index 49fca829..5b9f6c64 100755 Binary files a/temporal-service/temporal-service and b/temporal-service/temporal-service differ diff --git a/ui/desktop/src/goosed.ts b/ui/desktop/src/goosed.ts index 1b4c1666..1431915b 100644 --- a/ui/desktop/src/goosed.ts +++ b/ui/desktop/src/goosed.ts @@ -26,7 +26,7 @@ export const findAvailablePort = (): Promise => { // Check if goosed server is ready by polling the status endpoint const checkServerStatus = async ( port: number, - maxAttempts: number = 60, + maxAttempts: number = 80, interval: number = 100 ): Promise => { const statusUrl = `http://127.0.0.1:${port}/status`;