diff --git a/admin-server/gateway.go b/admin-server/gateway.go index 3a3636a..3668da1 100644 --- a/admin-server/gateway.go +++ b/admin-server/gateway.go @@ -44,10 +44,14 @@ type GatewayConfig struct { AuthURL string `yaml:"authUrl,omitempty" json:"authUrl,omitempty"` LoginURL string `yaml:"loginUrl,omitempty" json:"loginUrl,omitempty"` - ListenPort int `yaml:"listenPort,omitempty" json:"listenPort,omitempty"` - ListenTLSPort int `yaml:"listenTlsPort,omitempty" json:"listenTlsPort,omitempty"` - RedirectPort int `yaml:"redirectPort,omitempty" json:"redirectPort,omitempty"` - Routes []Route `yaml:"routes" json:"routes"` + ListenPort int `yaml:"listenPort,omitempty" json:"listenPort,omitempty"` + ListenTLSPort int `yaml:"listenTlsPort,omitempty" json:"listenTlsPort,omitempty"` + RedirectPort int `yaml:"redirectPort,omitempty" json:"redirectPort,omitempty"` + // RedirectWithPort:http→https 跳转时是否带上入口端口。 + // 默认 false(跳 https://host,适用于前置 frp/443 的场景); + // 全新安装且对外端口=入口端口时置 true。 + RedirectWithPort bool `yaml:"redirectWithPort,omitempty" json:"redirectWithPort,omitempty"` + Routes []Route `yaml:"routes" json:"routes"` } func defaultGatewayConfig() *GatewayConfig { @@ -319,7 +323,15 @@ func RenderGatewayConf(cfg *GatewayConfig) (string, error) { b.WriteString("# 由 HomeDash 后台生成,请勿手改。源: " + gatewayFile + "\n\n") if cfg.ListenHTTPS && cfg.RedirectPort > 0 { - fmt.Fprintf(&b, "server {\n listen %d default_server;\n server_name _;\n return 302 https://$host$request_uri;\n}\n\n", cfg.RedirectPort) + // 跳转目标是否带上入口端口:由 redirectWithPort 显式决定。 + // 不能用"端口 != 443"推导——生产容器监听 3080,但对外经 frp 映射为 443, + // 那种情况下带端口反而把公网用户导向 https://host:3080(打不开)。 + // 只有在"对外端口就等于入口端口"的部署(如全新安装 13080)才应开启。 + redirectHost := "$host" + if cfg.RedirectWithPort && cfg.ListenPort != 443 { + redirectHost = fmt.Sprintf("$host:%d", cfg.ListenPort) + } + fmt.Fprintf(&b, "server {\n listen %d default_server;\n server_name _;\n return 302 https://%s$request_uri;\n}\n\n", cfg.RedirectPort, redirectHost) } // 默认服务器:保留原统一代理的自定义错误页设计。 diff --git a/admin-server/gateway_test.go b/admin-server/gateway_test.go index 612a746..5a6dcc1 100644 --- a/admin-server/gateway_test.go +++ b/admin-server/gateway_test.go @@ -389,3 +389,52 @@ func TestPortalKindNoDuplicateRoot(t *testing.T) { t.Fatalf("门户段内 root 指令应恰好 1 次,实际 %d 次:\n%s", n, block) } } + +// redirectWithPort=true 时跳转目标带入口端口(对外端口=入口端口的部署)。 +func TestRedirectTargetIncludesEntryPort(t *testing.T) { + out, err := RenderGatewayConf(&GatewayConfig{ + ListenPort: 3080, ListenTLSPort: 8443, RedirectPort: 3081, ListenHTTPS: true, RedirectWithPort: true, + CertFile: "/etc/nginx/certs/fullchain.pem", KeyFile: "/etc/nginx/certs/privkey.pem", + AuthURL: "http://127.0.0.1:3005/auth/verify", LoginURL: "https://login.x.com/login", + Routes: []Route{{Host: "x.com", Kind: "portal"}}, + }) + if err != nil { + t.Fatalf("渲染失败: %v", err) + } + if !strings.Contains(out, "return 302 https://$host:3080$request_uri;") { + t.Fatalf("跳转目标未带端口:\n%s", out) + } +} + +// 标准 443 部署不应出现 :443 后缀。 +func TestRedirectTargetOmitsPortOn443(t *testing.T) { + out, err := RenderGatewayConf(&GatewayConfig{ + ListenPort: 443, RedirectPort: 3081, ListenHTTPS: true, + CertFile: "/etc/nginx/certs/fullchain.pem", KeyFile: "/etc/nginx/certs/privkey.pem", + AuthURL: "http://127.0.0.1:3005/auth/verify", LoginURL: "https://login.x.com/login", + Routes: []Route{{Host: "x.com", Kind: "portal"}}, + }) + if err != nil { + t.Fatalf("渲染失败: %v", err) + } + if !strings.Contains(out, "return 302 https://$host$request_uri;") || strings.Contains(out, "$host:443") { + t.Fatalf("443 部署跳转目标不正确:\n%s", out) + } +} + +// 默认(redirectWithPort=false)即使入口不是 443 也不带端口—— +// 生产就是这种:监听 3080,对外经 frp 是 443,带端口会把公网用户导向 :3080。 +func TestRedirectDefaultsNoPortOnNonStandardPort(t *testing.T) { + out, err := RenderGatewayConf(&GatewayConfig{ + ListenPort: 3080, RedirectPort: 3081, ListenHTTPS: true, RedirectWithPort: false, + CertFile: "/etc/nginx/certs/fullchain.pem", KeyFile: "/etc/nginx/certs/privkey.pem", + AuthURL: "http://127.0.0.1:3005/auth/verify", LoginURL: "https://login.x.com/login", + Routes: []Route{{Host: "x.com", Kind: "portal"}}, + }) + if err != nil { + t.Fatalf("渲染失败: %v", err) + } + if !strings.Contains(out, "return 302 https://$host$request_uri;") || strings.Contains(out, "$host:3080") { + t.Fatalf("默认不应带端口:\n%s", out) + } +} diff --git a/deploy/install-host.sh b/deploy/install-host.sh index 88c2ff3..92d6d52 100755 --- a/deploy/install-host.sh +++ b/deploy/install-host.sh @@ -102,6 +102,11 @@ else echo "listenTlsPort: $TLS_PORT" echo "redirectPort: $REDIRECT_PORT" echo "listenHttps: $([ "$TLS" = 1 ] && echo true || echo false)" + # 对外端口=入口端口的部署(全新安装通常如此),跳转要带上端口; + # 若前面有 frp/nginx 把 443 映射到本入口端口,请删掉这一行。 + if [ "$TLS" = "1" ] && [ "$ENTRY_PORT" != "443" ]; then + echo "redirectWithPort: true" + fi if [ "$TLS" = 1 ]; then echo "certFile: $CERT_FILE" echo "keyFile: $KEY_FILE"