fix: remote agent exec url_path_prefix error

This commit is contained in:
engigu
2026-01-14 10:04:29 +08:00
parent f2641f45b3
commit f5f6affd7a
5 changed files with 52 additions and 15 deletions
+1
View File
@@ -10,6 +10,7 @@ baihu
# Data & Logs # Data & Logs
data/ data/
agent/logs/
!data/agent/ !data/agent/
data/agent/* data/agent/*
!data/agent/version.txt !data/agent/version.txt
+11 -1
View File
@@ -202,9 +202,19 @@ func (a *Agent) connectWS() error {
wsURL = strings.Replace(wsURL, "https://", "wss://", 1) wsURL = strings.Replace(wsURL, "https://", "wss://", 1)
wsURL = fmt.Sprintf("%s/api/agent/ws?token=%s&machine_id=%s", wsURL, url.QueryEscape(a.config.Token), url.QueryEscape(a.machineID)) wsURL = fmt.Sprintf("%s/api/agent/ws?token=%s&machine_id=%s", wsURL, url.QueryEscape(a.config.Token), url.QueryEscape(a.machineID))
log.Infof("正在连接 WebSocket: %s", wsURL)
log.Infof("Token: %s..., MachineID: %s...", a.config.Token[:8], a.machineID[:16])
dialer := websocket.Dialer{HandshakeTimeout: 10 * time.Second} dialer := websocket.Dialer{HandshakeTimeout: 10 * time.Second}
conn, _, err := dialer.Dial(wsURL, nil) conn, resp, err := dialer.Dial(wsURL, nil)
if err != nil { if err != nil {
if resp != nil {
bodyBytes, _ := io.ReadAll(resp.Body)
log.Errorf("WebSocket 握手失败: HTTP %d, Body: %s", resp.StatusCode, string(bodyBytes))
resp.Body.Close()
} else {
log.Errorf("WebSocket 连接失败: %v", err)
}
return err return err
} }
+4
View File
@@ -1,6 +1,10 @@
[agent] [agent]
# 主服务器地址(http/httpsAgent 会自动转换为 WebSocket 连接) # 主服务器地址(http/httpsAgent 会自动转换为 WebSocket 连接)
# 如果主服务配置了url_prefix, 这里要也要加上路径
server_url = http://192.168.1.100:8052 server_url = http://192.168.1.100:8052
# 比如 url_prefix=/baihu
; server_url = http://192.168.1.100:8052/baihu
# Agent 名称(留空则使用主机名) # Agent 名称(留空则使用主机名)
name = name =
# 注册令牌(首次注册时填写,注册成功后会自动替换为认证 Token) # 注册令牌(首次注册时填写,注册成功后会自动替换为认证 Token)
+23 -1
View File
@@ -337,8 +337,19 @@ func (c *AgentController) ForceUpdate(ctx *gin.Context) {
// WSConnect Agent WebSocket 连接 // WSConnect Agent WebSocket 连接
func (c *AgentController) WSConnect(ctx *gin.Context) { func (c *AgentController) WSConnect(ctx *gin.Context) {
// 添加 panic 恢复
defer func() {
if r := recover(); r != nil {
logger.Errorf("[AgentWS] WSConnect panic: %v", r)
ctx.JSON(http.StatusInternalServerError, gin.H{"error": "服务器内部错误"})
}
}()
ip := ctx.ClientIP() ip := ctx.ClientIP()
// 打印请求信息用于调试
logger.Infof("[AgentWS] 收到连接请求: IP=%s, URL=%s", ip, ctx.Request.URL.String())
// 检查 IP 限流 // 检查 IP 限流
if allowed, reason := c.wsManager.CheckRateLimit(ip); !allowed { if allowed, reason := c.wsManager.CheckRateLimit(ip); !allowed {
logger.Warnf("[AgentWS] IP %s 被限流: %s", ip, reason) logger.Warnf("[AgentWS] IP %s 被限流: %s", ip, reason)
@@ -349,36 +360,45 @@ func (c *AgentController) WSConnect(ctx *gin.Context) {
token := ctx.Query("token") token := ctx.Query("token")
if token == "" { if token == "" {
c.wsManager.RecordConnectFail(ip) c.wsManager.RecordConnectFail(ip)
logger.Warnf("[AgentWS] 连接失败: 缺少 token, IP=%s", ip)
ctx.JSON(http.StatusUnauthorized, gin.H{"error": "缺少 token"}) ctx.JSON(http.StatusUnauthorized, gin.H{"error": "缺少 token"})
return return
} }
machineID := ctx.Query("machine_id") machineID := ctx.Query("machine_id")
logger.Infof("[AgentWS] Token: %s..., MachineID: %s...", token[:8], machineID[:16])
isNewAgent := false isNewAgent := false
// 先尝试用 token 查找已有 Agent // 先尝试用 token 查找已有 Agent
agent := c.agentService.GetByToken(token) agent := c.agentService.GetByToken(token)
logger.Infof("[AgentWS] GetByToken 结果: agent=%v", agent != nil)
// 如果没找到,尝试用令牌注册(会检查 machine_id 是否已存在) // 如果没找到,尝试用令牌注册(会检查 machine_id 是否已存在)
if agent == nil { if agent == nil {
logger.Infof("[AgentWS] 尝试注册新 Agent")
var err error var err error
agent, isNewAgent, err = c.agentService.RegisterByToken(token, machineID, ip) agent, isNewAgent, err = c.agentService.RegisterByToken(token, machineID, ip)
if err != nil { if err != nil {
c.wsManager.RecordConnectFail(ip) c.wsManager.RecordConnectFail(ip)
logger.Warnf("[AgentWS] 注册失败: %v, IP=%s, token=%s", err, ip, token[:8]+"...")
ctx.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) ctx.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return return
} }
logger.Infof("[AgentWS] 注册成功: Agent #%d, isNew=%v", agent.ID, isNewAgent)
} }
if !agent.Enabled { if !agent.Enabled {
c.wsManager.RecordConnectFail(ip) c.wsManager.RecordConnectFail(ip)
logger.Warnf("[AgentWS] Agent #%d 已禁用, IP=%s", agent.ID, ip)
ctx.JSON(http.StatusForbidden, gin.H{"error": "Agent 已禁用"}) ctx.JSON(http.StatusForbidden, gin.H{"error": "Agent 已禁用"})
return return
} }
logger.Infof("[AgentWS] 准备升级连接: Agent #%d, IP=%s", agent.ID, ip)
conn, err := agentUpgrader.Upgrade(ctx.Writer, ctx.Request, nil) conn, err := agentUpgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil { if err != nil {
logger.Errorf("[AgentWS] 升级连接失败: %v", err) logger.Errorf("[AgentWS] 升级连接失败: %v, Agent #%d, IP=%s", err, agent.ID, ip)
return return
} }
@@ -399,6 +419,8 @@ func (c *AgentController) WSConnect(ctx *gin.Context) {
"machine_id": machineID, "machine_id": machineID,
}) })
logger.Infof("[AgentWS] Agent #%d 连接成功", agent.ID)
// 启动读写协程 // 启动读写协程
go c.wsWritePump(ac) go c.wsWritePump(ac)
go c.wsReadPump(ac, agent) go c.wsReadPump(ac, agent)
+9 -9
View File
@@ -217,16 +217,16 @@ func Setup(c *Controllers) *gin.Engine {
agents.DELETE("/tokens/:id", c.Agent.DeleteToken) agents.DELETE("/tokens/:id", c.Agent.DeleteToken)
} }
} }
}
// Agent API(供远程 Agent 调用) // Agent API(供远程 Agent 调用,不使用 /v1 版本号
agentAPI := api.Group("/agent") agentAPI := root.Group("/api/agent")
{ {
agentAPI.POST("/heartbeat", c.Agent.Heartbeat) agentAPI.POST("/heartbeat", c.Agent.Heartbeat)
agentAPI.GET("/tasks", c.Agent.GetTasks) agentAPI.GET("/tasks", c.Agent.GetTasks)
agentAPI.POST("/report", c.Agent.ReportResult) agentAPI.POST("/report", c.Agent.ReportResult)
agentAPI.GET("/download", c.Agent.Download) agentAPI.GET("/download", c.Agent.Download)
agentAPI.GET("/ws", c.Agent.WSConnect) // WebSocket 连接 agentAPI.GET("/ws", c.Agent.WSConnect) // WebSocket 连接
}
} }
// SPA fallback - serve index.html (no cache for HTML) // SPA fallback - serve index.html (no cache for HTML)