Persist build extra_files for Build Manager history, print dashboard login on every start, add libp2p for Mesh P2P forge, defer WebSocket until login, and split devrun.bat from LAUNCH.bat with USB deck auto-detection.
100 lines
2.7 KiB
Go
100 lines
2.7 KiB
Go
package maintenance
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
"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
|
|
|
|
var (
|
|
retentionMu sync.Mutex
|
|
retentionCancel context.CancelFunc
|
|
)
|
|
|
|
// 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
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
retentionMu.Lock()
|
|
retentionCancel = cancel
|
|
retentionMu.Unlock()
|
|
|
|
go func() {
|
|
runRetentionFn(database, dataDir, statsHours, buildDays)
|
|
ticker := time.NewTicker(retentionTickInterval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
runRetentionFn(database, dataDir, statsHours, buildDays)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// StopRetentionJobs stops the background retention loop started by StartRetentionJobs.
|
|
func StopRetentionJobs() {
|
|
retentionMu.Lock()
|
|
cancel := retentionCancel
|
|
retentionCancel = nil
|
|
retentionMu.Unlock()
|
|
if cancel != nil {
|
|
cancel()
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|