mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-09-30 05:02:06 +02:00
Runtime artifacts (agent workspaces, tool-output spill, C2 payloads, chat uploads, workflow checkpoints, diagnostic logs) previously accumulated without bound: most were only removed when a conversation or project was deleted, and tmp/c2 plus workflow checkpoints were never removed at all. Add a storage cleaner with named per-category tasks (Gitea-style), a settings page tab, and a background sweep that is off by default so upgrading never deletes existing data. Safety properties, since mis-deleting live task data costs far more than the disk saved: - dry-run is the default; a real cleanup requires dry_run=false together with confirm=true at the API layer, not just a frontend dialog - sessions active within active_grace_hours are always skipped, and a failed activity lookup skips conservatively (fail closed) - directories whose conversation/project no longer exists are reclaimed as orphans after orphan_grace_days - scanners never follow symlinks and every candidate path is confined to its category root; deletion renames to a .tmp-for-deletion marker first so a crash leaves recoverable residue instead of a half-deleted dir - storage:* permissions are admin-only; without the grantSystemRolePermissions skip the default branch would have given operators an irreversible file-deletion right Also fix two confirmed leaks: DeleteConversation left chat_uploads files on disk (their rows already vanished via ON DELETE CASCADE), and workflow checkpoints had no deletion path at all. Co-authored-by: Parallels <parallels@kali-linux-2025-2.localdomain>
65 lines
1.4 KiB
Go
65 lines
1.4 KiB
Go
//go:build linux
|
||
|
||
package storage
|
||
|
||
import (
|
||
"os"
|
||
"path/filepath"
|
||
"syscall"
|
||
)
|
||
|
||
func filesystemUsage(path string) (Filesystem, error) {
|
||
path = filepath.Clean(path)
|
||
if path == "" {
|
||
path = "."
|
||
}
|
||
// 路径可能尚不存在,向上找最近的存在祖先,否则 Statfs 直接 ENOENT。
|
||
probe := path
|
||
for {
|
||
if _, err := os.Stat(probe); err == nil {
|
||
break
|
||
}
|
||
parent := filepath.Dir(probe)
|
||
if parent == probe {
|
||
break
|
||
}
|
||
probe = parent
|
||
}
|
||
|
||
var st syscall.Statfs_t
|
||
if err := syscall.Statfs(probe, &st); err != nil {
|
||
return Filesystem{Path: path}, err
|
||
}
|
||
|
||
bsize := int64(st.Bsize)
|
||
if bsize <= 0 {
|
||
bsize = 512
|
||
}
|
||
total := int64(st.Blocks) * bsize
|
||
// 用 Bavail 而不是 Bfree:f_bfree 含 root 保留块(通常约 5%),
|
||
// 对非 root 进程会高估可用空间,导致「明明还有空间却写失败」。
|
||
free := int64(st.Bavail) * bsize
|
||
used := (int64(st.Blocks) - int64(st.Bfree)) * bsize
|
||
if used < 0 {
|
||
used = 0
|
||
}
|
||
|
||
fs := Filesystem{
|
||
Path: path,
|
||
TotalBytes: total,
|
||
FreeBytes: free,
|
||
UsedBytes: used,
|
||
InodesTotal: int64(st.Files),
|
||
InodesFree: int64(st.Ffree),
|
||
Available: true,
|
||
}
|
||
// 分母用 used+free 而非 total:与 gopsutil 一致,避免 root 保留块把使用率算低。
|
||
if denom := used + free; denom > 0 {
|
||
fs.UsedPercent = float64(used) / float64(denom) * 100
|
||
}
|
||
if fs.InodesFree < 0 {
|
||
fs.InodesFree = 0
|
||
}
|
||
return fs, nil
|
||
}
|