Files
SycunandParallels 470eb5ead1 feat: add runtime storage cleanup with per-category retention (#310) (#313)
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>
2026-09-25 14:46:23 +08:00

65 lines
1.4 KiB
Go
Raw Permalink 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 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
}