Session 4 complete - RedFlag update management platform
🚩 Private development - version retention only ✅ Complete web dashboard (React + TypeScript + TailwindCSS) ✅ Production-ready server backend (Go + Gin + PostgreSQL) ✅ Linux agent with APT + Docker scanning + local CLI tools ✅ JWT authentication and REST API ✅ Update discovery and approval workflow 🚧 Status: Alpha software - active development 📦 Purpose: Version retention during development ⚠️ Not for public use or deployment
This commit is contained in:
360
aggregator-agent/cmd/agent/main.go
Normal file
360
aggregator-agent/cmd/agent/main.go
Normal file
@@ -0,0 +1,360 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/aggregator-project/aggregator-agent/internal/cache"
|
||||
"github.com/aggregator-project/aggregator-agent/internal/client"
|
||||
"github.com/aggregator-project/aggregator-agent/internal/config"
|
||||
"github.com/aggregator-project/aggregator-agent/internal/display"
|
||||
"github.com/aggregator-project/aggregator-agent/internal/scanner"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
AgentVersion = "0.1.0"
|
||||
ConfigPath = "/etc/aggregator/config.json"
|
||||
)
|
||||
|
||||
func main() {
|
||||
registerCmd := flag.Bool("register", false, "Register agent with server")
|
||||
scanCmd := flag.Bool("scan", false, "Scan for updates and display locally")
|
||||
statusCmd := flag.Bool("status", false, "Show agent status")
|
||||
listUpdatesCmd := flag.Bool("list-updates", false, "List detailed update information")
|
||||
serverURL := flag.String("server", "http://localhost:8080", "Server URL")
|
||||
exportFormat := flag.String("export", "", "Export format: json, csv")
|
||||
flag.Parse()
|
||||
|
||||
// Load configuration
|
||||
cfg, err := config.Load(ConfigPath)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load configuration:", err)
|
||||
}
|
||||
|
||||
// Handle registration
|
||||
if *registerCmd {
|
||||
if err := registerAgent(cfg, *serverURL); err != nil {
|
||||
log.Fatal("Registration failed:", err)
|
||||
}
|
||||
fmt.Println("✓ Agent registered successfully!")
|
||||
fmt.Printf("Agent ID: %s\n", cfg.AgentID)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle scan command
|
||||
if *scanCmd {
|
||||
if err := handleScanCommand(cfg, *exportFormat); err != nil {
|
||||
log.Fatal("Scan failed:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle status command
|
||||
if *statusCmd {
|
||||
if err := handleStatusCommand(cfg); err != nil {
|
||||
log.Fatal("Status command failed:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle list-updates command
|
||||
if *listUpdatesCmd {
|
||||
if err := handleListUpdatesCommand(cfg, *exportFormat); err != nil {
|
||||
log.Fatal("List updates failed:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check if registered
|
||||
if !cfg.IsRegistered() {
|
||||
log.Fatal("Agent not registered. Run with -register flag first.")
|
||||
}
|
||||
|
||||
// Start agent service
|
||||
if err := runAgent(cfg); err != nil {
|
||||
log.Fatal("Agent failed:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func registerAgent(cfg *config.Config, serverURL string) error {
|
||||
hostname, _ := os.Hostname()
|
||||
osType, osVersion, osArch := client.DetectSystem()
|
||||
|
||||
apiClient := client.NewClient(serverURL, "")
|
||||
|
||||
req := client.RegisterRequest{
|
||||
Hostname: hostname,
|
||||
OSType: osType,
|
||||
OSVersion: osVersion,
|
||||
OSArchitecture: osArch,
|
||||
AgentVersion: AgentVersion,
|
||||
Metadata: map[string]string{
|
||||
"installation_time": time.Now().Format(time.RFC3339),
|
||||
},
|
||||
}
|
||||
|
||||
resp, err := apiClient.Register(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update configuration
|
||||
cfg.ServerURL = serverURL
|
||||
cfg.AgentID = resp.AgentID
|
||||
cfg.Token = resp.Token
|
||||
|
||||
// Get check-in interval from server config
|
||||
if interval, ok := resp.Config["check_in_interval"].(float64); ok {
|
||||
cfg.CheckInInterval = int(interval)
|
||||
} else {
|
||||
cfg.CheckInInterval = 300 // Default 5 minutes
|
||||
}
|
||||
|
||||
// Save configuration
|
||||
return cfg.Save(ConfigPath)
|
||||
}
|
||||
|
||||
func runAgent(cfg *config.Config) error {
|
||||
log.Printf("🚩 RedFlag Agent v%s starting...\n", AgentVersion)
|
||||
log.Printf("Agent ID: %s\n", cfg.AgentID)
|
||||
log.Printf("Server: %s\n", cfg.ServerURL)
|
||||
log.Printf("Check-in interval: %ds\n", cfg.CheckInInterval)
|
||||
|
||||
apiClient := client.NewClient(cfg.ServerURL, cfg.Token)
|
||||
|
||||
// Initialize scanners
|
||||
aptScanner := scanner.NewAPTScanner()
|
||||
dockerScanner, _ := scanner.NewDockerScanner()
|
||||
|
||||
// Main check-in loop
|
||||
for {
|
||||
// Add jitter to prevent thundering herd
|
||||
jitter := time.Duration(rand.Intn(30)) * time.Second
|
||||
time.Sleep(jitter)
|
||||
|
||||
log.Println("Checking in with server...")
|
||||
|
||||
// Get commands from server
|
||||
commands, err := apiClient.GetCommands(cfg.AgentID)
|
||||
if err != nil {
|
||||
log.Printf("Error getting commands: %v\n", err)
|
||||
time.Sleep(time.Duration(cfg.CheckInInterval) * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
// Process each command
|
||||
for _, cmd := range commands {
|
||||
log.Printf("Processing command: %s (%s)\n", cmd.Type, cmd.ID)
|
||||
|
||||
switch cmd.Type {
|
||||
case "scan_updates":
|
||||
if err := handleScanUpdates(apiClient, cfg, aptScanner, dockerScanner, cmd.ID); err != nil {
|
||||
log.Printf("Error scanning updates: %v\n", err)
|
||||
}
|
||||
|
||||
case "collect_specs":
|
||||
log.Println("Spec collection not yet implemented")
|
||||
|
||||
case "install_updates":
|
||||
log.Println("Update installation not yet implemented")
|
||||
|
||||
default:
|
||||
log.Printf("Unknown command type: %s\n", cmd.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for next check-in
|
||||
time.Sleep(time.Duration(cfg.CheckInInterval) * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func handleScanUpdates(apiClient *client.Client, cfg *config.Config, aptScanner *scanner.APTScanner, dockerScanner *scanner.DockerScanner, commandID string) error {
|
||||
log.Println("Scanning for updates...")
|
||||
|
||||
var allUpdates []client.UpdateReportItem
|
||||
|
||||
// Scan APT updates
|
||||
if aptScanner.IsAvailable() {
|
||||
log.Println(" - Scanning APT packages...")
|
||||
updates, err := aptScanner.Scan()
|
||||
if err != nil {
|
||||
log.Printf(" APT scan failed: %v\n", err)
|
||||
} else {
|
||||
log.Printf(" Found %d APT updates\n", len(updates))
|
||||
allUpdates = append(allUpdates, updates...)
|
||||
}
|
||||
}
|
||||
|
||||
// Scan Docker updates
|
||||
if dockerScanner != nil && dockerScanner.IsAvailable() {
|
||||
log.Println(" - Scanning Docker images...")
|
||||
updates, err := dockerScanner.Scan()
|
||||
if err != nil {
|
||||
log.Printf(" Docker scan failed: %v\n", err)
|
||||
} else {
|
||||
log.Printf(" Found %d Docker image updates\n", len(updates))
|
||||
allUpdates = append(allUpdates, updates...)
|
||||
}
|
||||
}
|
||||
|
||||
// Report to server
|
||||
if len(allUpdates) > 0 {
|
||||
report := client.UpdateReport{
|
||||
CommandID: commandID,
|
||||
Timestamp: time.Now(),
|
||||
Updates: allUpdates,
|
||||
}
|
||||
|
||||
if err := apiClient.ReportUpdates(cfg.AgentID, report); err != nil {
|
||||
return fmt.Errorf("failed to report updates: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("✓ Reported %d updates to server\n", len(allUpdates))
|
||||
} else {
|
||||
log.Println("✓ No updates found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleScanCommand performs a local scan and displays results
|
||||
func handleScanCommand(cfg *config.Config, exportFormat string) error {
|
||||
// Initialize scanners
|
||||
aptScanner := scanner.NewAPTScanner()
|
||||
dockerScanner, _ := scanner.NewDockerScanner()
|
||||
|
||||
fmt.Println("🔍 Scanning for updates...")
|
||||
var allUpdates []client.UpdateReportItem
|
||||
|
||||
// Scan APT updates
|
||||
if aptScanner.IsAvailable() {
|
||||
fmt.Println(" - Scanning APT packages...")
|
||||
updates, err := aptScanner.Scan()
|
||||
if err != nil {
|
||||
fmt.Printf(" ⚠️ APT scan failed: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" ✓ Found %d APT updates\n", len(updates))
|
||||
allUpdates = append(allUpdates, updates...)
|
||||
}
|
||||
}
|
||||
|
||||
// Scan Docker updates
|
||||
if dockerScanner != nil && dockerScanner.IsAvailable() {
|
||||
fmt.Println(" - Scanning Docker images...")
|
||||
updates, err := dockerScanner.Scan()
|
||||
if err != nil {
|
||||
fmt.Printf(" ⚠️ Docker scan failed: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" ✓ Found %d Docker image updates\n", len(updates))
|
||||
allUpdates = append(allUpdates, updates...)
|
||||
}
|
||||
}
|
||||
|
||||
// Load and update cache
|
||||
localCache, err := cache.Load()
|
||||
if err != nil {
|
||||
fmt.Printf("⚠️ Warning: Failed to load cache: %v\n", err)
|
||||
localCache = &cache.LocalCache{}
|
||||
}
|
||||
|
||||
// Update cache with scan results
|
||||
localCache.UpdateScanResults(allUpdates)
|
||||
if cfg.IsRegistered() {
|
||||
localCache.SetAgentInfo(cfg.AgentID, cfg.ServerURL)
|
||||
localCache.SetAgentStatus("online")
|
||||
}
|
||||
|
||||
// Save cache
|
||||
if err := localCache.Save(); err != nil {
|
||||
fmt.Printf("⚠️ Warning: Failed to save cache: %v\n", err)
|
||||
}
|
||||
|
||||
// Display results
|
||||
fmt.Println()
|
||||
return display.PrintScanResults(allUpdates, exportFormat)
|
||||
}
|
||||
|
||||
// handleStatusCommand displays agent status information
|
||||
func handleStatusCommand(cfg *config.Config) error {
|
||||
// Load cache
|
||||
localCache, err := cache.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load cache: %w", err)
|
||||
}
|
||||
|
||||
// Determine status
|
||||
agentStatus := "offline"
|
||||
if cfg.IsRegistered() {
|
||||
agentStatus = "online"
|
||||
}
|
||||
if localCache.AgentStatus != "" {
|
||||
agentStatus = localCache.AgentStatus
|
||||
}
|
||||
|
||||
// Use cached info if available, otherwise use config
|
||||
agentID := cfg.AgentID.String()
|
||||
if localCache.AgentID != (uuid.UUID{}) {
|
||||
agentID = localCache.AgentID.String()
|
||||
}
|
||||
|
||||
serverURL := cfg.ServerURL
|
||||
if localCache.ServerURL != "" {
|
||||
serverURL = localCache.ServerURL
|
||||
}
|
||||
|
||||
// Display status
|
||||
display.PrintAgentStatus(
|
||||
agentID,
|
||||
serverURL,
|
||||
localCache.LastCheckIn,
|
||||
localCache.LastScanTime,
|
||||
localCache.UpdateCount,
|
||||
agentStatus,
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// handleListUpdatesCommand displays detailed update information
|
||||
func handleListUpdatesCommand(cfg *config.Config, exportFormat string) error {
|
||||
// Load cache
|
||||
localCache, err := cache.Load()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load cache: %w", err)
|
||||
}
|
||||
|
||||
// Check if we have cached scan results
|
||||
if len(localCache.Updates) == 0 {
|
||||
fmt.Println("📋 No cached scan results found.")
|
||||
fmt.Println("💡 Run '--scan' first to discover available updates.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Warn if cache is old
|
||||
if localCache.IsExpired(24 * time.Hour) {
|
||||
fmt.Printf("⚠️ Scan results are %s old. Run '--scan' for latest results.\n\n",
|
||||
formatTimeSince(localCache.LastScanTime))
|
||||
}
|
||||
|
||||
// Display detailed results
|
||||
return display.PrintDetailedUpdates(localCache.Updates, exportFormat)
|
||||
}
|
||||
|
||||
// formatTimeSince formats a duration as "X time ago"
|
||||
func formatTimeSince(t time.Time) string {
|
||||
duration := time.Since(t)
|
||||
if duration < time.Minute {
|
||||
return fmt.Sprintf("%d seconds ago", int(duration.Seconds()))
|
||||
} else if duration < time.Hour {
|
||||
return fmt.Sprintf("%d minutes ago", int(duration.Minutes()))
|
||||
} else if duration < 24*time.Hour {
|
||||
return fmt.Sprintf("%d hours ago", int(duration.Hours()))
|
||||
} else {
|
||||
return fmt.Sprintf("%d days ago", int(duration.Hours()/24))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user