From b01e49de49af74b266f7c96beb16d16a32d24671 Mon Sep 17 00:00:00 2001 From: HomeDash Bot Date: Sat, 12 Sep 2026 09:52:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(gateway):=20http=E2=86=92https=20=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E6=98=AF=E5=90=A6=E5=B8=A6=E7=AB=AF=E5=8F=A3=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=98=BE=E5=BC=8F=E5=BC=80=E5=85=B3=EF=BC=88=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E7=94=9F=E4=BA=A7=E5=9B=9E=E5=BD=92=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 练兵发现:跳转写成 return 302 https://$host$request_uri(不带端口), 非 443 端口的新装会跳到 443 打不开。 但直接用'端口 != 443 就带端口'推导会把生产弄坏:生产容器监听 3080, 对外经 frp 映射为 443,带端口会把公网用户导向 https://host:3080。 因此新增 gateway.yml 字段 redirectWithPort(默认 false,行为不变): - 默认:跳 https://host(前置 frp/443 的部署,含现生产) - 全新安装且对外端口=入口端口:install-host.sh 自动置 true 测试补两条:开关为真时带端口、默认(生产形态)不带端口。 --- admin-server/gateway.go | 22 ++++++++++++---- admin-server/gateway_test.go | 49 ++++++++++++++++++++++++++++++++++++ deploy/install-host.sh | 5 ++++ 3 files changed, 71 insertions(+), 5 deletions(-) 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"