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 { if b.FilePath != "" { dir := filepath.Dir(b.FilePath) _ = os.RemoveAll(dir) } else { _ = os.RemoveAll(filepath.Join(dataDir, "builds", b.ID)) } if err := database.DeleteBuild(b.ID); err != nil { log.Printf("[Retention] delete build %s: %v", b.ID, err) } else { log.Printf("[Retention] removed build %s (%s)", b.ID, b.WorkerName) } } } }