Files
AetherForge/server/internal/maintenance/retention.go
AetherForge ea6f54ad03 Expand test coverage across server, agent, and web; fix bugs found during audit.
Adds hundreds of unit/integration/e2e tests, fixes WS bcrypt auth, config merge, fleet analytics, agent schedule/log tail, and documents stale PROBLEMS items. Updates PROBLEMS.md, README, and test scripts; ignores local spread-kits and coverage dirs.
2026-05-31 01:13:58 -07:00

72 lines
2.2 KiB
Go

package maintenance
import (
"log"
"os"
"path/filepath"
"time"
"crypto-miner-server/internal/db"
)
// retentionTickInterval is the delay between scheduled retention passes (overridable in tests).
var retentionTickInterval = 6 * time.Hour
// runRetentionFn is the work function invoked by StartRetentionJobs (overridable in tests).
var runRetentionFn = runRetention
// StartRetentionJobs purges old stats and build artifacts on an interval.
func StartRetentionJobs(database *db.Database, dataDir string, statsHours, buildDays int) {
if statsHours <= 0 && buildDays <= 0 {
return
}
go func() {
runRetentionFn(database, dataDir, statsHours, buildDays)
ticker := time.NewTicker(retentionTickInterval)
defer ticker.Stop()
for range ticker.C {
runRetentionFn(database, dataDir, statsHours, buildDays)
}
}()
}
func runRetention(database *db.Database, dataDir string, statsHours, buildDays int) {
if statsHours > 0 {
cutoff := time.Now().Add(-time.Duration(statsHours) * time.Hour)
n, err := database.PurgeHashrateSamplesBefore(cutoff)
if err != nil {
log.Printf("[Retention] hashrate purge failed: %v", err)
} else if n > 0 {
log.Printf("[Retention] purged %d hashrate samples older than %dh", n, statsHours)
}
}
if buildDays > 0 {
cutoff := time.Now().Add(-time.Duration(buildDays) * 24 * time.Hour)
builds, err := database.ListBuildsOlderThan(cutoff)
if err != nil {
log.Printf("[Retention] build list failed: %v", err)
return
}
for _, b := range builds {
// Delete the DB record first. If this fails the build directory is
// still intact so the next retention pass can retry cleanly.
if err := database.DeleteBuild(b.ID); err != nil {
log.Printf("[Retention] delete build %s from DB: %v — skipping file removal", b.ID, err)
continue
}
// Remove files only after the DB row is gone.
var dir string
if b.FilePath != "" {
dir = filepath.Dir(b.FilePath)
} else {
dir = filepath.Join(dataDir, "builds", b.ID)
}
if err := os.RemoveAll(dir); err != nil {
log.Printf("[Retention] remove build dir %s: %v", dir, err)
} else {
log.Printf("[Retention] removed build %s (%s)", b.ID, b.WorkerName)
}
}
}
}