From 0dcdb89bb5c31c3bc81afac7176751d256504fec Mon Sep 17 00:00:00 2001 From: Akizon77 Date: Wed, 15 Oct 2025 12:25:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89HTTP=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=92=8CWebSocket?= =?UTF-8?q?=E6=8B=A8=E5=8F=B7=E5=99=A8=EF=BC=8C=E6=94=AF=E6=8C=81TLS?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build_all.ps1 | 72 +++++++++++++++++++++++++++++++ dnsresolver/resolver.go | 96 +++++++++++++++++++++++------------------ server/basicInfo.go | 1 - server/websocket.go | 55 +++++++++++------------ update/update.go | 4 +- 5 files changed, 152 insertions(+), 76 deletions(-) create mode 100644 build_all.ps1 diff --git a/build_all.ps1 b/build_all.ps1 new file mode 100644 index 0000000..aa06d2b --- /dev/null +++ b/build_all.ps1 @@ -0,0 +1,72 @@ +# Requires: PowerShell 5+, Go toolchain, git (optional for version) + +# Colors +$Red = 'Red' +$Green = 'Green' +$White = 'White' + +# OS/ARCH matrix +$osList = @('windows','linux','darwin','freebsd') +$archList = @('amd64','arm64','386','arm') + +# Ensure build directory +$buildDir = Join-Path -Path (Get-Location) -ChildPath 'build' +New-Item -ItemType Directory -Force -Path $buildDir | Out-Null + +# Detect version from git tags or fallback to dev +$version = (git describe --tags --abbrev=0 2>$null) +if (-not $version) { $version = 'dev' } +$version = $version.Trim() + +# Check go exists +if (-not (Get-Command go -ErrorAction SilentlyContinue)) { + Write-Host 'Go toolchain not found in PATH. Please install Go and try again.' -ForegroundColor $Red + exit 1 +} + +$failedBuilds = @() + +foreach ($goos in $osList) { + foreach ($goarch in $archList) { + # Skip unsupported combos: windows/arm, darwin/386, darwin/arm + if ((($goos -eq 'windows') -and ($goarch -eq 'arm')) -or + (($goos -eq 'darwin') -and (($goarch -eq '386') -or ($goarch -eq 'arm')))) { + continue + } + + Write-Host "Building for $goos/$goarch..." -ForegroundColor $White + + $binaryName = "komari-agent-$goos-$goarch" + if ($goos -eq 'windows') { $binaryName = "$binaryName.exe" } + $outPath = Join-Path $buildDir $binaryName + + # Set env per invocation + $env:GOOS = $goos + $env:GOARCH = $goarch + $env:CGO_ENABLED = '0' + + & go build -trimpath -ldflags "-s -w -X github.com/komari-monitor/komari-agent/update.CurrentVersion=$version" -o "$outPath" + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to build for $goos/$goarch" -ForegroundColor $Red + $failedBuilds += "$goos/$goarch" + } + else { + Write-Host "Successfully built $binaryName" -ForegroundColor $Green + } + + # Clear env to avoid affecting subsequent shells (optional) + Remove-Item Env:GOOS -ErrorAction SilentlyContinue + Remove-Item Env:GOARCH -ErrorAction SilentlyContinue + Remove-Item Env:CGO_ENABLED -ErrorAction SilentlyContinue + } +} + +if ($failedBuilds.Count -gt 0) { + Write-Host "`nThe following builds failed:" -ForegroundColor $Red + foreach ($b in $failedBuilds) { Write-Host "- $b" -ForegroundColor $Red } +} +else { + Write-Host "`nAll builds completed successfully." -ForegroundColor $Green +} + +Write-Host "`nBinaries are in the ./build directory." -ForegroundColor $White diff --git a/dnsresolver/resolver.go b/dnsresolver/resolver.go index 8505caf..7ff237d 100644 --- a/dnsresolver/resolver.go +++ b/dnsresolver/resolver.go @@ -2,6 +2,7 @@ package dnsresolver import ( "context" + "crypto/tls" "fmt" "log" "net" @@ -10,6 +11,8 @@ import ( "strings" "sync" "time" + + "github.com/komari-monitor/komari-agent/cmd/flags" ) var ( @@ -110,57 +113,64 @@ func GetCustomResolver() *net.Resolver { } // GetHTTPClient 返回一个使用自定义DNS解析器的HTTP客户端 -func GetHTTPClient(timeout time.Duration) *http.Client { +// buildTransport 构建带有自定义解析/拨号策略的 HTTP 传输层,可注入 TLS 配置 +func buildTransport(timeout time.Duration, tlsConfig *tls.Config) *http.Transport { if timeout <= 0 { timeout = 30 * time.Second } - customResolver := GetCustomResolver() - - return &http.Client{ - Transport: &http.Transport{ - Proxy: http.ProxyFromEnvironment, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { - host, port, err := net.SplitHostPort(addr) - if err != nil { - return nil, err + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } + ips, err := customResolver.LookupHost(ctx, host) + if err != nil { + return nil, err + } + // 根据本机是否具备 IPv4 动态排序 + preferIPv4 := preferIPv4First() + sort.SliceStable(ips, func(i, j int) bool { + ip1 := net.ParseIP(ips[i]) + ip2 := net.ParseIP(ips[j]) + if ip1 == nil || ip2 == nil { + return false } - ips, err := customResolver.LookupHost(ctx, host) - if err != nil { - return nil, err + if preferIPv4 { + return ip1.To4() != nil && ip2.To4() == nil } - // 根据本机是否具备 IPv4 动态排序 - preferIPv4 := preferIPv4First() - sort.SliceStable(ips, func(i, j int) bool { - ip1 := net.ParseIP(ips[i]) - ip2 := net.ParseIP(ips[j]) - if ip1 == nil || ip2 == nil { - return false - } - if preferIPv4 { - return ip1.To4() != nil && ip2.To4() == nil - } - // IPv6 优先 - return ip1.To4() == nil && ip2.To4() != nil - }) - for _, ip := range ips { - dialer := &net.Dialer{ - Timeout: 30 * time.Second, - KeepAlive: 30 * time.Second, - DualStack: true, - } - conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip, port)) - if err == nil { - return conn, nil - } + // IPv6 优先 + return ip1.To4() == nil && ip2.To4() != nil + }) + for _, ip := range ips { + dialer := &net.Dialer{ + Timeout: timeout, + KeepAlive: 30 * time.Second, + DualStack: true, } - return nil, fmt.Errorf("failed to dial to any of the resolved IPs") - }, - MaxIdleConns: 10, - IdleConnTimeout: 90 * time.Second, - TLSHandshakeTimeout: 10 * time.Second, - ExpectContinueTimeout: 1 * time.Second, + conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip, port)) + if err == nil { + return conn, nil + } + } + return nil, fmt.Errorf("failed to dial to any of the resolved IPs") }, + MaxIdleConns: 10, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + TLSClientConfig: tlsConfig, + ForceAttemptHTTP2: true, + } +} + +func GetHTTPClient(timeout time.Duration) *http.Client { + return &http.Client{ + Transport: buildTransport(timeout, &tls.Config{ + InsecureSkipVerify: flags.IgnoreUnsafeCert, + }), Timeout: timeout, } } diff --git a/server/basicInfo.go b/server/basicInfo.go index e2fb39e..fcd384c 100644 --- a/server/basicInfo.go +++ b/server/basicInfo.go @@ -87,7 +87,6 @@ func tryUploadData(data map[string]interface{}) error { req.Header.Set("CF-Access-Client-Secret", flags.CFAccessClientSecret) } - // 使用dnsresolver获取自定义HTTP客户端 client := dnsresolver.GetHTTPClient(30 * time.Second) resp, err := client.Do(req) diff --git a/server/websocket.go b/server/websocket.go index e00c4ef..9385204 100644 --- a/server/websocket.go +++ b/server/websocket.go @@ -92,23 +92,9 @@ func EstablishWebSocketConnection() { } func connectWebSocket(websocketEndpoint string) (*ws.SafeConn, error) { - // 使用自定义解析和连接策略(IPv4 优先,较长超时) - dialer := &websocket.Dialer{ - HandshakeTimeout: 15 * time.Second, - NetDialContext: dnsresolver.GetDialContext(15 * time.Second), - } + dialer := newWSDialer() - // 可选:忽略 TLS 证书(当用户显式设置) - if flags.IgnoreUnsafeCert { - dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - } - - // 创建请求头并添加Cloudflare Access头部 - headers := http.Header{} - if flags.CFAccessClientID != "" && flags.CFAccessClientSecret != "" { - headers.Set("CF-Access-Client-Id", flags.CFAccessClientID) - headers.Set("CF-Access-Client-Secret", flags.CFAccessClientSecret) - } + headers := newWSHeaders() conn, resp, err := dialer.Dial(websocketEndpoint, headers) if err != nil { @@ -170,20 +156,9 @@ func establishTerminalConnection(token, id, endpoint string) { endpoint = "ws" + strings.TrimPrefix(endpoint, "http") // 使用与主 WS 相同的拨号策略 - dialer := &websocket.Dialer{ - HandshakeTimeout: 15 * time.Second, - NetDialContext: dnsresolver.GetDialContext(15 * time.Second), - } - if flags.IgnoreUnsafeCert { - dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - } + dialer := newWSDialer() - // 创建请求头并添加Cloudflare Access头部 - headers := http.Header{} - if flags.CFAccessClientID != "" && flags.CFAccessClientSecret != "" { - headers.Set("CF-Access-Client-Id", flags.CFAccessClientID) - headers.Set("CF-Access-Client-Secret", flags.CFAccessClientSecret) - } + headers := newWSHeaders() conn, _, err := dialer.Dial(endpoint, headers) if err != nil { @@ -197,3 +172,25 @@ func establishTerminalConnection(token, id, endpoint string) { conn.Close() } } + +// newWSDialer 构造统一的 WebSocket 拨号器(自定义解析、IPv4/IPv6 动态排序、可选 TLS 忽略) +func newWSDialer() *websocket.Dialer { + d := &websocket.Dialer{ + HandshakeTimeout: 15 * time.Second, + NetDialContext: dnsresolver.GetDialContext(15 * time.Second), + } + if flags.IgnoreUnsafeCert { + d.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} + } + return d +} + +// newWSHeaders 统一构造 WS 请求头(含 Cloudflare Access 头) +func newWSHeaders() http.Header { + headers := http.Header{} + if flags.CFAccessClientID != "" && flags.CFAccessClientSecret != "" { + headers.Set("CF-Access-Client-Id", flags.CFAccessClientID) + headers.Set("CF-Access-Client-Secret", flags.CFAccessClientSecret) + } + return headers +} diff --git a/update/update.go b/update/update.go index 21c74de..aa7845b 100644 --- a/update/update.go +++ b/update/update.go @@ -47,9 +47,7 @@ func CheckAndUpdate() error { return fmt.Errorf("failed to parse current version: %v", err) } - // 使用dnsresolver创建自定义HTTP客户端并设置为全局默认客户端 - // 这会影响所有HTTP请求,包括selfupdate库中的请求 - http.DefaultClient = dnsresolver.GetHTTPClient(60 * time.Second) // Create selfupdate configuration + http.DefaultClient = dnsresolver.GetHTTPClient(60 * time.Second) config := selfupdate.Config{} updater, err := selfupdate.NewUpdater(config) if err != nil {