package main import ( "flag" "log" "math" "os" "path/filepath" "strings" "time" "github.com/cynfo/rmm-agent-linux/client" "github.com/cynfo/rmm-agent-linux/collector" "github.com/cynfo/rmm-agent-linux/config" "github.com/cynfo/rmm-agent-linux/ws" ) var Version = "dev" func main() { cfgPath := flag.String("config", "/etc/rmm/config.yaml", "Path zur Konfigurationsdatei") insecure := flag.Bool("insecure", false, "TLS-Zertifikatpruefung deaktivieren") version := flag.Bool("version", false, "Version anzeigen") flag.Parse() if *version { log.Printf("RMM Agent Linux v%s", Version) return } cfg, err := config.Load(*cfgPath) if err != nil { log.Fatalf("Konfiguration laden fehlgeschlagen: %v", err) } if *insecure { cfg.Insecure = true } log.Printf("RMM Agent Linux v%s startet: %s", Version, cfg.AgentName) log.Printf("Backend: %s, Intervall: %ds", cfg.BackendURL, cfg.IntervalSeconds) c := client.New(cfg.BackendURL, cfg.APIKey, cfg.Insecure) // Agent-ID persistent laden oder neu registrieren idFile := "/etc/rmm/agent_id" var agentID string if data, err := os.ReadFile(idFile); err == nil && len(strings.TrimSpace(string(data))) > 0 { agentID = strings.TrimSpace(string(data)) log.Printf("Agent-ID aus Datei geladen: %s", agentID) } // Registrierung mit Retry (sendet bestehende ID mit, falls vorhanden) backoff := 5 * time.Second for { hostname := collector.CollectHostname() if hostname == "" { hostname, _ = os.Hostname() } // Bestimme OS-Version und Platform osVersion := collector.CollectOSVersion() platform := "linux" regReq := map[string]string{ "name": cfg.AgentName, "hostname": hostname, "ip": getLocalIP(), "opnsense_version": osVersion, // Verwende OS-Version statt OPNsense "agent_version": Version, "platform": platform, } if agentID != "" { regReq["agent_id"] = agentID } newID, regErr := c.Register(regReq) if regErr != nil { log.Printf("Registrierung fehlgeschlagen: %v (Retry in %v)", regErr, backoff) time.Sleep(backoff) backoff = time.Duration(math.Min(float64(backoff*2), float64(5*time.Minute))) continue } agentID = newID break } // ID persistent speichern - verzeichnis erstellen falls nötig if err := os.MkdirAll(filepath.Dir(idFile), 0755); err != nil { log.Printf("WARNUNG: Verzeichnis für Agent-ID konnte nicht erstellt werden: %v", err) } if err := os.WriteFile(idFile, []byte(agentID), 0600); err != nil { log.Printf("WARNUNG: Agent-ID konnte nicht gespeichert werden: %v", err) } log.Printf("Agent registriert mit ID: %s", agentID) // WebSocket-Client starten wsClient := ws.NewClient(cfg.BackendURL, agentID, cfg.APIKey, cfg.Insecure) if err := wsClient.Start(); err != nil { log.Printf("WebSocket-Client starten fehlgeschlagen: %v", err) } defer wsClient.Stop() // Heartbeat-Loop ticker := time.NewTicker(time.Duration(cfg.IntervalSeconds) * time.Second) defer ticker.Stop() // Ersten Heartbeat sofort senden sendHeartbeat(c, agentID) backoff = 5 * time.Second for range ticker.C { if err := sendHeartbeat(c, agentID); err != nil { log.Printf("Heartbeat fehlgeschlagen: %v", err) backoff = time.Duration(math.Min(float64(backoff*2), float64(5*time.Minute))) } else { backoff = 5 * time.Second } } } func sendHeartbeat(c *client.Client, agentID string) error { log.Println("Sammle Systemdaten...") hw := collector.CollectHardware() cpu := collector.CollectCPU() mem := collector.CollectMemory() disks := collector.CollectDisks() net := collector.CollectNetwork() svcs := collector.CollectServices() updates := collector.CollectUpdates() // Proxmox-spezifische Daten (optional) proxmox := collector.CollectProxmox() systemData := map[string]interface{}{ "agent_id": agentID, "hostname": collector.CollectHostname(), "opnsense_version": collector.CollectOSVersion(), // Linux/Debian Version "freebsd_version": "", // Nicht verfügbar auf Linux "uptime_seconds": collector.CollectUptime(), "hardware": map[string]interface{}{ "manufacturer": hw.Manufacturer, "model": hw.Model, "serial": hw.Serial, "bios_version": hw.BIOSVersion, }, "cpu": map[string]interface{}{ "model": cpu.Model, "cores": cpu.Cores, "threads": cpu.Threads, "freq_mhz": cpu.FreqMHz, "usage_percent": cpu.UsagePercent, }, "memory": map[string]interface{}{ "total_bytes": mem.TotalBytes, "used_bytes": mem.UsedBytes, "free_bytes": mem.FreeBytes, }, "disks": disks, "network_interfaces": net, "services": svcs, "updates": updates, } // Proxmox-Daten hinzufügen falls verfügbar if proxmox != nil { systemData["proxmox"] = proxmox } // Backup-Daten (PVE Jobs + Task-Historie) if backups := collector.CollectBackups(); backups != nil { systemData["backups"] = backups } req := map[string]interface{}{ "agent_id": agentID, "agent_version": Version, "system_data": systemData, } if err := c.Heartbeat(req); err != nil { return err } log.Println("Heartbeat gesendet") return nil } // getLocalIP versucht die primaere IP-Adresse zu ermitteln func getLocalIP() string { // Versuche ueber die Network Interfaces die erste nicht-loopback IP ifaces := collector.CollectNetwork() for _, iface := range ifaces { if iface.IP != "" && iface.Status == "up" && iface.Name != "lo" { return iface.IP } } return "" }