feat(launcher): add host overrides for launcher and gateway
This commit is contained in:
parent
df9124b824
commit
4e977367c2
8 changed files with 323 additions and 13 deletions
|
|
@ -2,10 +2,13 @@ package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
"github.com/sipeed/picoclaw/cmd/picoclaw/internal"
|
||||||
|
"github.com/sipeed/picoclaw/pkg/config"
|
||||||
"github.com/sipeed/picoclaw/pkg/gateway"
|
"github.com/sipeed/picoclaw/pkg/gateway"
|
||||||
"github.com/sipeed/picoclaw/pkg/logger"
|
"github.com/sipeed/picoclaw/pkg/logger"
|
||||||
"github.com/sipeed/picoclaw/pkg/utils"
|
"github.com/sipeed/picoclaw/pkg/utils"
|
||||||
|
|
@ -15,6 +18,7 @@ func NewGatewayCommand() *cobra.Command {
|
||||||
var debug bool
|
var debug bool
|
||||||
var noTruncate bool
|
var noTruncate bool
|
||||||
var allowEmpty bool
|
var allowEmpty bool
|
||||||
|
var host string
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "gateway",
|
Use: "gateway",
|
||||||
|
|
@ -34,6 +38,21 @@ func NewGatewayCommand() *cobra.Command {
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
RunE: func(_ *cobra.Command, _ []string) error {
|
RunE: func(_ *cobra.Command, _ []string) error {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if host != "" {
|
||||||
|
prevHost, hadPrev := os.LookupEnv(config.EnvGatewayHost)
|
||||||
|
if err := os.Setenv(config.EnvGatewayHost, host); err != nil {
|
||||||
|
return fmt.Errorf("failed to set %s: %w", config.EnvGatewayHost, err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if hadPrev {
|
||||||
|
_ = os.Setenv(config.EnvGatewayHost, prevHost)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = os.Unsetenv(config.EnvGatewayHost)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
return gateway.Run(debug, internal.GetPicoclawHome(), internal.GetConfigPath(), allowEmpty)
|
return gateway.Run(debug, internal.GetPicoclawHome(), internal.GetConfigPath(), allowEmpty)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -47,6 +66,12 @@ func NewGatewayCommand() *cobra.Command {
|
||||||
false,
|
false,
|
||||||
"Continue starting even when no default model is configured",
|
"Continue starting even when no default model is configured",
|
||||||
)
|
)
|
||||||
|
cmd.Flags().StringVar(
|
||||||
|
&host,
|
||||||
|
"host",
|
||||||
|
"",
|
||||||
|
"Host address for gateway binding (overrides gateway.host for this run)",
|
||||||
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,4 +29,5 @@ func TestNewGatewayCommand(t *testing.T) {
|
||||||
assert.True(t, cmd.HasFlags())
|
assert.True(t, cmd.HasFlags())
|
||||||
assert.NotNil(t, cmd.Flags().Lookup("debug"))
|
assert.NotNil(t, cmd.Flags().Lookup("debug"))
|
||||||
assert.NotNil(t, cmd.Flags().Lookup("allow-empty"))
|
assert.NotNil(t, cmd.Flags().Lookup("allow-empty"))
|
||||||
|
assert.NotNil(t, cmd.Flags().Lookup("host"))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *Handler) effectiveLauncherPublic() bool {
|
func (h *Handler) effectiveLauncherPublic() bool {
|
||||||
|
if h.serverHostExplicit {
|
||||||
|
// -host takes precedence over -public and launcher-config public setting.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if h.serverPublicExplicit {
|
if h.serverPublicExplicit {
|
||||||
return h.serverPublic
|
return h.serverPublic
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +28,34 @@ func (h *Handler) effectiveLauncherPublic() bool {
|
||||||
return h.serverPublic
|
return h.serverPublic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func canonicalLauncherBindHost(host string) string {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if host == "" || strings.EqualFold(host, "localhost") {
|
||||||
|
return "127.0.0.1"
|
||||||
|
}
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) launcherAndGatewayBindHostsAligned() bool {
|
||||||
|
cfg, err := config.LoadConfig(h.configPath)
|
||||||
|
if err != nil || cfg == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// With -host specified, -public is ignored, so launcher's legacy bind host is loopback.
|
||||||
|
launcherHost := canonicalLauncherBindHost("127.0.0.1")
|
||||||
|
gatewayHost := canonicalLauncherBindHost(cfg.Gateway.Host)
|
||||||
|
return launcherHost == gatewayHost
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) gatewayHostOverride() string {
|
func (h *Handler) gatewayHostOverride() string {
|
||||||
|
if h.serverHostExplicit {
|
||||||
|
if h.launcherAndGatewayBindHostsAligned() {
|
||||||
|
return strings.TrimSpace(h.serverHost)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
if h.effectiveLauncherPublic() {
|
if h.effectiveLauncherPublic() {
|
||||||
return "0.0.0.0"
|
return "0.0.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -240,3 +240,52 @@ func TestBuildWsURLUsesRequestHostNotGatewayBindLoopback(t *testing.T) {
|
||||||
t.Fatalf("buildWsURL() = %q, want %q", got, "ws://localhost:18800/pico/ws")
|
t.Fatalf("buildWsURL() = %q, want %q", got, "ws://localhost:18800/pico/ws")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGatewayHostOverrideWithExplicitHostAndAlignedGatewayHost(t *testing.T) {
|
||||||
|
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||||
|
writeGatewayHostConfig(t, configPath, "127.0.0.1")
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
h.SetServerOptions(18800, false, false, nil)
|
||||||
|
h.SetServerBindHost("0.0.0.0", true)
|
||||||
|
|
||||||
|
if got := h.gatewayHostOverride(); got != "0.0.0.0" {
|
||||||
|
t.Fatalf("gatewayHostOverride() = %q, want %q", got, "0.0.0.0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGatewayHostOverrideWithExplicitHostAndMismatchedGatewayHost(t *testing.T) {
|
||||||
|
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||||
|
writeGatewayHostConfig(t, configPath, "0.0.0.0")
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
h.SetServerOptions(18800, false, false, nil)
|
||||||
|
h.SetServerBindHost("192.168.1.10", true)
|
||||||
|
|
||||||
|
if got := h.gatewayHostOverride(); got != "" {
|
||||||
|
t.Fatalf("gatewayHostOverride() = %q, want empty", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGatewayHostExplicitIgnoresPublicFlag(t *testing.T) {
|
||||||
|
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||||
|
writeGatewayHostConfig(t, configPath, "127.0.0.1")
|
||||||
|
|
||||||
|
h := NewHandler(configPath)
|
||||||
|
h.SetServerOptions(18800, true, true, nil)
|
||||||
|
h.SetServerBindHost("127.0.0.1", true)
|
||||||
|
|
||||||
|
if got := h.effectiveLauncherPublic(); got {
|
||||||
|
t.Fatalf("effectiveLauncherPublic() = %t, want false when explicit host is set", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeGatewayHostConfig(t *testing.T, configPath, host string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
cfg := config.DefaultConfig()
|
||||||
|
cfg.Gateway.Host = host
|
||||||
|
if err := config.SaveConfig(configPath, cfg); err != nil {
|
||||||
|
t.Fatalf("SaveConfig() error = %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sipeed/picoclaw/web/backend/launcherconfig"
|
"github.com/sipeed/picoclaw/web/backend/launcherconfig"
|
||||||
|
|
@ -13,6 +14,8 @@ type Handler struct {
|
||||||
serverPort int
|
serverPort int
|
||||||
serverPublic bool
|
serverPublic bool
|
||||||
serverPublicExplicit bool
|
serverPublicExplicit bool
|
||||||
|
serverHost string
|
||||||
|
serverHostExplicit bool
|
||||||
serverCIDRs []string
|
serverCIDRs []string
|
||||||
debug bool
|
debug bool
|
||||||
oauthMu sync.Mutex
|
oauthMu sync.Mutex
|
||||||
|
|
@ -29,6 +32,7 @@ func NewHandler(configPath string) *Handler {
|
||||||
return &Handler{
|
return &Handler{
|
||||||
configPath: configPath,
|
configPath: configPath,
|
||||||
serverPort: launcherconfig.DefaultPort,
|
serverPort: launcherconfig.DefaultPort,
|
||||||
|
serverHost: "127.0.0.1",
|
||||||
oauthFlows: make(map[string]*oauthFlow),
|
oauthFlows: make(map[string]*oauthFlow),
|
||||||
oauthState: make(map[string]string),
|
oauthState: make(map[string]string),
|
||||||
weixinFlows: make(map[string]*weixinFlow),
|
weixinFlows: make(map[string]*weixinFlow),
|
||||||
|
|
@ -41,9 +45,30 @@ func (h *Handler) SetServerOptions(port int, public bool, publicExplicit bool, a
|
||||||
h.serverPort = port
|
h.serverPort = port
|
||||||
h.serverPublic = public
|
h.serverPublic = public
|
||||||
h.serverPublicExplicit = publicExplicit
|
h.serverPublicExplicit = publicExplicit
|
||||||
|
h.serverHost = "127.0.0.1"
|
||||||
|
if public {
|
||||||
|
h.serverHost = "0.0.0.0"
|
||||||
|
}
|
||||||
|
h.serverHostExplicit = false
|
||||||
h.serverCIDRs = append([]string(nil), allowedCIDRs...)
|
h.serverCIDRs = append([]string(nil), allowedCIDRs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetServerBindHost stores the launcher's effective bind host.
|
||||||
|
// When explicit is true, the value came from the -host flag.
|
||||||
|
func (h *Handler) SetServerBindHost(host string, explicit bool) {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if host == "" {
|
||||||
|
host = "127.0.0.1"
|
||||||
|
if h.serverPublic {
|
||||||
|
host = "0.0.0.0"
|
||||||
|
}
|
||||||
|
explicit = false
|
||||||
|
}
|
||||||
|
|
||||||
|
h.serverHost = host
|
||||||
|
h.serverHostExplicit = explicit
|
||||||
|
}
|
||||||
|
|
||||||
func (h *Handler) SetDebug(debug bool) {
|
func (h *Handler) SetDebug(debug bool) {
|
||||||
h.debug = debug
|
h.debug = debug
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,10 @@ const (
|
||||||
FileName = "launcher-config.json"
|
FileName = "launcher-config.json"
|
||||||
// DefaultPort is the default port for the web launcher.
|
// DefaultPort is the default port for the web launcher.
|
||||||
DefaultPort = 18800
|
DefaultPort = 18800
|
||||||
|
// EnvLauncherToken overrides launcher dashboard token.
|
||||||
|
EnvLauncherToken = "PICOCLAW_LAUNCHER_TOKEN"
|
||||||
|
// EnvLauncherHost overrides launcher listen host.
|
||||||
|
EnvLauncherHost = "PICOCLAW_LAUNCHER_HOST"
|
||||||
|
|
||||||
// dashboardSigningKeyBytes is the HMAC-SHA256 key size (256 bits).
|
// dashboardSigningKeyBytes is the HMAC-SHA256 key size (256 bits).
|
||||||
dashboardSigningKeyBytes = 32
|
dashboardSigningKeyBytes = 32
|
||||||
|
|
@ -59,7 +63,7 @@ func Validate(cfg Config) error {
|
||||||
|
|
||||||
// EnsureDashboardSecrets returns signing key bytes and the effective dashboard token for this
|
// EnsureDashboardSecrets returns signing key bytes and the effective dashboard token for this
|
||||||
// process. The signing key is freshly random each call; the token comes from
|
// process. The signing key is freshly random each call; the token comes from
|
||||||
// PICOCLAW_LAUNCHER_TOKEN when set, otherwise launcher-config.json launcher_token,
|
// EnvLauncherToken when set, otherwise launcher-config.json launcher_token,
|
||||||
// otherwise a new random token.
|
// otherwise a new random token.
|
||||||
func EnsureDashboardSecrets(
|
func EnsureDashboardSecrets(
|
||||||
cfg Config,
|
cfg Config,
|
||||||
|
|
@ -69,7 +73,7 @@ func EnsureDashboardSecrets(
|
||||||
return "", nil, "", err
|
return "", nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
effectiveToken = strings.TrimSpace(os.Getenv("PICOCLAW_LAUNCHER_TOKEN"))
|
effectiveToken = strings.TrimSpace(os.Getenv(EnvLauncherToken))
|
||||||
if effectiveToken != "" {
|
if effectiveToken != "" {
|
||||||
return effectiveToken, signingKey, DashboardTokenSourceEnv, nil
|
return effectiveToken, signingKey, DashboardTokenSourceEnv, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,14 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -65,6 +67,47 @@ func dashboardTokenConfigHelpPath(source launcherconfig.DashboardTokenSource, la
|
||||||
return launcherPath
|
return launcherPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func resolveLauncherBindHost(
|
||||||
|
host string,
|
||||||
|
explicitHost bool,
|
||||||
|
envHost string,
|
||||||
|
effectivePublic bool,
|
||||||
|
) (string, bool, bool, error) {
|
||||||
|
if explicitHost {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
if host == "" {
|
||||||
|
return "", false, false, errors.New("host cannot be empty")
|
||||||
|
}
|
||||||
|
// When -host is specified, -public is ignored.
|
||||||
|
return host, false, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
envHost = strings.TrimSpace(envHost)
|
||||||
|
if envHost != "" {
|
||||||
|
// Environment host follows explicit override semantics.
|
||||||
|
return envHost, false, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if effectivePublic {
|
||||||
|
return "0.0.0.0", true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "127.0.0.1", false, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWildcardBindHost(host string) bool {
|
||||||
|
host = strings.TrimSpace(host)
|
||||||
|
return host == "0.0.0.0" || host == "::"
|
||||||
|
}
|
||||||
|
|
||||||
|
func browserHostForLauncher(bindHost string) string {
|
||||||
|
bindHost = strings.TrimSpace(bindHost)
|
||||||
|
if bindHost == "" || isWildcardBindHost(bindHost) {
|
||||||
|
return "localhost"
|
||||||
|
}
|
||||||
|
return bindHost
|
||||||
|
}
|
||||||
|
|
||||||
// maskSecret masks a secret for display. It always shows up to the first 3
|
// maskSecret masks a secret for display. It always shows up to the first 3
|
||||||
// runes. The last 4 runes are only appended when at least 5 runes remain
|
// runes. The last 4 runes are only appended when at least 5 runes remain
|
||||||
// hidden in the middle (i.e. string length >= 12), so an 8-char minimum
|
// hidden in the middle (i.e. string length >= 12), so an 8-char minimum
|
||||||
|
|
@ -85,6 +128,7 @@ func maskSecret(s string) string {
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
port := flag.String("port", "18800", "Port to listen on")
|
port := flag.String("port", "18800", "Port to listen on")
|
||||||
|
host := flag.String("host", "", "Host to listen on (overrides -public when set)")
|
||||||
public := flag.Bool("public", false, "Listen on all interfaces (0.0.0.0) instead of localhost only")
|
public := flag.Bool("public", false, "Listen on all interfaces (0.0.0.0) instead of localhost only")
|
||||||
noBrowser = flag.Bool("no-browser", false, "Do not auto-open browser on startup")
|
noBrowser = flag.Bool("no-browser", false, "Do not auto-open browser on startup")
|
||||||
lang := flag.String("lang", "", "Language: en (English) or zh (Chinese). Default: auto-detect from system locale")
|
lang := flag.String("lang", "", "Language: en (English) or zh (Chinese). Default: auto-detect from system locale")
|
||||||
|
|
@ -112,6 +156,8 @@ func main() {
|
||||||
os.Args[0],
|
os.Args[0],
|
||||||
)
|
)
|
||||||
fmt.Fprintf(os.Stderr, " Allow access from other devices on the local network\n")
|
fmt.Fprintf(os.Stderr, " Allow access from other devices on the local network\n")
|
||||||
|
fmt.Fprintf(os.Stderr, " %s -host 0.0.0.0 ./config.json\n", os.Args[0])
|
||||||
|
fmt.Fprintf(os.Stderr, " Bind launcher and gateway host explicitly\n")
|
||||||
fmt.Fprintf(os.Stderr, " %s -console -d ./config.json\n", os.Args[0])
|
fmt.Fprintf(os.Stderr, " %s -console -d ./config.json\n", os.Args[0])
|
||||||
fmt.Fprintf(os.Stderr, " Run in the terminal with debug logs enabled\n")
|
fmt.Fprintf(os.Stderr, " Run in the terminal with debug logs enabled\n")
|
||||||
}
|
}
|
||||||
|
|
@ -175,8 +221,9 @@ func main() {
|
||||||
logger.DebugC(
|
logger.DebugC(
|
||||||
"web",
|
"web",
|
||||||
fmt.Sprintf(
|
fmt.Sprintf(
|
||||||
"Launcher flags: console=%t public=%t no_browser=%t config=%s",
|
"Launcher flags: console=%t host=%q public=%t no_browser=%t config=%s",
|
||||||
enableConsole,
|
enableConsole,
|
||||||
|
*host,
|
||||||
*public,
|
*public,
|
||||||
*noBrowser,
|
*noBrowser,
|
||||||
absPath,
|
absPath,
|
||||||
|
|
@ -186,10 +233,13 @@ func main() {
|
||||||
|
|
||||||
var explicitPort bool
|
var explicitPort bool
|
||||||
var explicitPublic bool
|
var explicitPublic bool
|
||||||
|
var explicitHost bool
|
||||||
flag.Visit(func(f *flag.Flag) {
|
flag.Visit(func(f *flag.Flag) {
|
||||||
switch f.Name {
|
switch f.Name {
|
||||||
case "port":
|
case "port":
|
||||||
explicitPort = true
|
explicitPort = true
|
||||||
|
case "host":
|
||||||
|
explicitHost = true
|
||||||
case "public":
|
case "public":
|
||||||
explicitPublic = true
|
explicitPublic = true
|
||||||
}
|
}
|
||||||
|
|
@ -210,6 +260,25 @@ func main() {
|
||||||
if !explicitPublic {
|
if !explicitPublic {
|
||||||
effectivePublic = launcherCfg.Public
|
effectivePublic = launcherCfg.Public
|
||||||
}
|
}
|
||||||
|
envHost := strings.TrimSpace(os.Getenv(launcherconfig.EnvLauncherHost))
|
||||||
|
|
||||||
|
effectiveHost, effectivePublic, hostExplicit, err := resolveLauncherBindHost(
|
||||||
|
*host,
|
||||||
|
explicitHost,
|
||||||
|
envHost,
|
||||||
|
effectivePublic,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf("Invalid host %q: %v", *host, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !explicitHost && envHost != "" {
|
||||||
|
logger.InfoC("web", "Using launcher host from environment PICOCLAW_LAUNCHER_HOST")
|
||||||
|
}
|
||||||
|
|
||||||
|
if hostExplicit && explicitPublic {
|
||||||
|
logger.InfoC("web", "Ignoring -public because launcher host was explicitly set")
|
||||||
|
}
|
||||||
|
|
||||||
portNum, err := strconv.Atoi(effectivePort)
|
portNum, err := strconv.Atoi(effectivePort)
|
||||||
if err != nil || portNum < 1 || portNum > 65535 {
|
if err != nil || portNum < 1 || portNum > 65535 {
|
||||||
|
|
@ -247,12 +316,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine listen address
|
// Determine listen address
|
||||||
var addr string
|
addr := net.JoinHostPort(effectiveHost, effectivePort)
|
||||||
if effectivePublic {
|
|
||||||
addr = "0.0.0.0:" + effectivePort
|
|
||||||
} else {
|
|
||||||
addr = "127.0.0.1:" + effectivePort
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize Server components
|
// Initialize Server components
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
@ -271,6 +335,7 @@ func main() {
|
||||||
logger.ErrorC("web", fmt.Sprintf("Warning: failed to ensure pico channel on startup: %v", err))
|
logger.ErrorC("web", fmt.Sprintf("Warning: failed to ensure pico channel on startup: %v", err))
|
||||||
}
|
}
|
||||||
apiHandler.SetServerOptions(portNum, effectivePublic, explicitPublic, launcherCfg.AllowedCIDRs)
|
apiHandler.SetServerOptions(portNum, effectivePublic, explicitPublic, launcherCfg.AllowedCIDRs)
|
||||||
|
apiHandler.SetServerBindHost(effectiveHost, hostExplicit)
|
||||||
apiHandler.RegisterRoutes(mux)
|
apiHandler.RegisterRoutes(mux)
|
||||||
|
|
||||||
// Frontend Embedded Assets
|
// Frontend Embedded Assets
|
||||||
|
|
@ -302,11 +367,14 @@ func main() {
|
||||||
fmt.Println(" Open the following URL in your browser:")
|
fmt.Println(" Open the following URL in your browser:")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Printf(" >> http://localhost:%s <<\n", effectivePort)
|
fmt.Printf(" >> http://localhost:%s <<\n", effectivePort)
|
||||||
if effectivePublic {
|
if isWildcardBindHost(effectiveHost) {
|
||||||
if ip := utils.GetLocalIP(); ip != "" {
|
if ip := utils.GetLocalIP(); ip != "" {
|
||||||
fmt.Printf(" >> http://%s:%s <<\n", ip, effectivePort)
|
fmt.Printf(" >> http://%s:%s <<\n", ip, effectivePort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if hostExplicit {
|
||||||
|
fmt.Printf(" >> http://%s <<\n", net.JoinHostPort(browserHostForLauncher(effectiveHost), effectivePort))
|
||||||
|
}
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
switch dashboardTokenSource {
|
switch dashboardTokenSource {
|
||||||
case launcherconfig.DashboardTokenSourceRandom:
|
case launcherconfig.DashboardTokenSourceRandom:
|
||||||
|
|
@ -331,15 +399,15 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log startup info to file
|
// Log startup info to file
|
||||||
logger.InfoC("web", fmt.Sprintf("Server will listen on http://localhost:%s", effectivePort))
|
logger.InfoC("web", fmt.Sprintf("Server will listen on http://%s", net.JoinHostPort(effectiveHost, effectivePort)))
|
||||||
if effectivePublic {
|
if isWildcardBindHost(effectiveHost) {
|
||||||
if ip := utils.GetLocalIP(); ip != "" {
|
if ip := utils.GetLocalIP(); ip != "" {
|
||||||
logger.InfoC("web", fmt.Sprintf("Public access enabled at http://%s:%s", ip, effectivePort))
|
logger.InfoC("web", fmt.Sprintf("Public access enabled at http://%s:%s", ip, effectivePort))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Share the local URL with the launcher runtime.
|
// Share the local URL with the launcher runtime.
|
||||||
serverAddr = fmt.Sprintf("http://localhost:%s", effectivePort)
|
serverAddr = fmt.Sprintf("http://%s", net.JoinHostPort(browserHostForLauncher(effectiveHost), effectivePort))
|
||||||
if dashboardToken != "" {
|
if dashboardToken != "" {
|
||||||
browserLaunchURL = serverAddr + "?token=" + url.QueryEscape(dashboardToken)
|
browserLaunchURL = serverAddr + "?token=" + url.QueryEscape(dashboardToken)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -95,3 +95,109 @@ func TestMaskSecret(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResolveLauncherBindHost(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
host string
|
||||||
|
envHost string
|
||||||
|
explicitHost bool
|
||||||
|
effectivePub bool
|
||||||
|
wantHost string
|
||||||
|
wantPublic bool
|
||||||
|
wantExplicit bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "explicit host overrides public",
|
||||||
|
host: "0.0.0.0",
|
||||||
|
explicitHost: true,
|
||||||
|
effectivePub: true,
|
||||||
|
wantHost: "0.0.0.0",
|
||||||
|
wantPublic: false,
|
||||||
|
wantExplicit: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit host overrides env host",
|
||||||
|
host: "127.0.0.1",
|
||||||
|
envHost: "0.0.0.0",
|
||||||
|
explicitHost: true,
|
||||||
|
effectivePub: true,
|
||||||
|
wantHost: "127.0.0.1",
|
||||||
|
wantPublic: false,
|
||||||
|
wantExplicit: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit host cannot be empty",
|
||||||
|
host: " ",
|
||||||
|
explicitHost: true,
|
||||||
|
effectivePub: false,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "env host overrides public",
|
||||||
|
envHost: "0.0.0.0",
|
||||||
|
explicitHost: false,
|
||||||
|
effectivePub: true,
|
||||||
|
wantHost: "0.0.0.0",
|
||||||
|
wantPublic: false,
|
||||||
|
wantExplicit: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "public mode without explicit host",
|
||||||
|
host: "",
|
||||||
|
explicitHost: false,
|
||||||
|
effectivePub: true,
|
||||||
|
wantHost: "0.0.0.0",
|
||||||
|
wantPublic: true,
|
||||||
|
wantExplicit: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "private mode without explicit host",
|
||||||
|
host: "",
|
||||||
|
explicitHost: false,
|
||||||
|
effectivePub: false,
|
||||||
|
wantHost: "127.0.0.1",
|
||||||
|
wantPublic: false,
|
||||||
|
wantExplicit: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
gotHost, gotPublic, gotExplicit, err := resolveLauncherBindHost(
|
||||||
|
tt.host,
|
||||||
|
tt.explicitHost,
|
||||||
|
tt.envHost,
|
||||||
|
tt.effectivePub,
|
||||||
|
)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Fatalf("resolveLauncherBindHost() error = %v, wantErr %t", err, tt.wantErr)
|
||||||
|
}
|
||||||
|
if tt.wantErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if gotHost != tt.wantHost {
|
||||||
|
t.Fatalf("resolveLauncherBindHost() host = %q, want %q", gotHost, tt.wantHost)
|
||||||
|
}
|
||||||
|
if gotPublic != tt.wantPublic {
|
||||||
|
t.Fatalf("resolveLauncherBindHost() public = %t, want %t", gotPublic, tt.wantPublic)
|
||||||
|
}
|
||||||
|
if gotExplicit != tt.wantExplicit {
|
||||||
|
t.Fatalf("resolveLauncherBindHost() explicit = %t, want %t", gotExplicit, tt.wantExplicit)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBrowserHostForLauncher(t *testing.T) {
|
||||||
|
if got := browserHostForLauncher("0.0.0.0"); got != "localhost" {
|
||||||
|
t.Fatalf("browserHostForLauncher(0.0.0.0) = %q, want %q", got, "localhost")
|
||||||
|
}
|
||||||
|
if got := browserHostForLauncher("::"); got != "localhost" {
|
||||||
|
t.Fatalf("browserHostForLauncher(::) = %q, want %q", got, "localhost")
|
||||||
|
}
|
||||||
|
if got := browserHostForLauncher("192.168.1.10"); got != "192.168.1.10" {
|
||||||
|
t.Fatalf("browserHostForLauncher(192.168.1.10) = %q, want %q", got, "192.168.1.10")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue