From 3cf4d7e5a9b1b39101eb40d1d66f522b71fb58d1 Mon Sep 17 00:00:00 2001 From: dushixiang Date: Fri, 9 Jan 2026 21:53:44 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=9B=AE=E5=BD=95=E4=BF=9D?= =?UTF-8?q?=E6=8A=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/handler/agent_query_handler.go | 5 + internal/handler/agent_ws_handler.go | 42 ++----- internal/handler/ssh_login_handler.go | 25 +--- internal/handler/tamper_handler.go | 6 +- internal/repo/agent_repo.go | 38 ------ internal/service/ssh_login_service.go | 77 ++++--------- internal/service/tamper_service.go | 108 ++++++++++++++---- pkg/agent/tamper/tamper_protector.go | 96 ++++++++++++++-- .../components/server/SystemInfoSection.tsx | 13 +-- 9 files changed, 226 insertions(+), 184 deletions(-) diff --git a/internal/handler/agent_query_handler.go b/internal/handler/agent_query_handler.go index b199416..8374be4 100644 --- a/internal/handler/agent_query_handler.go +++ b/internal/handler/agent_query_handler.go @@ -8,6 +8,7 @@ import ( "github.com/dushixiang/pika/internal/utils" "github.com/go-orz/orz" "github.com/labstack/echo/v4" + "gorm.io/datatypes" ) // Get 获取探针详情(公开接口,已登录返回全部,未登录返回公开可见) @@ -22,6 +23,10 @@ func (h *AgentHandler) Get(c echo.Context) error { return err } + // 隐藏敏感配置 + agent.SSHLoginConfig = datatypes.JSONType[models.SSHLoginConfigData]{} + agent.TamperProtectConfig = datatypes.JSONType[models.TamperProtectConfigData]{} + // 未登录时隐藏敏感信息 if !isAuthenticated { agent.IP = "" diff --git a/internal/handler/agent_ws_handler.go b/internal/handler/agent_ws_handler.go index c2f1065..7c8c679 100644 --- a/internal/handler/agent_ws_handler.go +++ b/internal/handler/agent_ws_handler.go @@ -86,10 +86,10 @@ func (h *AgentHandler) handleWebSocketMessage(ctx context.Context, agentID strin return h.handleSSHLoginEventMessage(ctx, agentID, data) case protocol.MessageTypeSSHLoginConfigResult: - return h.handleSSHLoginConfigResultMessage(agentID, data) + return h.handleSSHLoginConfigResultMessage(ctx, agentID, data) case protocol.MessageTypeTamperProtect: - return h.handleTamperProtectMessage(agentID, data) + return h.handleTamperProtectMessage(ctx, agentID, data) default: h.logger.Warn("unknown message type", zap.String("type", messageType)) @@ -192,34 +192,22 @@ func (h *AgentHandler) handleSSHLoginEventMessage(ctx context.Context, agentID s return h.sshLoginService.HandleEvent(ctx, agentID, eventData) } -func (h *AgentHandler) handleSSHLoginConfigResultMessage(agentID string, data json.RawMessage) error { +func (h *AgentHandler) handleSSHLoginConfigResultMessage(ctx context.Context, agentID string, data json.RawMessage) error { var resultData protocol.SSHLoginConfigResult if err := json.Unmarshal(data, &resultData); err != nil { h.logger.Error("failed to unmarshal ssh login config result", zap.Error(err)) return err } - return h.sshLoginService.HandleConfigResult(agentID, resultData) + return h.sshLoginService.HandleConfigResult(ctx, agentID, resultData) } -func (h *AgentHandler) handleTamperProtectMessage(agentID string, data json.RawMessage) error { +func (h *AgentHandler) handleTamperProtectMessage(ctx context.Context, agentID string, data json.RawMessage) error { var protectResp protocol.TamperProtectResponse if err := json.Unmarshal(data, &protectResp); err != nil { h.logger.Error("failed to unmarshal tamper protect response", zap.Error(err)) return err } - if protectResp.Success { - h.logger.Info("tamper protect config applied successfully", - zap.String("agentID", agentID), - zap.String("message", protectResp.Message), - zap.Int("current_paths", len(protectResp.Paths)), - zap.Int("added", len(protectResp.Added)), - zap.Int("removed", len(protectResp.Removed))) - } else { - h.logger.Error("tamper protect config apply failed", - zap.String("agentID", agentID), - zap.String("message", protectResp.Message)) - } - return nil + return h.tamperService.HandleConfigResult(ctx, agentID, protectResp) } // sendRegisterSuccess 发送注册成功响应 @@ -249,24 +237,16 @@ func (h *AgentHandler) sendRegisterError(conn *websocket.Conn, errMsg string) er // sendTamperConfig 发送防篡改配置(探针初始化时发送完整配置作为新增) func (h *AgentHandler) sendTamperConfig(conn *websocket.Conn, agentID string) error { - // 获取探针的防篡改配置 - config, err := h.tamperService.GetConfigByAgentID(agentID) + // 使用 TamperService 构建初始配置(复用逻辑,会自动判断 enabled 状态) + added, removed, err := h.tamperService.BuildInitialConfig(context.Background(), agentID) if err != nil { return err } - // 构建配置数据 - 将完整配置作为新增发送(探针刚连接,所有路径都是新增) - var paths []string - if config != nil && len(config.Paths) > 0 { - paths = config.Paths - } else { - paths = []string{} // 空列表 - } - - // 使用增量配置格式,将所有路径作为新增 + // 使用增量配置格式 configData := protocol.TamperProtectConfig{ - Added: paths, - Removed: []string{}, // 初始化时没有需要移除的 + Added: added, + Removed: removed, } msgData, err := json.Marshal(protocol.OutboundMessage{ diff --git a/internal/handler/ssh_login_handler.go b/internal/handler/ssh_login_handler.go index 01f212b..620b7e9 100644 --- a/internal/handler/ssh_login_handler.go +++ b/internal/handler/ssh_login_handler.go @@ -34,7 +34,7 @@ func (h *SSHLoginHandler) GetConfig(c echo.Context) error { }) } - config, err := h.service.GetConfig(agentID) + config, err := h.service.GetConfig(c.Request().Context(), agentID) if err != nil { h.logger.Error("获取SSH登录监控配置失败", zap.Error(err)) return c.JSON(http.StatusInternalServerError, map[string]string{ @@ -42,13 +42,6 @@ func (h *SSHLoginHandler) GetConfig(c echo.Context) error { }) } - if config == nil { - // 返回默认配置 - return c.JSON(http.StatusOK, map[string]interface{}{ - "enabled": false, - }) - } - return c.JSON(http.StatusOK, config) } @@ -72,7 +65,7 @@ func (h *SSHLoginHandler) UpdateConfig(c echo.Context) error { }) } - config, configSent, err := h.service.UpdateConfig(c.Request().Context(), agentID, req.Enabled) + err := h.service.UpdateConfig(c.Request().Context(), agentID, req.Enabled) if err != nil { h.logger.Error("更新SSH登录监控配置失败", zap.Error(err)) return c.JSON(http.StatusInternalServerError, map[string]string{ @@ -80,19 +73,7 @@ func (h *SSHLoginHandler) UpdateConfig(c echo.Context) error { }) } - // 根据下发状态返回不同的消息 - message := "配置已保存" - if configSent { - message = "配置已保存并成功下发到探针" - } else { - message = "配置已保存,将在探针下次连接时生效" - } - - return c.JSON(http.StatusOK, map[string]interface{}{ - "message": message, - "config": config, - "configSent": configSent, // 告知前端配置是否成功下发 - }) + return c.JSON(http.StatusOK, orz.Map{}) } // ListEvents 查询SSH登录事件 diff --git a/internal/handler/tamper_handler.go b/internal/handler/tamper_handler.go index 22a1c88..b11d1cd 100644 --- a/internal/handler/tamper_handler.go +++ b/internal/handler/tamper_handler.go @@ -38,7 +38,7 @@ func (h *TamperHandler) UpdateTamperConfig(c echo.Context) error { }) } - config, err := h.tamperService.UpdateConfig(agentID, req.Enabled, req.Paths) + err := h.tamperService.UpdateConfig(c.Request().Context(), agentID, req.Enabled, req.Paths) if err != nil { h.logger.Error("更新防篡改配置失败", zap.Error(err), zap.String("agentId", agentID)) return c.JSON(http.StatusInternalServerError, map[string]interface{}{ @@ -46,7 +46,7 @@ func (h *TamperHandler) UpdateTamperConfig(c echo.Context) error { }) } - return c.JSON(http.StatusOK, config) + return c.JSON(http.StatusOK, orz.Map{}) } // GetTamperConfig 获取探针的防篡改配置 @@ -54,7 +54,7 @@ func (h *TamperHandler) UpdateTamperConfig(c echo.Context) error { func (h *TamperHandler) GetTamperConfig(c echo.Context) error { agentID := c.Param("id") - config, err := h.tamperService.GetConfigByAgentID(agentID) + config, err := h.tamperService.GetConfigByAgentID(c.Request().Context(), agentID) if err != nil { h.logger.Error("获取防篡改配置失败", zap.Error(err), zap.String("agentId", agentID)) return c.JSON(http.StatusInternalServerError, map[string]interface{}{ diff --git a/internal/repo/agent_repo.go b/internal/repo/agent_repo.go index 1e3aa74..6e632ff 100644 --- a/internal/repo/agent_repo.go +++ b/internal/repo/agent_repo.go @@ -238,41 +238,3 @@ func (r *AgentRepo) FindAgentsWithTrafficReset(ctx context.Context) ([]models.Ag } return agents, nil } - -// === 配置管理 === - -// GetTamperProtectConfig 获取防篡改保护配置 -func (r *AgentRepo) GetTamperProtectConfig(ctx context.Context, agentID string) (*models.TamperProtectConfigData, error) { - var agent models.Agent - err := r.db.WithContext(ctx).Select("tamper_protect_config").Where("id = ?", agentID).First(&agent).Error - if err != nil { - return nil, err - } - data := agent.TamperProtectConfig.Data() - return &data, nil -} - -// UpdateTamperProtectConfig 更新防篡改保护配置 -func (r *AgentRepo) UpdateTamperProtectConfig(ctx context.Context, agentID string, config *models.TamperProtectConfigData) error { - return r.db.WithContext(ctx).Model(&models.Agent{}). - Where("id = ?", agentID). - Update("tamper_protect_config", config).Error -} - -// GetSSHLoginConfig 获取SSH登录监控配置 -func (r *AgentRepo) GetSSHLoginConfig(ctx context.Context, agentID string) (*models.SSHLoginConfigData, error) { - var agent models.Agent - err := r.db.WithContext(ctx).Select("ssh_login_config").Where("id = ?", agentID).First(&agent).Error - if err != nil { - return nil, err - } - data := agent.SSHLoginConfig.Data() - return &data, nil -} - -// UpdateSSHLoginConfig 更新SSH登录监控配置 -func (r *AgentRepo) UpdateSSHLoginConfig(ctx context.Context, agentID string, config *models.SSHLoginConfigData) error { - return r.db.WithContext(ctx).Model(&models.Agent{}). - Where("id = ?", agentID). - Update("ssh_login_config", config).Error -} diff --git a/internal/service/ssh_login_service.go b/internal/service/ssh_login_service.go index c7b5969..04a4a2f 100644 --- a/internal/service/ssh_login_service.go +++ b/internal/service/ssh_login_service.go @@ -3,7 +3,6 @@ package service import ( "context" "encoding/json" - "errors" "fmt" "time" @@ -11,7 +10,9 @@ import ( "github.com/dushixiang/pika/internal/protocol" "github.com/dushixiang/pika/internal/repo" "github.com/dushixiang/pika/internal/websocket" + "go.uber.org/zap" + "gorm.io/datatypes" "gorm.io/gorm" ) @@ -38,46 +39,33 @@ func NewSSHLoginService(logger *zap.Logger, db *gorm.DB, wsManager *websocket.Ma // === 配置管理 === // GetConfig 获取探针的配置 -func (s *SSHLoginService) GetConfig(agentID string) (*models.SSHLoginConfigData, error) { - config, err := s.agentRepo.GetSSHLoginConfig(context.Background(), agentID) +func (s *SSHLoginService) GetConfig(ctx context.Context, agentID string) (*models.SSHLoginConfigData, error) { + agent, err := s.agentRepo.FindById(ctx, agentID) if err != nil { return nil, err } - return config, nil + config := agent.SSHLoginConfig.Data() + return &config, nil } // UpdateConfig 更新配置并下发到 Agent -// 返回: config - 配置对象, configSent - 是否成功下发到 Agent, error - 错误信息 -func (s *SSHLoginService) UpdateConfig(ctx context.Context, agentID string, enabled bool) (*models.SSHLoginConfigData, bool, error) { - // 检查探针是否存在 - agent, err := s.agentRepo.FindById(ctx, agentID) - if err != nil { - return nil, false, fmt.Errorf("探针不存在") - } - +// 返回: config - 配置对象, error - 错误信息 +func (s *SSHLoginService) UpdateConfig(ctx context.Context, agentID string, enabled bool) error { // 保存配置到数据库 - config := &models.SSHLoginConfigData{ + config := models.SSHLoginConfigData{ Enabled: enabled, } - if err := s.agentRepo.UpdateSSHLoginConfig(ctx, agentID, config); err != nil { - return nil, false, err + var agentForUpdate = models.Agent{ + ID: agentID, + SSHLoginConfig: datatypes.NewJSONType(config), + } + if err := s.agentRepo.UpdateById(ctx, &agentForUpdate); err != nil { + return err } // 下发配置到 Agent - configSent := false - if agent.Status == 1 { // 仅在探针在线时尝试下发 - if err := s.sendConfigToAgent(agentID, enabled); err != nil { - s.logger.Warn("下发SSH登录监控配置到Agent失败", zap.Error(err), zap.String("agentId", agentID)) - } else { - s.logger.Info("成功下发SSH登录监控配置", zap.String("agentId", agentID), zap.Bool("enabled", enabled)) - configSent = true - } - } else { - s.logger.Info("探针离线,配置将在下次连接时生效", zap.String("agentId", agentID)) - } - - return config, configSent, nil + return s.sendConfigToAgent(agentID, enabled) } // sendConfigToAgent 下发配置到 Agent @@ -100,16 +88,12 @@ func (s *SSHLoginService) sendConfigToAgent(agentID string, enabled bool) error } // HandleConfigResult 处理 Agent 上报的配置应用结果 -func (s *SSHLoginService) HandleConfigResult(agentID string, result protocol.SSHLoginConfigResult) error { +func (s *SSHLoginService) HandleConfigResult(ctx context.Context, agentID string, result protocol.SSHLoginConfigResult) error { // 获取现有配置 - config, err := s.agentRepo.GetSSHLoginConfig(context.Background(), agentID) + config, err := s.GetConfig(ctx, agentID) if err != nil { return fmt.Errorf("获取配置失败: %w", err) } - if config == nil { - s.logger.Warn("收到配置应用结果,但配置不存在", zap.String("agentId", agentID)) - return nil - } // 更新配置应用状态 status := "success" @@ -120,24 +104,11 @@ func (s *SSHLoginService) HandleConfigResult(agentID string, result protocol.SSH config.ApplyStatus = status config.ApplyMessage = result.Message - if err := s.agentRepo.UpdateSSHLoginConfig(context.Background(), agentID, config); err != nil { - s.logger.Error("更新配置应用状态失败", zap.Error(err), zap.String("agentId", agentID)) - return err + var agentForUpdate = models.Agent{ + ID: agentID, + SSHLoginConfig: datatypes.NewJSONType(*config), } - - // 记录日志 - if result.Success { - s.logger.Info("Agent成功应用SSH登录监控配置", - zap.String("agentId", agentID), - zap.Bool("enabled", result.Enabled), - zap.String("message", result.Message)) - } else { - s.logger.Warn("Agent应用SSH登录监控配置失败", - zap.String("agentId", agentID), - zap.String("message", result.Message)) - } - - return nil + return s.agentRepo.UpdateById(ctx, &agentForUpdate) } // === 事件处理 === @@ -145,8 +116,8 @@ func (s *SSHLoginService) HandleConfigResult(agentID string, result protocol.SSH // HandleEvent 处理 Agent 上报的事件 func (s *SSHLoginService) HandleEvent(ctx context.Context, agentID string, eventData protocol.SSHLoginEvent) error { // 检查是否启用监控 - config, err := s.agentRepo.GetSSHLoginConfig(ctx, agentID) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + config, err := s.GetConfig(ctx, agentID) + if err != nil { return err } diff --git a/internal/service/tamper_service.go b/internal/service/tamper_service.go index 7d443bb..eae485b 100644 --- a/internal/service/tamper_service.go +++ b/internal/service/tamper_service.go @@ -3,15 +3,17 @@ package service import ( "context" "encoding/json" - "errors" + "fmt" "time" "github.com/dushixiang/pika/internal/models" "github.com/dushixiang/pika/internal/protocol" "github.com/dushixiang/pika/internal/repo" "github.com/dushixiang/pika/internal/websocket" + "github.com/google/uuid" "go.uber.org/zap" + "gorm.io/datatypes" "gorm.io/gorm" ) @@ -32,33 +34,56 @@ func NewTamperService(logger *zap.Logger, db *gorm.DB, wsManager *websocket.Mana } // GetConfigByAgentID 获取探针的防篡改配置 -func (s *TamperService) GetConfigByAgentID(agentID string) (*models.TamperProtectConfigData, error) { - config, err := s.agentRepo.GetTamperProtectConfig(context.Background(), agentID) +func (s *TamperService) GetConfigByAgentID(ctx context.Context, agentID string) (*models.TamperProtectConfigData, error) { + agent, err := s.agentRepo.FindById(ctx, agentID) if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil // 没有配置不算错误 - } return nil, err } - return config, nil + config := agent.TamperProtectConfig.Data() + return &config, nil +} + +func (s *TamperService) UpdateConfigByAgentID(ctx context.Context, agentID string, config *models.TamperProtectConfigData) error { + var agentForUpdate = models.Agent{ + ID: agentID, + TamperProtectConfig: datatypes.NewJSONType(*config), + } + return s.agentRepo.UpdateById(ctx, &agentForUpdate) } // UpdateConfig 更新探针的防篡改配置 -func (s *TamperService) UpdateConfig(agentID string, enabled bool, paths []string) (*models.TamperProtectConfigData, error) { +func (s *TamperService) UpdateConfig(ctx context.Context, agentID string, enabled bool, paths []string) error { // 查找现有配置 - config, err := s.agentRepo.GetTamperProtectConfig(context.Background(), agentID) - if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { - return nil, err + config, err := s.GetConfigByAgentID(ctx, agentID) + if err != nil { + return err } - // 获取旧的路径列表用于比对 + // 获取旧的路径列表和启用状态用于比对 var oldPaths []string + var wasEnabled bool if config != nil { oldPaths = config.Paths + wasEnabled = config.Enabled } - // 计算新增和移除的路径 - added, removed := s.calculatePathDiff(oldPaths, paths) + var added, removed []string + + // 处理不同的状态转换场景 + if !enabled { + // 场景1: 禁用防篡改功能,需要移除所有旧的路径配置 + removed = oldPaths + added = []string{} + // 注意: 不清空 paths,保留配置以便下次启用时使用 + } else if !wasEnabled { + // 场景2: 从禁用切换到启用,所有路径都作为新增 + // 因为探针端已经移除了所有监控,需要重新添加 + added = paths + removed = []string{} + } else { + // 场景3: 启用状态下的正常增量更新 + added, removed = s.calculatePathDiff(oldPaths, paths) + } // 创建或更新配置 newConfig := &models.TamperProtectConfigData{ @@ -67,12 +92,12 @@ func (s *TamperService) UpdateConfig(agentID string, enabled bool, paths []strin } // 保存配置到数据库 - if err := s.agentRepo.UpdateTamperProtectConfig(context.Background(), agentID, newConfig); err != nil { - return nil, err + if err := s.UpdateConfigByAgentID(ctx, agentID, newConfig); err != nil { + return err } // 下发增量配置到探针 - if err := s.sendConfigToAgent(agentID, added, removed); err != nil { + if err := s.sendIncrementalConfigToAgent(agentID, added, removed); err != nil { s.logger.Warn("下发防篡改配置到探针失败", zap.String("agentId", agentID), zap.Strings("added", added), @@ -87,7 +112,7 @@ func (s *TamperService) UpdateConfig(agentID string, enabled bool, paths []strin zap.Int("totalPaths", len(paths))) } - return newConfig, nil + return nil } // calculatePathDiff 计算路径的新增和移除 @@ -120,8 +145,27 @@ func (s *TamperService) calculatePathDiff(oldPaths, newPaths []string) (added, r return added, removed } -// sendConfigToAgent 通过WebSocket下发配置到探针(增量更新) -func (s *TamperService) sendConfigToAgent(agentID string, added, removed []string) error { +// BuildInitialConfig 构建探针初始化时的配置(用于探针连接时) +// 根据 enabled 状态决定发送的内容: +// - enabled=true: 发送所有配置的路径作为新增 +// - enabled=false: 发送空配置 +func (s *TamperService) BuildInitialConfig(ctx context.Context, agentID string) (added, removed []string, err error) { + config, err := s.GetConfigByAgentID(ctx, agentID) + if err != nil { + return nil, nil, err + } + + // 如果启用了防篡改,将所有路径作为新增发送 + if config != nil && config.Enabled && len(config.Paths) > 0 { + return config.Paths, []string{}, nil + } + + // 未启用或没有配置,返回空 + return []string{}, []string{}, nil +} + +// sendIncrementalConfigToAgent 通过WebSocket下发配置到探针(增量更新) +func (s *TamperService) sendIncrementalConfigToAgent(agentID string, added, removed []string) error { // 如果没有任何变更,不需要下发 if len(added) == 0 && len(removed) == 0 { return nil @@ -165,3 +209,27 @@ func (s *TamperService) CleanupOldRecords(ctx context.Context) error { threshold := time.Now().AddDate(0, 0, -30).UnixMilli() return s.TamperEventRepo.DeleteOldEvents(ctx, threshold) } + +func (s *TamperService) HandleConfigResult(ctx context.Context, agentID string, resp protocol.TamperProtectResponse) error { + // 获取现有配置 + config, err := s.GetConfigByAgentID(ctx, agentID) + if err != nil { + return fmt.Errorf("获取配置失败: %w", err) + } + + // 更新配置应用状态 + status := "success" + if !resp.Success { + status = "failed" + } + + config.ApplyStatus = status + config.ApplyMessage = resp.Message + + var agentForUpdate = models.Agent{ + ID: agentID, + TamperProtectConfig: datatypes.NewJSONType(*config), + } + // 更新探针配置 + return s.agentRepo.UpdateById(ctx, &agentForUpdate) +} diff --git a/pkg/agent/tamper/tamper_protector.go b/pkg/agent/tamper/tamper_protector.go index 6a9b40b..a701b14 100644 --- a/pkg/agent/tamper/tamper_protector.go +++ b/pkg/agent/tamper/tamper_protector.go @@ -5,6 +5,7 @@ import ( "fmt" "log/slog" "os" + "path/filepath" "runtime" "sync" "time" @@ -344,17 +345,34 @@ func (p *Protector) initWatcher(ctx context.Context) error { // addPath 添加目录保护(内部方法,不加锁) func (p *Protector) addPath(path string) error { - // 设置不可变属性 - if err := p.setImmutable(path, true); err != nil { - return fmt.Errorf("设置目录不可变属性失败: %w", err) + // 检查路径是否存在 + info, err := os.Stat(path) + if err != nil { + return fmt.Errorf("无法访问路径: %w", err) + } + + // 如果是目录,递归设置所有文件和子目录的不可变属性 + if info.IsDir() { + if err := p.setImmutableRecursive(path, true); err != nil { + return fmt.Errorf("递归设置目录不可变属性失败: %w", err) + } + } else { + // 单个文件,直接设置不可变属性 + if err := p.setImmutable(path, true); err != nil { + return fmt.Errorf("设置文件不可变属性失败: %w", err) + } } // 添加到监控 if p.watcher != nil { if err := p.watcher.Add(path); err != nil { // 如果添加监控失败,尝试回滚不可变属性 - _ = p.setImmutable(path, false) - return fmt.Errorf("添加目录到监控失败: %w", err) + if info.IsDir() { + _ = p.setImmutableRecursive(path, false) + } else { + _ = p.setImmutable(path, false) + } + return fmt.Errorf("添加路径到监控失败: %w", err) } } @@ -366,14 +384,32 @@ func (p *Protector) removePath(path string) error { // 从监控中移除 if p.watcher != nil { if err := p.watcher.Remove(path); err != nil { - slog.Warn("从监控中移除目录失败", "error", err) + slog.Warn("从监控中移除路径失败", "error", err) // 继续执行,不返回错误 } } - // 移除不可变属性 - if err := p.setImmutable(path, false); err != nil { - return fmt.Errorf("移除目录不可变属性失败: %w", err) + // 检查路径是否存在 + info, err := os.Stat(path) + if err != nil { + // 如果路径不存在,不报错,直接返回 + if os.IsNotExist(err) { + slog.Info("路径已不存在,跳过移除属性", "path", path) + return nil + } + return fmt.Errorf("无法访问路径: %w", err) + } + + // 如果是目录,递归移除所有文件和子目录的不可变属性 + if info.IsDir() { + if err := p.setImmutableRecursive(path, false); err != nil { + return fmt.Errorf("递归移除目录不可变属性失败: %w", err) + } + } else { + // 单个文件,直接移除不可变属性 + if err := p.setImmutable(path, false); err != nil { + return fmt.Errorf("移除文件不可变属性失败: %w", err) + } } return nil @@ -519,6 +555,48 @@ func (p *Protector) checkImmutable(path string) (bool, error) { return IsAttr(f, FS_IMMUTABLE_FL) } +// setImmutableRecursive 递归设置或移除目录及其所有子文件/子目录的不可变属性 +func (p *Protector) setImmutableRecursive(rootPath string, immutable bool) error { + var lastErr error + var errCount int + + // 使用 filepath.Walk 遍历目录树 + err := filepath.Walk(rootPath, func(path string, info os.FileInfo, err error) error { + if err != nil { + slog.Warn("访问路径失败", "path", path, "error", err) + lastErr = err + errCount++ + return nil // 继续处理其他文件 + } + + // 设置不可变属性 + if setErr := p.setImmutable(path, immutable); setErr != nil { + slog.Warn("设置不可变属性失败", "path", path, "immutable", immutable, "error", setErr) + lastErr = setErr + errCount++ + return nil // 继续处理其他文件 + } + + if immutable { + slog.Debug("已设置不可变属性", "path", path) + } else { + slog.Debug("已移除不可变属性", "path", path) + } + + return nil + }) + + if err != nil { + return fmt.Errorf("遍历目录失败: %w", err) + } + + if errCount > 0 { + return fmt.Errorf("部分文件处理失败,共 %d 个错误,最后一个错误: %w", errCount, lastErr) + } + + return nil +} + // setImmutable 设置或移除文件/目录的不可变属性 func (p *Protector) setImmutable(path string, immutable bool) error { // 打开文件/目录 diff --git a/web/src/portal/components/server/SystemInfoSection.tsx b/web/src/portal/components/server/SystemInfoSection.tsx index cc941bf..cdb6c9b 100644 --- a/web/src/portal/components/server/SystemInfoSection.tsx +++ b/web/src/portal/components/server/SystemInfoSection.tsx @@ -6,8 +6,6 @@ import {formatBytes, formatDateTime, formatPercentValue, formatUptime} from '@po import type {Agent, LatestMetrics} from '@/types'; import CyberCard from "@portal/components/CyberCard.tsx"; -type AccentVariant = 'blue' | 'emerald' | 'purple' | 'amber'; - interface SystemInfoSectionProps { agent: Agent; latestMetrics: LatestMetrics | null; @@ -125,19 +123,18 @@ export const SystemInfoSection = ({agent, latestMetrics}: SystemInfoSectionProps ]; // 如果配置了流量限额,添加流量统计信息到网络卡片 - if (agent && agent.trafficLimit && agent.trafficLimit > 0) { - const trafficUsedPercent = ((agent.trafficUsed || 0) / agent.trafficLimit) * 100; - const trafficRemaining = agent.trafficLimit - (agent.trafficUsed || 0); + if (agent?.trafficStats?.enabled && agent.trafficStats.limit > 0) { + const trafficUsedPercent = (agent.trafficStats.used / agent.trafficStats.limit) * 100; networkMetrics.push({ label: '流量限额', - value: `${formatBytes(agent.trafficUsed || 0, 1)} / ${formatBytes(agent.trafficLimit, 1)} (${formatPercentValue(trafficUsedPercent)}%)`, + value: `${formatBytes(agent.trafficStats.used, 1)} / ${formatBytes(agent.trafficStats.limit, 1)} (${formatPercentValue(trafficUsedPercent)}%)`, }); - if (agent.trafficResetDay && agent.trafficResetDay > 0) { + if (agent.trafficStats.resetDay > 0) { networkMetrics.push({ label: '重置日期', - value: `每月${agent.trafficResetDay}号`, + value: `每月${agent.trafficStats.resetDay}号`, }); } }