cynfo3000 7f9fbd257f Linux/Proxmox Agent + Plattform-Updater + Proxmox Frontend
- agent-linux/: Kompletter Linux/Proxmox RMM Agent
  - Collectors: CPU, Memory, Disk, Network, Services, Updates, Proxmox (VMs, Container, Storage, ZFS), Backups
  - Backup Collector: Jobs nach Node gefiltert (Cluster-aware)
  - WebSocket: Tunnel-Support, writeMux fuer stabile Verbindungen
  - Systemd Service Template

- updater/: Plattform-unabhaengiger Updater (FreeBSD + Linux)
  - runtime.GOOS erkennt Plattform automatisch
  - FreeBSD: /usr/local/rmm/, service rmm_agent
  - Linux: /usr/local/bin/, /etc/rmm/, systemctl rmm-agent
  - Firmware-Download mit ?platform= Parameter

- frontend/: Proxmox-Bereich
  - ProxmoxServers.jsx: Eigene Seite fuer PVE Server mit VM/CT Counts
  - ProxmoxPanel.jsx: Detail-Panel mit Uebersicht, VMs, Container, Storage, ZFS, Tunnel, Dienste, Updates, Backups
  - Agents.jsx: Filtert Linux-Agents raus (nur OPNsense)
  - Sidebar: Proxmox Navigationspunkt
  - Installationsanleitung mit rmm-agent-linux + rmm-updater-linux

- backend: Platform-Feld fuer Agents, Firmware multi-platform
2026-03-01 22:03:18 +01:00

185 lines
4.0 KiB
Go

package collector
import (
"os"
"os/exec"
"strconv"
"strings"
)
type NetworkInterface struct {
Name string `json:"name"`
Role string `json:"role"`
IP string `json:"ip"`
MAC string `json:"mac"`
Status string `json:"status"`
RxBytes int64 `json:"rx_bytes"`
TxBytes int64 `json:"tx_bytes"`
}
func CollectNetwork() []NetworkInterface {
var interfaces []NetworkInterface
// Interface-Statistiken aus /proc/net/dev lesen
stats := readNetDevStats()
// Interface-Informationen aus /sys/class/net/* sammeln
entries, err := os.ReadDir("/sys/class/net")
if err != nil {
return interfaces
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
ifName := entry.Name()
// Überspringe loopback
if ifName == "lo" {
continue
}
iface := NetworkInterface{
Name: ifName,
Role: determineInterfaceRole(ifName),
}
// Status aus operstate lesen
if data, err := os.ReadFile("/sys/class/net/" + ifName + "/operstate"); err == nil {
state := strings.TrimSpace(string(data))
if state == "up" {
iface.Status = "up"
} else {
iface.Status = "down"
}
}
// MAC-Adresse lesen
if data, err := os.ReadFile("/sys/class/net/" + ifName + "/address"); err == nil {
iface.MAC = strings.TrimSpace(string(data))
}
// Statistiken hinzufügen
if stat, exists := stats[ifName]; exists {
iface.RxBytes = stat.rxBytes
iface.TxBytes = stat.txBytes
}
// IP-Adresse über ip addr bestimmen
iface.IP = getInterfaceIP(ifName)
interfaces = append(interfaces, iface)
}
return interfaces
}
// Bestimmt die Rolle eines Interfaces basierend auf dem Namen
func determineInterfaceRole(ifName string) string {
switch {
case strings.HasPrefix(ifName, "eth"):
return "lan"
case strings.HasPrefix(ifName, "ens"):
return "lan"
case strings.HasPrefix(ifName, "enp"):
return "lan"
case strings.HasPrefix(ifName, "wlan"):
return "wan"
case strings.HasPrefix(ifName, "wlp"):
return "wan"
case strings.HasPrefix(ifName, "ppp"):
return "wan"
case strings.HasPrefix(ifName, "tun"):
return "vpn"
case strings.HasPrefix(ifName, "tap"):
return "vpn"
case strings.HasPrefix(ifName, "br"):
return "bridge"
case strings.HasPrefix(ifName, "docker"):
return "virtual"
case strings.HasPrefix(ifName, "veth"):
return "virtual"
case strings.HasPrefix(ifName, "virbr"):
return "virtual"
default:
return "other"
}
}
// Liest die erste IPv4-Adresse eines Interfaces
func getInterfaceIP(ifName string) string {
output, err := exec.Command("/usr/sbin/ip", "-4", "addr", "show", ifName).CombinedOutput()
if err != nil {
return ""
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "inet ") {
fields := strings.Fields(line)
if len(fields) >= 2 {
// inet 192.168.1.100/24 ... -> 192.168.1.100
ipCidr := fields[1]
if parts := strings.SplitN(ipCidr, "/", 2); len(parts) > 0 {
return parts[0]
}
}
}
}
return ""
}
// Network-Statistiken
type netDevStat struct {
rxBytes int64
txBytes int64
}
// Liest Interface-Statistiken aus /proc/net/dev
func readNetDevStats() map[string]netDevStat {
stats := make(map[string]netDevStat)
data, err := os.ReadFile("/proc/net/dev")
if err != nil {
return stats
}
lines := strings.Split(string(data), "\n")
if len(lines) < 3 {
return stats
}
// Ersten 2 Zeilen sind Header
for _, line := range lines[2:] {
line = strings.TrimSpace(line)
if line == "" {
continue
}
// Interface: rx_bytes rx_packets ... tx_bytes tx_packets ...
parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
continue
}
ifName := strings.TrimSpace(parts[0])
values := strings.Fields(strings.TrimSpace(parts[1]))
if len(values) >= 9 {
// rx_bytes ist das erste Feld, tx_bytes das 9te Feld (0-basiert: Index 8)
rxBytes, _ := strconv.ParseInt(values[0], 10, 64)
txBytes, _ := strconv.ParseInt(values[8], 10, 64)
stats[ifName] = netDevStat{
rxBytes: rxBytes,
txBytes: txBytes,
}
}
}
return stats
}