mirror of
https://github.com/fankes/komari-agent.git
synced 2025-10-18 18:49:23 +08:00
feat: 添加自定义HTTP客户端和WebSocket拨号器,支持TLS配置
This commit is contained in:
72
build_all.ps1
Normal file
72
build_all.ps1
Normal file
@@ -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
|
@@ -2,6 +2,7 @@ package dnsresolver
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
@@ -10,6 +11,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/komari-monitor/komari-agent/cmd/flags"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -110,57 +113,64 @@ func GetCustomResolver() *net.Resolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetHTTPClient 返回一个使用自定义DNS解析器的HTTP客户端
|
// 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 {
|
if timeout <= 0 {
|
||||||
timeout = 30 * time.Second
|
timeout = 30 * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
customResolver := GetCustomResolver()
|
customResolver := GetCustomResolver()
|
||||||
|
return &http.Transport{
|
||||||
return &http.Client{
|
Proxy: http.ProxyFromEnvironment,
|
||||||
Transport: &http.Transport{
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
Proxy: http.ProxyFromEnvironment,
|
host, port, err := net.SplitHostPort(addr)
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
if err != nil {
|
||||||
host, port, err := net.SplitHostPort(addr)
|
return nil, err
|
||||||
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 preferIPv4 {
|
||||||
if err != nil {
|
return ip1.To4() != nil && ip2.To4() == nil
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
// 根据本机是否具备 IPv4 动态排序
|
// IPv6 优先
|
||||||
preferIPv4 := preferIPv4First()
|
return ip1.To4() == nil && ip2.To4() != nil
|
||||||
sort.SliceStable(ips, func(i, j int) bool {
|
})
|
||||||
ip1 := net.ParseIP(ips[i])
|
for _, ip := range ips {
|
||||||
ip2 := net.ParseIP(ips[j])
|
dialer := &net.Dialer{
|
||||||
if ip1 == nil || ip2 == nil {
|
Timeout: timeout,
|
||||||
return false
|
KeepAlive: 30 * time.Second,
|
||||||
}
|
DualStack: true,
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to dial to any of the resolved IPs")
|
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip, port))
|
||||||
},
|
if err == nil {
|
||||||
MaxIdleConns: 10,
|
return conn, nil
|
||||||
IdleConnTimeout: 90 * time.Second,
|
}
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
}
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
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,
|
Timeout: timeout,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -87,7 +87,6 @@ func tryUploadData(data map[string]interface{}) error {
|
|||||||
req.Header.Set("CF-Access-Client-Secret", flags.CFAccessClientSecret)
|
req.Header.Set("CF-Access-Client-Secret", flags.CFAccessClientSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用dnsresolver获取自定义HTTP客户端
|
|
||||||
client := dnsresolver.GetHTTPClient(30 * time.Second)
|
client := dnsresolver.GetHTTPClient(30 * time.Second)
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
|
@@ -92,23 +92,9 @@ func EstablishWebSocketConnection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func connectWebSocket(websocketEndpoint string) (*ws.SafeConn, error) {
|
func connectWebSocket(websocketEndpoint string) (*ws.SafeConn, error) {
|
||||||
// 使用自定义解析和连接策略(IPv4 优先,较长超时)
|
dialer := newWSDialer()
|
||||||
dialer := &websocket.Dialer{
|
|
||||||
HandshakeTimeout: 15 * time.Second,
|
|
||||||
NetDialContext: dnsresolver.GetDialContext(15 * time.Second),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 可选:忽略 TLS 证书(当用户显式设置)
|
headers := newWSHeaders()
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, resp, err := dialer.Dial(websocketEndpoint, headers)
|
conn, resp, err := dialer.Dial(websocketEndpoint, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -170,20 +156,9 @@ func establishTerminalConnection(token, id, endpoint string) {
|
|||||||
endpoint = "ws" + strings.TrimPrefix(endpoint, "http")
|
endpoint = "ws" + strings.TrimPrefix(endpoint, "http")
|
||||||
|
|
||||||
// 使用与主 WS 相同的拨号策略
|
// 使用与主 WS 相同的拨号策略
|
||||||
dialer := &websocket.Dialer{
|
dialer := newWSDialer()
|
||||||
HandshakeTimeout: 15 * time.Second,
|
|
||||||
NetDialContext: dnsresolver.GetDialContext(15 * time.Second),
|
|
||||||
}
|
|
||||||
if flags.IgnoreUnsafeCert {
|
|
||||||
dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建请求头并添加Cloudflare Access头部
|
headers := newWSHeaders()
|
||||||
headers := http.Header{}
|
|
||||||
if flags.CFAccessClientID != "" && flags.CFAccessClientSecret != "" {
|
|
||||||
headers.Set("CF-Access-Client-Id", flags.CFAccessClientID)
|
|
||||||
headers.Set("CF-Access-Client-Secret", flags.CFAccessClientSecret)
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, _, err := dialer.Dial(endpoint, headers)
|
conn, _, err := dialer.Dial(endpoint, headers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -197,3 +172,25 @@ func establishTerminalConnection(token, id, endpoint string) {
|
|||||||
conn.Close()
|
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
|
||||||
|
}
|
||||||
|
@@ -47,9 +47,7 @@ func CheckAndUpdate() error {
|
|||||||
return fmt.Errorf("failed to parse current version: %v", err)
|
return fmt.Errorf("failed to parse current version: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用dnsresolver创建自定义HTTP客户端并设置为全局默认客户端
|
http.DefaultClient = dnsresolver.GetHTTPClient(60 * time.Second)
|
||||||
// 这会影响所有HTTP请求,包括selfupdate库中的请求
|
|
||||||
http.DefaultClient = dnsresolver.GetHTTPClient(60 * time.Second) // Create selfupdate configuration
|
|
||||||
config := selfupdate.Config{}
|
config := selfupdate.Config{}
|
||||||
updater, err := selfupdate.NewUpdater(config)
|
updater, err := selfupdate.NewUpdater(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Reference in New Issue
Block a user