diff --git a/cmd/model.go b/cmd/model.go index 5613893e..eaef1168 100644 --- a/cmd/model.go +++ b/cmd/model.go @@ -6,6 +6,7 @@ import ( "github.com/abiosoft/colima/cmd/root" "github.com/abiosoft/colima/config/configmanager" "github.com/abiosoft/colima/model" + "github.com/abiosoft/colima/util" "github.com/abiosoft/colima/util/terminal" "github.com/spf13/cobra" ) @@ -159,13 +160,35 @@ Press Ctrl-C to stop the server. return err } + // Determine the port to use + port := modelCmdArgs.ServePort + portExplicitlySet := cmd.Flags().Changed("port") + + // If port was not explicitly set, find an available port starting from the default + const maxPortAttempts = 20 + if !portExplicitlySet { + availablePort, found := util.FindAvailablePort(port, maxPortAttempts) + if !found { + return fmt.Errorf("no available port found in range %d-%d", port, port+maxPortAttempts-1) + } + if availablePort != port { + fmt.Printf("Port %d is in use, using port %d instead\n", port, availablePort) + } + port = availablePort + } else { + // User explicitly set the port, check if it's available + if _, found := util.FindAvailablePort(port, 1); !found { + return fmt.Errorf("port %d is already in use", port) + } + } + // Build header for alternate screen separator := "────────────────────────────────────────" - header := fmt.Sprintf("Colima - Model Server (Ctrl-C to stop)\nWeb UI & API at http://localhost:%d\n%s", modelCmdArgs.ServePort, separator) + header := fmt.Sprintf("Colima - Model Server (Ctrl-C to stop)\nWeb UI & API at http://localhost:%d\n%s", port, separator) // Run in alternate screen with header return terminal.WithAltScreen(func() error { - return runner.Serve(normalizedModel, modelCmdArgs.ServePort) + return runner.Serve(normalizedModel, port) }, header) }, } diff --git a/util/util.go b/util/util.go index c5eddf6e..46b161a7 100644 --- a/util/util.go +++ b/util/util.go @@ -36,6 +36,31 @@ func RandomAvailablePort() int { return listener.Addr().(*net.TCPAddr).Port } +// isPortAvailable checks if a specific port is available on the host. +func isPortAvailable(port int) bool { + listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + return false + } + if err := listener.Close(); err != nil { + return false + } + return true +} + +// FindAvailablePort finds the first available port starting from startPort. +// It checks up to maxAttempts consecutive ports (startPort, startPort+1, ...). +// Returns the available port and true if found, or 0 and false if no port is available. +func FindAvailablePort(startPort, maxAttempts int) (int, bool) { + for i := range maxAttempts { + port := startPort + i + if isPortAvailable(port) { + return port, true + } + } + return 0, false +} + // HostIPAddresses returns all IPv4 addresses on the host. func HostIPAddresses() []net.IP { var addresses []net.IP