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.
This commit is contained in:
AetherForge
2026-06-04 21:53:31 -07:00
parent 8466c7aa9b
commit 1551bd5dad
138 changed files with 7523 additions and 489 deletions

15
agent/stats/cpu_common.go Normal file
View File

@@ -0,0 +1,15 @@
package stats
func cpuBusyPercentFromDeltas(idleDelta, totalDelta float64) float64 {
if totalDelta <= 0 {
return 0
}
busyPct := (1.0 - idleDelta/totalDelta) * 100
if busyPct < 0 {
return 0
}
if busyPct > 100 {
return 100
}
return busyPct
}

62
agent/stats/cpu_darwin.go Normal file
View File

@@ -0,0 +1,62 @@
//go:build darwin
package stats
import (
"os/exec"
"strconv"
"strings"
)
func (r *Reporter) SystemCPUPercent() float64 {
idle, total, ok := readDarwinCPUSample()
if !ok {
return 0
}
r.mu.Lock()
defer r.mu.Unlock()
if !r.hasSample {
r.lastIdle = idle
r.lastTotal = total
r.hasSample = true
return 0
}
idleDelta := float64(idle - r.lastIdle)
totalDelta := float64(total - r.lastTotal)
r.lastIdle = idle
r.lastTotal = total
return cpuBusyPercentFromDeltas(idleDelta, totalDelta)
}
func readDarwinCPUSample() (idle, total uint64, ok bool) {
out, err := exec.Command("sysctl", "-n", "kern.cp_time").Output()
if err != nil {
return 0, 0, false
}
return parseDarwinCPTimes(string(out))
}
func parseDarwinCPTimes(raw string) (idle, total uint64, ok bool) {
parts := strings.Fields(strings.TrimSpace(raw))
if len(parts) < 4 {
return 0, 0, false
}
var values []uint64
for _, p := range parts {
v, err := strconv.ParseUint(p, 10, 64)
if err != nil {
return 0, 0, false
}
values = append(values, v)
}
for _, v := range values {
total += v
}
// user, nice, sys, idle[, intr]
idle = values[3]
return idle, total, true
}

72
agent/stats/cpu_linux.go Normal file
View File

@@ -0,0 +1,72 @@
//go:build linux
package stats
import (
"bufio"
"os"
"strconv"
"strings"
)
func (r *Reporter) SystemCPUPercent() float64 {
idle, total, ok := readProcStatCPUSample()
if !ok {
return 0
}
r.mu.Lock()
defer r.mu.Unlock()
if !r.hasSample {
r.lastIdle = idle
r.lastTotal = total
r.hasSample = true
return 0
}
idleDelta := float64(idle - r.lastIdle)
totalDelta := float64(total - r.lastTotal)
r.lastIdle = idle
r.lastTotal = total
return cpuBusyPercentFromDeltas(idleDelta, totalDelta)
}
func readProcStatCPUSample() (idle, total uint64, ok bool) {
f, err := os.Open("/proc/stat")
if err != nil {
return 0, 0, false
}
defer f.Close()
sc := bufio.NewScanner(f)
if !sc.Scan() {
return 0, 0, false
}
return parseProcStatCPU(sc.Text())
}
func parseProcStatCPU(line string) (idle, total uint64, ok bool) {
fields := strings.Fields(line)
if len(fields) < 5 || fields[0] != "cpu" {
return 0, 0, false
}
var values []uint64
for _, f := range fields[1:] {
v, err := strconv.ParseUint(f, 10, 64)
if err != nil {
return 0, 0, false
}
values = append(values, v)
}
for _, v := range values {
total += v
}
// idle + iowait (index 3 and 4 when present)
idle = values[3]
if len(values) > 4 {
idle += values[4]
}
return idle, total, true
}

View File

@@ -0,0 +1,34 @@
//go:build linux
package stats
import "testing"
func TestParseProcStatCPU(t *testing.T) {
idle, total, ok := parseProcStatCPU("cpu 4705 0 1234 8123 45 0 12 0 0 0")
if !ok {
t.Fatal("expected ok")
}
if idle != 8123+45 {
t.Fatalf("idle=%d", idle)
}
wantTotal := uint64(4705 + 0 + 1234 + 8123 + 45 + 0 + 12 + 0 + 0 + 0)
if total != wantTotal {
t.Fatalf("total=%d want %d", total, wantTotal)
}
}
func TestParseProcStatCPUBadLine(t *testing.T) {
if _, _, ok := parseProcStatCPU("meminfo"); ok {
t.Fatal("expected false for non-cpu line")
}
}
func TestSystemCPUPercentLinuxSecondSample(t *testing.T) {
r := NewReporter()
_ = r.SystemCPUPercent() // first sample seeds baseline
pct := r.SystemCPUPercent()
if pct < 0 || pct > 100 {
t.Fatalf("cpu percent out of range: %v", pct)
}
}

7
agent/stats/cpu_stub.go Normal file
View File

@@ -0,0 +1,7 @@
//go:build !windows && !linux && !darwin
package stats
func (r *Reporter) SystemCPUPercent() float64 {
return 0
}

View File

@@ -17,20 +17,6 @@ func filetimeToUint64(ft filetime) uint64 {
return (uint64(ft.HighDateTime) << 32) | uint64(ft.LowDateTime)
}
func cpuBusyPercentFromDeltas(idleDelta, totalDelta float64) float64 {
if totalDelta <= 0 {
return 0
}
busyPct := (1.0 - idleDelta/totalDelta) * 100
if busyPct < 0 {
return 0
}
if busyPct > 100 {
return 100
}
return busyPct
}
func (r *Reporter) SystemCPUPercent() float64 {
r.mu.Lock()
defer r.mu.Unlock()

View File

@@ -10,6 +10,10 @@ import (
type Reporter struct {
mu sync.Mutex
lastIdle uint64
lastTotal uint64
hasSample bool
}
func NewReporter() *Reporter {
@@ -45,6 +49,3 @@ func (r *Reporter) TotalMemoryMB() uint64 {
return total / (1024 * 1024)
}
func (r *Reporter) SystemCPUPercent() float64 {
return 0
}