WebSocket 把 proxy_http_version 1.1 + Upgrade/Connection 移进 location / 内部
CI / verify (push) Canceled after 0s

This commit is contained in:
2026-10-17 11:58:43 +08:00
parent c1bcdd0b86
commit 6b8ff0e5a3
6 changed files with 35 additions and 12 deletions
+10
View File
@@ -81,6 +81,16 @@ func TestNginxRouteSharedTemplateSemantics(t *testing.T) {
} }
} }
denyIdx := strings.Index(out, "deny 8.8.8.8;") denyIdx := strings.Index(out, "deny 8.8.8.8;")
// WebSocket 头必须落在 location 内部:location 自定义 proxy_set_header 后,
// server 级的那组不会被继承,写在 server 块里等于没生效(升级请求会一直挂起)
locationIdx := strings.Index(out, "location / {")
upgradeIdx := strings.Index(out, "proxy_set_header Upgrade $http_upgrade;")
if locationIdx < 0 || upgradeIdx < 0 || upgradeIdx < locationIdx {
t.Fatalf("Upgrade 头必须写在 location 内:\n%s", out)
}
if !strings.Contains(out, "proxy_read_timeout 3600s;") {
t.Fatalf("WebSocket 未配读超时时应给长连接默认超时:\n%s", out)
}
allowIdx := strings.Index(out, "allow 192.168.1.0/24;") allowIdx := strings.Index(out, "allow 192.168.1.0/24;")
denyAllIdx := strings.Index(out, "deny all;") denyAllIdx := strings.Index(out, "deny all;")
if denyIdx < 0 || allowIdx < 0 || denyAllIdx < 0 || !(denyIdx < allowIdx && allowIdx < denyAllIdx) { if denyIdx < 0 || allowIdx < 0 || denyAllIdx < 0 || !(denyIdx < allowIdx && allowIdx < denyAllIdx) {
+12 -6
View File
@@ -31,7 +31,7 @@ import (
"github.com/havline/havline/internal/sysinfo" "github.com/havline/havline/internal/sysinfo"
) )
const AgentVersion = "0.15.3" const AgentVersion = "0.15.4"
// Config 是 havline-agent 的运行配置。agent 只在 VPS 本地管理自己的 Nginx 片段、证书和 frps 配置。 // Config 是 havline-agent 的运行配置。agent 只在 VPS 本地管理自己的 Nginx 片段、证书和 frps 配置。
type Config struct { type Config struct {
@@ -1656,10 +1656,6 @@ func nginxRoute(req routeRequest, certDir string) string {
if req.ConnLimitMax > 0 { if req.ConnLimitMax > 0 {
b.WriteString(" limit_conn_status 503;\n") b.WriteString(" limit_conn_status 503;\n")
} }
if req.WebSocket {
b.WriteString(" proxy_http_version 1.1;\n")
b.WriteString(nginxtmpl.UpgradeHeaders(" ", "\"upgrade\""))
}
// IP 访问控制:白名单(allow + deny all)与黑名单(deny)可同时生效; // IP 访问控制:白名单(allow + deny all)与黑名单(deny)可同时生效;
// 顺序由共享模板统一:deny → 仅大陆规则 → allow → deny all。 // 顺序由共享模板统一:deny → 仅大陆规则 → allow → deny all。
chinaDeny := "" chinaDeny := ""
@@ -1690,7 +1686,17 @@ func nginxRoute(req routeRequest, certDir string) string {
Burst: req.RateLimitBurst, Burst: req.RateLimitBurst,
Conn: req.ConnLimitMax, Conn: req.ConnLimitMax,
}, " ") }, " ")
fmt.Fprintf(&b, " location / {\n%s%s%s proxy_pass http://%s;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_http_version 1.1;\n }\n}\n", aclText, authLine, limitText, req.Upstream) // WebSocket 必须写在 location 里:location 一旦自定义了 proxy_set_header,
// server 级的那组不会被继承——写在 server 块等于没写,表现就是升级请求一直挂起。
wsText := ""
if req.WebSocket {
wsText = nginxtmpl.UpgradeHeaders(" ", "\"upgrade\"")
// 规则没显式配 proxy_read_timeout 时给长连接留足空闲时间,避免 60 秒被 Nginx 掐断
if strings.TrimSpace(req.ProxyReadTimeout) == "" {
wsText += " proxy_read_timeout 3600s;\n"
}
}
fmt.Fprintf(&b, " location / {\n%s%s%s proxy_pass http://%s;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_http_version 1.1;\n%s }\n}\n", aclText, authLine, limitText, req.Upstream, wsText)
return b.String() return b.String()
} }
Binary file not shown.
Binary file not shown.
+11 -4
View File
@@ -4,14 +4,21 @@ Manual OCI layout pusher for registries whose token realm is misconfigured.
This script is an emergency fallback. It reads Docker's OCI layout output, This script is an emergency fallback. It reads Docker's OCI layout output,
obtains a Bearer token over HTTPS from the Git credential store, and uploads obtains a Bearer token over HTTPS from the Git credential store, and uploads
the blobs and image manifest directly through the Registry HTTP API. the blobs and image manifest directly through the Registry HTTP API.
默认面向 GitHub Container Registry(ghcr.io)。如果要用回自建 Gitea registry:
-Registry jayhub.top -Repository mrlee/havline -CredentialHost jayhub.top `
-TokenPath /v2/token -TokenService container_registry
#> #>
param( param(
[Parameter(Mandatory = $true)][string]$LayoutDir, [Parameter(Mandatory = $true)][string]$LayoutDir,
[Parameter(Mandatory = $true)][string]$Platform, [Parameter(Mandatory = $true)][string]$Platform,
[Parameter(Mandatory = $true)][string]$Tag, [Parameter(Mandatory = $true)][string]$Tag,
[string]$Registry = "jayhub.top", [string]$Registry = "ghcr.io",
[string]$Repository = "mrlee/havline", [string]$Repository = "mrlee233/havline",
[string]$CredentialHost = "jayhub.top" [string]$CredentialHost = "ghcr.io",
# GHCR: /token?service=ghcr.io;Gitea: /v2/token?service=container_registry
[string]$TokenPath = "/token",
[string]$TokenService = "ghcr.io"
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -33,7 +40,7 @@ function Get-RegistryBasicAuth {
function Get-BearerToken([string]$Scope) { function Get-BearerToken([string]$Scope) {
$basic = Get-RegistryBasicAuth $basic = Get-RegistryBasicAuth
$url = "https://$Registry/v2/token?service=container_registry&scope=$Scope" $url = "https://$Registry$TokenPath?service=$TokenService&scope=$Scope"
$response = Invoke-RestMethod -Uri $url -Headers @{ Authorization = $basic } $response = Invoke-RestMethod -Uri $url -Headers @{ Authorization = $basic }
if (-not $response.token) { throw "Registry did not return a token" } if (-not $response.token) { throw "Registry did not return a token" }
return $response.token return $response.token
+2 -2
View File
@@ -5,7 +5,7 @@
# #
# 可选环境变量: # 可选环境变量:
# COMPOSE_FILE=docker-compose.hub.yml # 要用的编排文件 # COMPOSE_FILE=docker-compose.hub.yml # 要用的编排文件
# REPO=http://.../mrlee/Havline.git # 源码仓库 # REPO=https://github.com/mrlee233/havline.git # 源码仓库
# BRANCH=main # 分支 # BRANCH=main # 分支
# #
# 它做四件事: # 它做四件事:
@@ -16,7 +16,7 @@
# 4) 打印「回滚到上一个 tag」的命令 # 4) 打印「回滚到上一个 tag」的命令
set -euo pipefail set -euo pipefail
REPO="${REPO:-https://jayhub.top/mrlee/Havline.git}" REPO="${REPO:-https://github.com/mrlee233/havline.git}"
BRANCH="${BRANCH:-main}" BRANCH="${BRANCH:-main}"
COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.hub.yml}" COMPOSE_FILE="${COMPOSE_FILE:-docker-compose.hub.yml}"
APP_CONTAINER="${APP_CONTAINER:-havline}" APP_CONTAINER="${APP_CONTAINER:-havline}"