rmm2/agent/collector/updates.go
cynfo3000 6120d0cffa Initial commit: RMM Agent + Backend
- Go Agent (FreeBSD/amd64) fuer OPNsense
- Go Backend (Linux/amd64) mit REST API + SQLite
- Collector: Hardware, CPU, Memory, Disks, Network, Services,
  WireGuard, DHCP (KEA/ISC/dnsmasq), Routes, Gateways,
  Certificates, Plugins, Updates
- Persistente Agent-ID
- TLS + API-Key Auth
- WebSocket-Infrastruktur (WIP): bidirektionaler Command-Kanal,
  TCP-Tunnel fuer Remote-Zugriff auf Firewall-WebUI
- Lastenheft und README
2026-02-28 07:38:14 +01:00

89 lines
2.5 KiB
Go

package collector
import (
"os/exec"
"strings"
)
type PendingUpdate struct {
Package string `json:"package"`
CurrentVer string `json:"current_version"`
NewVer string `json:"new_version"`
}
type UpdateInfo struct {
UpdateAvailable bool `json:"update_available"`
PendingCount int `json:"pending_count"`
Updates []PendingUpdate `json:"updates"`
OPNsenseUpdate string `json:"opnsense_update,omitempty"` // z.B. "26.1.2 -> 26.1.2_5"
}
// CollectUpdates prueft ob Updates verfuegbar sind
func CollectUpdates() UpdateInfo {
info := UpdateInfo{}
// 1. OPNsense Core Update pruefen
// opnsense-update -c: exit 0 = kein Update, exit 1 = Update verfuegbar (mit Versions-Info auf stdout)
if out, err := exec.Command("/usr/local/sbin/opnsense-update", "-c").CombinedOutput(); err != nil {
// Exit 1 = Update verfuegbar
outStr := strings.TrimSpace(string(out))
if outStr != "" {
info.OPNsenseUpdate = outStr
info.UpdateAvailable = true
}
}
// 2. Paket-Updates pruefen via pkg upgrade -n (dry-run)
out, err := exec.Command("/usr/sbin/pkg", "upgrade", "-n").CombinedOutput()
if err != nil || len(out) == 0 {
// Bei pkg upgrade -n ist exit 1 = Updates verfuegbar
// Trotzdem stdout parsen
}
content := string(out)
// Zeilen mit ":" im UPGRADED-Block parsen
// Format: "\tpackage: old_version -> new_version"
inUpgradeBlock := false
for _, line := range strings.Split(content, "\n") {
trimmed := strings.TrimSpace(line)
if strings.Contains(trimmed, "packages to be UPGRADED") ||
strings.Contains(trimmed, "will be affected") {
inUpgradeBlock = true
continue
}
if inUpgradeBlock && strings.Contains(trimmed, "->") {
// Format: "package: old -> new"
parts := strings.SplitN(trimmed, ":", 2)
if len(parts) == 2 {
pkg := strings.TrimSpace(parts[0])
versions := strings.TrimSpace(parts[1])
arrowParts := strings.SplitN(versions, "->", 2)
if len(arrowParts) == 2 {
update := PendingUpdate{
Package: pkg,
CurrentVer: strings.TrimSpace(arrowParts[0]),
NewVer: strings.TrimSpace(arrowParts[1]),
}
info.Updates = append(info.Updates, update)
info.UpdateAvailable = true
}
}
}
// Block endet bei Leerzeile oder "Number of packages"
if inUpgradeBlock && (trimmed == "" || strings.HasPrefix(trimmed, "Number of")) {
if strings.HasPrefix(trimmed, "Number of") {
continue
}
inUpgradeBlock = false
}
}
info.PendingCount = len(info.Updates)
return info
}