Files
Redflag/aggregator-agent/internal/orchestrator/system_scanner.go
Fimeg 3690472396 feat: granular subsystem commands with parallel scanner execution
Split monolithic scan_updates into individual subsystems (updates/storage/system/docker).
Scanners now run in parallel via goroutines - cuts scan time roughly in half, maybe more.

Agent changes:
- Orchestrator pattern for scanner management
- New scanners: storage (disk metrics), system (cpu/mem/processes)
- New commands: scan_storage, scan_system, scan_docker
- Wrapped existing scanners (APT/DNF/Docker/Windows/Winget) with common interface
- Version bump to 0.1.20

Server changes:
- Migration 015: agent_subsystems table with trigger for auto-init
- Subsystem CRUD: enable/disable, interval (5min-24hr), auto-run toggle
- API routes: /api/v1/agents/:id/subsystems/* (9 endpoints)
- Stats tracking per subsystem

Web UI changes:
- ChatTimeline shows subsystem-specific labels and icons
- AgentScanners got interactive toggles, interval dropdowns, manual trigger buttons
- TypeScript types added for subsystems

Backward compatible with legacy scan_updates - for now. Bugs probably exist somewhere.
2025-11-01 21:34:26 -04:00

138 lines
4.2 KiB
Go

package orchestrator
import (
"fmt"
"github.com/Fimeg/RedFlag/aggregator-agent/internal/client"
"github.com/Fimeg/RedFlag/aggregator-agent/internal/system"
)
// SystemScanner scans system metrics (CPU, memory, processes, uptime)
type SystemScanner struct {
agentVersion string
}
// NewSystemScanner creates a new system scanner
func NewSystemScanner(agentVersion string) *SystemScanner {
return &SystemScanner{
agentVersion: agentVersion,
}
}
// IsAvailable always returns true since system scanning is always available
func (s *SystemScanner) IsAvailable() bool {
return true
}
// Scan collects system information and returns it as "updates" for reporting
func (s *SystemScanner) Scan() ([]client.UpdateReportItem, error) {
sysInfo, err := system.GetSystemInfo(s.agentVersion)
if err != nil {
return nil, fmt.Errorf("failed to get system info: %w", err)
}
// Convert system info to UpdateReportItem format for reporting
var items []client.UpdateReportItem
// CPU info item
cpuItem := client.UpdateReportItem{
PackageName: "system-cpu",
CurrentVersion: fmt.Sprintf("%d cores, %d threads", sysInfo.CPUInfo.Cores, sysInfo.CPUInfo.Threads),
AvailableVersion: sysInfo.CPUInfo.ModelName,
PackageType: "system",
Severity: "low",
PackageDescription: fmt.Sprintf("CPU: %s", sysInfo.CPUInfo.ModelName),
Metadata: map[string]interface{}{
"cpu_model": sysInfo.CPUInfo.ModelName,
"cpu_cores": sysInfo.CPUInfo.Cores,
"cpu_threads": sysInfo.CPUInfo.Threads,
},
}
items = append(items, cpuItem)
// Memory info item
memItem := client.UpdateReportItem{
PackageName: "system-memory",
CurrentVersion: fmt.Sprintf("%.1f%% used", sysInfo.MemoryInfo.UsedPercent),
AvailableVersion: fmt.Sprintf("%d GB total", sysInfo.MemoryInfo.Total/(1024*1024*1024)),
PackageType: "system",
Severity: determineMemorySeverity(sysInfo.MemoryInfo.UsedPercent),
PackageDescription: fmt.Sprintf("Memory: %.1f GB / %.1f GB used",
float64(sysInfo.MemoryInfo.Used)/(1024*1024*1024),
float64(sysInfo.MemoryInfo.Total)/(1024*1024*1024)),
Metadata: map[string]interface{}{
"memory_total": sysInfo.MemoryInfo.Total,
"memory_used": sysInfo.MemoryInfo.Used,
"memory_available": sysInfo.MemoryInfo.Available,
"memory_used_percent": sysInfo.MemoryInfo.UsedPercent,
},
}
items = append(items, memItem)
// Process count item
processItem := client.UpdateReportItem{
PackageName: "system-processes",
CurrentVersion: fmt.Sprintf("%d processes", sysInfo.RunningProcesses),
AvailableVersion: "n/a",
PackageType: "system",
Severity: "low",
PackageDescription: fmt.Sprintf("Running Processes: %d", sysInfo.RunningProcesses),
Metadata: map[string]interface{}{
"process_count": sysInfo.RunningProcesses,
},
}
items = append(items, processItem)
// Uptime item
uptimeItem := client.UpdateReportItem{
PackageName: "system-uptime",
CurrentVersion: sysInfo.Uptime,
AvailableVersion: "n/a",
PackageType: "system",
Severity: "low",
PackageDescription: fmt.Sprintf("System Uptime: %s", sysInfo.Uptime),
Metadata: map[string]interface{}{
"uptime": sysInfo.Uptime,
},
}
items = append(items, uptimeItem)
// Reboot required item (if applicable)
if sysInfo.RebootRequired {
rebootItem := client.UpdateReportItem{
PackageName: "system-reboot",
CurrentVersion: "required",
AvailableVersion: "n/a",
PackageType: "system",
Severity: "important",
PackageDescription: fmt.Sprintf("Reboot Required: %s", sysInfo.RebootReason),
Metadata: map[string]interface{}{
"reboot_required": true,
"reboot_reason": sysInfo.RebootReason,
},
}
items = append(items, rebootItem)
}
return items, nil
}
// Name returns the scanner name
func (s *SystemScanner) Name() string {
return "System Metrics Reporter"
}
// determineMemorySeverity returns severity based on memory usage percentage
func determineMemorySeverity(usedPercent float64) string {
switch {
case usedPercent >= 95:
return "critical"
case usedPercent >= 90:
return "important"
case usedPercent >= 80:
return "moderate"
default:
return "low"
}
}