package main import ( "bufio" "fmt" "log" "os" "os/exec" "strconv" "strings" "syscall" "time" "nannyagentv2/internal/auth" "nannyagentv2/internal/config" "nannyagentv2/internal/metrics" "nannyagentv2/internal/types" ) const Version = "v2.0.0" // checkRootPrivileges ensures the program is running as root func checkRootPrivileges() { if os.Geteuid() != 0 { fmt.Fprintf(os.Stderr, "❌ ERROR: This program must be run as root for eBPF functionality.\n") fmt.Fprintf(os.Stderr, "Please run with: sudo %s\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Reason: eBPF programs require root privileges to:\n") fmt.Fprintf(os.Stderr, " - Load programs into the kernel\n") fmt.Fprintf(os.Stderr, " - Attach to kernel functions and tracepoints\n") fmt.Fprintf(os.Stderr, " - Access kernel memory maps\n") os.Exit(1) } } // checkKernelVersionCompatibility ensures kernel version is 4.4 or higher func checkKernelVersionCompatibility() { output, err := exec.Command("uname", "-r").Output() if err != nil { fmt.Fprintf(os.Stderr, "❌ ERROR: Cannot determine kernel version: %v\n", err) os.Exit(1) } kernelVersion := strings.TrimSpace(string(output)) // Parse version (e.g., "5.15.0-56-generic" -> major=5, minor=15) parts := strings.Split(kernelVersion, ".") if len(parts) < 2 { fmt.Fprintf(os.Stderr, "❌ ERROR: Cannot parse kernel version: %s\n", kernelVersion) os.Exit(1) } major, err := strconv.Atoi(parts[0]) if err != nil { fmt.Fprintf(os.Stderr, "❌ ERROR: Cannot parse major kernel version: %s\n", parts[0]) os.Exit(1) } minor, err := strconv.Atoi(parts[1]) if err != nil { fmt.Fprintf(os.Stderr, "❌ ERROR: Cannot parse minor kernel version: %s\n", parts[1]) os.Exit(1) } // Check if kernel is 4.4 or higher if major < 4 || (major == 4 && minor < 4) { fmt.Fprintf(os.Stderr, "❌ ERROR: Kernel version %s is too old for eBPF.\n", kernelVersion) fmt.Fprintf(os.Stderr, "Required: Linux kernel 4.4 or higher\n") fmt.Fprintf(os.Stderr, "Current: %s\n", kernelVersion) fmt.Fprintf(os.Stderr, "Reason: eBPF requires kernel features introduced in 4.4+:\n") fmt.Fprintf(os.Stderr, " - BPF system call support\n") fmt.Fprintf(os.Stderr, " - eBPF program types (kprobe, tracepoint)\n") fmt.Fprintf(os.Stderr, " - BPF maps and helper functions\n") os.Exit(1) } fmt.Printf("✅ Kernel version %s is compatible with eBPF\n", kernelVersion) } // checkEBPFSupport validates eBPF subsystem availability func checkEBPFSupport() { // Check if /sys/kernel/debug/tracing exists (debugfs mounted) if _, err := os.Stat("/sys/kernel/debug/tracing"); os.IsNotExist(err) { fmt.Fprintf(os.Stderr, "⚠️ WARNING: debugfs not mounted. Some eBPF features may not work.\n") fmt.Fprintf(os.Stderr, "To fix: sudo mount -t debugfs debugfs /sys/kernel/debug\n") } // Check if we can access BPF syscall fd, _, errno := syscall.Syscall(321, 0, 0, 0) // BPF syscall number on x86_64 if errno != 0 && errno != syscall.EINVAL { fmt.Fprintf(os.Stderr, "❌ ERROR: BPF syscall not available (errno: %v)\n", errno) fmt.Fprintf(os.Stderr, "This may indicate:\n") fmt.Fprintf(os.Stderr, " - Kernel compiled without BPF support\n") fmt.Fprintf(os.Stderr, " - BPF syscall disabled in kernel config\n") os.Exit(1) } if fd > 0 { syscall.Close(int(fd)) } fmt.Printf("✅ eBPF syscall is available\n") } // runInteractiveDiagnostics starts the interactive diagnostic session func runInteractiveDiagnostics(agent *LinuxDiagnosticAgent) { fmt.Println("") fmt.Println("🔍 Linux eBPF-Enhanced Diagnostic Agent") fmt.Println("=======================================") fmt.Println("Linux Diagnostic Agent Started") fmt.Println("Enter a system issue description (or 'quit' to exit):") scanner := bufio.NewScanner(os.Stdin) for { fmt.Print("> ") if !scanner.Scan() { break } input := strings.TrimSpace(scanner.Text()) if input == "quit" || input == "exit" { break } if input == "" { continue } // Process the issue with AI capabilities via TensorZero if err := agent.DiagnoseIssue(input); err != nil { fmt.Printf("Error: %v\n", err) } } if err := scanner.Err(); err != nil { log.Fatal(err) } fmt.Println("Goodbye!") } func main() { fmt.Printf("🚀 NannyAgent v%s starting...\n", Version) // Perform system compatibility checks first fmt.Println("Performing system compatibility checks...") checkRootPrivileges() checkKernelVersionCompatibility() checkEBPFSupport() fmt.Println("✅ All system checks passed") fmt.Println("") // Load configuration cfg, err := config.LoadConfig() if err != nil { log.Fatalf("❌ Failed to load configuration: %v", err) } cfg.PrintConfig() // Initialize components authManager := auth.NewAuthManager(cfg) metricsCollector := metrics.NewCollector(Version) // Ensure authentication token, err := authManager.EnsureAuthenticated() if err != nil { log.Fatalf("❌ Authentication failed: %v", err) } fmt.Println("✅ Authentication successful!") // Initialize the diagnostic agent agent := NewLinuxDiagnosticAgent() // Start background metrics collection in a goroutine go func() { fmt.Println("❤️ Starting background metrics collection and heartbeat...") ticker := time.NewTicker(time.Duration(cfg.MetricsInterval) * time.Second) defer ticker.Stop() // Send initial heartbeat if err := sendHeartbeat(cfg, token, metricsCollector); err != nil { log.Printf("⚠️ Initial heartbeat failed: %v", err) } // Main heartbeat loop for range ticker.C { // Check if token needs refresh if authManager.IsTokenExpired(token) { fmt.Println("🔄 Token expiring soon, refreshing...") newToken, refreshErr := authManager.EnsureAuthenticated() if refreshErr != nil { log.Printf("❌ Token refresh failed: %v", refreshErr) continue } token = newToken fmt.Println("✅ Token refreshed successfully") } // Send heartbeat if err := sendHeartbeat(cfg, token, metricsCollector); err != nil { log.Printf("⚠️ Heartbeat failed: %v", err) // If unauthorized, try to refresh token if err.Error() == "unauthorized" { fmt.Println("🔄 Unauthorized, attempting token refresh...") newToken, refreshErr := authManager.EnsureAuthenticated() if refreshErr != nil { log.Printf("❌ Token refresh failed: %v", refreshErr) continue } token = newToken // Retry heartbeat with new token (silently) if retryErr := sendHeartbeat(cfg, token, metricsCollector); retryErr != nil { log.Printf("⚠️ Retry heartbeat failed: %v", retryErr) } } } // No logging for successful heartbeats - they should be silent } }() // Start the interactive diagnostic session (blocking) runInteractiveDiagnostics(agent) } // sendHeartbeat collects metrics and sends heartbeat to the server func sendHeartbeat(cfg *config.Config, token *types.AuthToken, collector *metrics.Collector) error { // Collect system metrics systemMetrics, err := collector.GatherSystemMetrics() if err != nil { return fmt.Errorf("failed to gather system metrics: %w", err) } // Send metrics using the collector with correct agent_id from token return collector.SendMetrics(cfg.AgentAuthURL, token.AccessToken, token.AgentID, systemMetrics) }