package main import ( "flag" "log" "math" "os" "path/filepath" "strings" "time" "github.com/cynfo/rmm-agent/client" "github.com/cynfo/rmm-agent/collector" "github.com/cynfo/rmm-agent/config" "github.com/cynfo/rmm-agent/ws" ) func main() { cfgPath := flag.String("config", "config.yaml", "Pfad zur Konfigurationsdatei") insecure := flag.Bool("insecure", false, "TLS-Zertifikatpruefung deaktivieren") flag.Parse() cfg, err := config.Load(*cfgPath) if err != nil { log.Fatalf("Konfiguration laden fehlgeschlagen: %v", err) } if *insecure { cfg.Insecure = true } log.Printf("RMM Agent startet: %s", 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 := filepath.Join(filepath.Dir(*cfgPath), "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() } regReq := map[string]string{ "name": cfg.AgentName, "hostname": hostname, "ip": getLocalIP(), "opnsense_version": collector.CollectOPNsenseVersion(), } 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 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() wg := collector.CollectWireGuard() dhcp := collector.CollectDHCP() routes := collector.CollectRoutes() gateways := collector.CollectGateways() certs := collector.CollectCertificates() plugins := collector.CollectPlugins() updates := collector.CollectUpdates() req := map[string]interface{}{ "agent_id": agentID, "system_data": map[string]interface{}{ "agent_id": agentID, "hostname": collector.CollectHostname(), "opnsense_version": collector.CollectOPNsenseVersion(), "freebsd_version": collector.CollectFreeBSDVersion(), "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, "wireguard": wg, "dhcp": dhcp, "routes": routes, "gateways": gateways, "certificates": certs, "plugins": plugins, "updates": updates, }, } 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 ifconfig die erste nicht-loopback IP ifaces := collector.CollectNetwork() for _, iface := range ifaces { if iface.IP != "" && iface.Status == "up" { return iface.IP } } return "" }