200 lines
5.4 KiB
Go
200 lines
5.4 KiB
Go
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"
|
|
)
|
|
|
|
var Version = "dev"
|
|
|
|
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 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 := 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(),
|
|
"agent_version": Version,
|
|
"platform": "freebsd",
|
|
}
|
|
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()
|
|
cronJobs := collector.CollectCron()
|
|
license := collector.CollectLicense()
|
|
security := collector.CollectSecurity()
|
|
pfStates := collector.CollectPFStates()
|
|
haproxy := collector.CollectHAProxy()
|
|
caddy := collector.CollectCaddy()
|
|
|
|
req := map[string]interface{}{
|
|
"agent_id": agentID,
|
|
"agent_version": Version,
|
|
"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,
|
|
"cron_jobs": cronJobs,
|
|
"license": license,
|
|
"security": security,
|
|
"pf_states": pfStates,
|
|
"haproxy": haproxy,
|
|
"caddy": caddy,
|
|
},
|
|
}
|
|
|
|
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 ""
|
|
}
|