mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
fix(host): modernize default host selection order
This commit is contained in:
@@ -503,7 +503,7 @@ func TestDefaultConfig_Temperature(t *testing.T) {
|
||||
func TestDefaultConfig_Gateway(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
|
||||
if cfg.Gateway.Host != "127.0.0.1" {
|
||||
if cfg.Gateway.Host != "localhost" {
|
||||
t.Error("Gateway host should have default value")
|
||||
}
|
||||
if cfg.Gateway.Port == 0 {
|
||||
@@ -739,7 +739,7 @@ func TestConfig_Complete(t *testing.T) {
|
||||
if cfg.Agents.Defaults.MaxToolIterations == 0 {
|
||||
t.Error("MaxToolIterations should not be zero")
|
||||
}
|
||||
if cfg.Gateway.Host != "127.0.0.1" {
|
||||
if cfg.Gateway.Host != "localhost" {
|
||||
t.Error("Gateway host should have default value")
|
||||
}
|
||||
if cfg.Gateway.Port == 0 {
|
||||
|
||||
@@ -259,7 +259,7 @@ func DefaultConfig() *Config {
|
||||
},
|
||||
},
|
||||
Gateway: GatewayConfig{
|
||||
Host: "127.0.0.1",
|
||||
Host: "localhost",
|
||||
Port: 18790,
|
||||
HotReload: false,
|
||||
LogLevel: DefaultGatewayLogLevel,
|
||||
|
||||
+97
-7
@@ -2,8 +2,10 @@ package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
)
|
||||
@@ -50,17 +52,105 @@ func EffectiveGatewayLogLevel(cfg *Config) string {
|
||||
return normalizeGatewayLogLevel(cfg.Gateway.LogLevel)
|
||||
}
|
||||
|
||||
var (
|
||||
gatewayIPFamiliesOnce sync.Once
|
||||
gatewayHasIPv4 bool
|
||||
gatewayHasIPv6 bool
|
||||
)
|
||||
|
||||
func detectGatewayIPFamilies() (bool, bool) {
|
||||
gatewayIPFamiliesOnce.Do(func() {
|
||||
if ips, err := net.LookupIP("localhost"); err == nil {
|
||||
for _, ip := range ips {
|
||||
if ip == nil {
|
||||
continue
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
gatewayHasIPv4 = true
|
||||
continue
|
||||
}
|
||||
gatewayHasIPv6 = true
|
||||
}
|
||||
}
|
||||
|
||||
if gatewayHasIPv4 && gatewayHasIPv6 {
|
||||
return
|
||||
}
|
||||
|
||||
if addrs, err := net.InterfaceAddrs(); err == nil {
|
||||
for _, addr := range addrs {
|
||||
ipnet, ok := addr.(*net.IPNet)
|
||||
if !ok || ipnet.IP == nil {
|
||||
continue
|
||||
}
|
||||
if ipnet.IP.To4() != nil {
|
||||
gatewayHasIPv4 = true
|
||||
continue
|
||||
}
|
||||
gatewayHasIPv6 = true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return gatewayHasIPv4, gatewayHasIPv6
|
||||
}
|
||||
|
||||
func selectAdaptiveGatewayLoopbackHost(hasIPv4, hasIPv6 bool) string {
|
||||
switch {
|
||||
case hasIPv4 && hasIPv6:
|
||||
return "localhost"
|
||||
case hasIPv6:
|
||||
return "::1"
|
||||
case hasIPv4:
|
||||
return "127.0.0.1"
|
||||
default:
|
||||
return "localhost"
|
||||
}
|
||||
}
|
||||
|
||||
func selectAdaptiveGatewayAnyHost(hasIPv4, hasIPv6 bool) string {
|
||||
switch {
|
||||
case hasIPv4 && hasIPv6:
|
||||
return "::"
|
||||
case hasIPv6:
|
||||
return "::"
|
||||
case hasIPv4:
|
||||
return "0.0.0.0"
|
||||
default:
|
||||
return "::"
|
||||
}
|
||||
}
|
||||
|
||||
func resolveAdaptiveGatewayLoopbackHost() string {
|
||||
hasIPv4, hasIPv6 := detectGatewayIPFamilies()
|
||||
return selectAdaptiveGatewayLoopbackHost(hasIPv4, hasIPv6)
|
||||
}
|
||||
|
||||
func resolveAdaptiveGatewayAnyHost() string {
|
||||
hasIPv4, hasIPv6 := detectGatewayIPFamilies()
|
||||
return selectAdaptiveGatewayAnyHost(hasIPv4, hasIPv6)
|
||||
}
|
||||
|
||||
func normalizeGatewayHost(host string) string {
|
||||
host = strings.TrimSpace(host)
|
||||
if host != "" {
|
||||
return host
|
||||
if host == "" {
|
||||
host = strings.TrimSpace(DefaultConfig().Gateway.Host)
|
||||
}
|
||||
|
||||
defaultHost := strings.TrimSpace(DefaultConfig().Gateway.Host)
|
||||
if defaultHost == "" {
|
||||
return "127.0.0.1"
|
||||
if host == "" {
|
||||
host = "localhost"
|
||||
}
|
||||
return defaultHost
|
||||
|
||||
if strings.EqualFold(host, "localhost") {
|
||||
return resolveAdaptiveGatewayLoopbackHost()
|
||||
}
|
||||
|
||||
trimmed := strings.Trim(host, "[]")
|
||||
if ip := net.ParseIP(trimmed); ip != nil && ip.IsUnspecified() {
|
||||
return resolveAdaptiveGatewayAnyHost()
|
||||
}
|
||||
|
||||
return host
|
||||
}
|
||||
|
||||
func resolveGatewayHostFromEnv(baseHost string) string {
|
||||
@@ -74,7 +164,7 @@ func resolveGatewayHostFromEnv(baseHost string) string {
|
||||
return normalizeGatewayHost(baseHost)
|
||||
}
|
||||
|
||||
return envHost
|
||||
return normalizeGatewayHost(envHost)
|
||||
}
|
||||
|
||||
// ResolveGatewayLogLevel reads the configured gateway log level without triggering
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -40,8 +39,9 @@ func TestLoadConfig_GatewayHostBlankEnvFallsBackToConfigHost(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig() error: %v", err)
|
||||
}
|
||||
if cfg.Gateway.Host != "localhost" {
|
||||
t.Fatalf("cfg.Gateway.Host = %q, want %q", cfg.Gateway.Host, "localhost")
|
||||
want := normalizeGatewayHost("localhost")
|
||||
if cfg.Gateway.Host != want {
|
||||
t.Fatalf("cfg.Gateway.Host = %q, want %q", cfg.Gateway.Host, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,8 +54,23 @@ func TestLoadConfig_GatewayHostBlankEnvAndConfigFallsBackToDefault(t *testing.T)
|
||||
t.Fatalf("LoadConfig() error: %v", err)
|
||||
}
|
||||
|
||||
defaultHost := strings.TrimSpace(DefaultConfig().Gateway.Host)
|
||||
defaultHost := normalizeGatewayHost(DefaultConfig().Gateway.Host)
|
||||
if cfg.Gateway.Host != defaultHost {
|
||||
t.Fatalf("cfg.Gateway.Host = %q, want %q", cfg.Gateway.Host, defaultHost)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfig_GatewayHostEnvWildcardUsesAdaptiveAnyHost(t *testing.T) {
|
||||
configPath := writeGatewayHostTestConfig(t, "localhost")
|
||||
t.Setenv(EnvGatewayHost, " 0.0.0.0 ")
|
||||
|
||||
cfg, err := LoadConfig(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig() error: %v", err)
|
||||
}
|
||||
|
||||
want := normalizeGatewayHost("0.0.0.0")
|
||||
if cfg.Gateway.Host != want {
|
||||
t.Fatalf("cfg.Gateway.Host = %q, want %q", cfg.Gateway.Host, want)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,12 @@ package gateway
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -217,7 +219,8 @@ func Run(debug bool, homePath, configPath string, allowEmptyStartup bool) (runEr
|
||||
runningServices.HealthServer.SetReloadFunc(reloadTrigger)
|
||||
agentLoop.SetReloadFunc(reloadTrigger)
|
||||
|
||||
fmt.Printf("✓ Gateway started on %s:%d\n", cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
listenAddr := net.JoinHostPort(cfg.Gateway.Host, strconv.Itoa(cfg.Gateway.Port))
|
||||
fmt.Printf("✓ Gateway started on %s\n", listenAddr)
|
||||
fmt.Println("Press Ctrl+C to stop")
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
@@ -390,7 +393,7 @@ func setupAndStartServices(
|
||||
fmt.Println("⚠ Warning: No channels enabled")
|
||||
}
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", cfg.Gateway.Host, cfg.Gateway.Port)
|
||||
addr := net.JoinHostPort(cfg.Gateway.Host, strconv.Itoa(cfg.Gateway.Port))
|
||||
runningServices.authToken = authToken
|
||||
runningServices.HealthServer = health.NewServer(cfg.Gateway.Host, cfg.Gateway.Port, authToken)
|
||||
runningServices.ChannelManager.SetupHTTPServer(addr, runningServices.HealthServer)
|
||||
@@ -409,10 +412,10 @@ func setupAndStartServices(
|
||||
voiceAgent.Start(vaCtx)
|
||||
}
|
||||
|
||||
healthAddr := net.JoinHostPort(cfg.Gateway.Host, strconv.Itoa(cfg.Gateway.Port))
|
||||
fmt.Printf(
|
||||
"✓ Health endpoints available at http://%s:%d/health, /ready and /reload (POST)\n",
|
||||
cfg.Gateway.Host,
|
||||
cfg.Gateway.Port,
|
||||
"✓ Health endpoints available at http://%s/health, /ready and /reload (POST)\n",
|
||||
healthAddr,
|
||||
)
|
||||
|
||||
stateManager := state.NewManager(cfg.WorkspacePath())
|
||||
|
||||
@@ -4,10 +4,11 @@ import (
|
||||
"context"
|
||||
"crypto/subtle"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -49,7 +50,7 @@ func NewServer(host string, port int, token string) *Server {
|
||||
mux.HandleFunc("/ready", s.readyHandler)
|
||||
mux.HandleFunc("/reload", s.reloadHandler)
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
addr := net.JoinHostPort(host, strconv.Itoa(port))
|
||||
s.server = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: mux,
|
||||
|
||||
@@ -305,6 +305,16 @@ func TestNewServer(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewServer_IPv6ListenAddrFormatting(t *testing.T) {
|
||||
s := NewServer("::", 18790, "")
|
||||
if s.server == nil {
|
||||
t.Fatal("server should be initialized")
|
||||
}
|
||||
if s.server.Addr != "[::]:18790" {
|
||||
t.Fatalf("server.Addr = %q, want %q", s.server.Addr, "[::]:18790")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContext_Cancellation(t *testing.T) {
|
||||
s := NewServer("127.0.0.1", 0, "")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user