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
This commit is contained in:
+61
-42
@@ -11,13 +11,12 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Config wird aus der gleichen config.yaml wie der Agent gelesen
|
||||
type Config struct {
|
||||
BackendURL string `yaml:"backend_url"`
|
||||
APIKey string `yaml:"api_key"`
|
||||
@@ -25,26 +24,57 @@ type Config struct {
|
||||
Insecure bool `yaml:"insecure"`
|
||||
}
|
||||
|
||||
type HeartbeatResponse struct {
|
||||
Message string `json:"message"`
|
||||
UpdateAvailable bool `json:"update_available"`
|
||||
UpdateVersion string `json:"update_version"`
|
||||
UpdateHash string `json:"update_hash"`
|
||||
// Plattform-spezifische Pfade und Befehle
|
||||
var (
|
||||
agentBinaryPath string
|
||||
configPath string
|
||||
agentIDPath string
|
||||
platformName string
|
||||
)
|
||||
|
||||
func init() {
|
||||
switch runtime.GOOS {
|
||||
case "freebsd":
|
||||
agentBinaryPath = "/usr/local/rmm/rmm-agent"
|
||||
configPath = "/usr/local/rmm/config.yaml"
|
||||
agentIDPath = "/usr/local/rmm/agent_id"
|
||||
platformName = "freebsd"
|
||||
default: // linux
|
||||
agentBinaryPath = "/usr/local/bin/rmm-agent"
|
||||
configPath = "/etc/rmm/config.yaml"
|
||||
agentIDPath = "/etc/rmm/agent_id"
|
||||
platformName = "linux"
|
||||
}
|
||||
}
|
||||
|
||||
func stopAgent() error {
|
||||
switch runtime.GOOS {
|
||||
case "freebsd":
|
||||
return exec.Command("/usr/sbin/service", "rmm_agent", "stop").Run()
|
||||
default:
|
||||
return exec.Command("/usr/bin/systemctl", "stop", "rmm-agent").Run()
|
||||
}
|
||||
}
|
||||
|
||||
func startAgent() error {
|
||||
switch runtime.GOOS {
|
||||
case "freebsd":
|
||||
return exec.Command("/usr/sbin/service", "rmm_agent", "start").Run()
|
||||
default:
|
||||
return exec.Command("/usr/bin/systemctl", "start", "rmm-agent").Run()
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
agentBinaryPath = "/usr/local/rmm/rmm-agent"
|
||||
configPath = "/usr/local/rmm/config.yaml"
|
||||
agentIDPath = "/usr/local/rmm/agent_id"
|
||||
checkInterval = 60 * time.Second
|
||||
logPrefix = "[rmm-updater] "
|
||||
checkInterval = 60 * time.Second
|
||||
logPrefix = "[rmm-updater] "
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetPrefix(logPrefix)
|
||||
log.SetFlags(log.LstdFlags)
|
||||
|
||||
log.Println("RMM Updater gestartet")
|
||||
log.Printf("RMM Updater gestartet (platform=%s, binary=%s)", platformName, agentBinaryPath)
|
||||
|
||||
cfg, err := loadConfig()
|
||||
if err != nil {
|
||||
@@ -80,9 +110,6 @@ func loadConfig() (*Config, error) {
|
||||
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Default: insecure wenn nicht explizit gesetzt (self-signed certs sind Standard)
|
||||
// yaml unmarshal setzt false als default fuer bool — wir brauchen true als default
|
||||
// Workaround: immer insecure wenn Backend self-signed nutzt
|
||||
cfg.Insecure = true
|
||||
return &cfg, nil
|
||||
}
|
||||
@@ -100,8 +127,8 @@ func loadAgentID() (string, error) {
|
||||
}
|
||||
|
||||
func checkAndUpdate(client *http.Client, cfg *Config, agentID string) {
|
||||
// Firmware-Info vom Backend holen
|
||||
url := fmt.Sprintf("%s/api/v1/firmware", cfg.BackendURL)
|
||||
// Firmware-Info vom Backend holen (plattformspezifisch)
|
||||
url := fmt.Sprintf("%s/api/v1/firmware?platform=%s", cfg.BackendURL, platformName)
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("X-API-Key", cfg.APIKey)
|
||||
|
||||
@@ -113,7 +140,7 @@ func checkAndUpdate(client *http.Client, cfg *Config, agentID string) {
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return // Keine Firmware vorhanden
|
||||
return
|
||||
}
|
||||
|
||||
var info struct {
|
||||
@@ -128,35 +155,34 @@ func checkAndUpdate(client *http.Client, cfg *Config, agentID string) {
|
||||
|
||||
// Pruefen ob lokale Binary schon die richtige Version hat
|
||||
if localHash, err := hashFile(agentBinaryPath); err == nil && localHash == info.Hash {
|
||||
return // Schon aktuell
|
||||
return
|
||||
}
|
||||
|
||||
// Heartbeat-Response pruefen: Update angefordert?
|
||||
hbURL := fmt.Sprintf("%s/api/v1/agents/%s", cfg.BackendURL, agentID)
|
||||
hbReq, _ := http.NewRequest("GET", hbURL, nil)
|
||||
hbReq.Header.Set("X-API-Key", cfg.APIKey)
|
||||
// Update angefordert?
|
||||
agentURL := fmt.Sprintf("%s/api/v1/agents/%s", cfg.BackendURL, agentID)
|
||||
agentReq, _ := http.NewRequest("GET", agentURL, nil)
|
||||
agentReq.Header.Set("X-API-Key", cfg.APIKey)
|
||||
|
||||
hbResp, err := client.Do(hbReq)
|
||||
agentResp, err := client.Do(agentReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer hbResp.Body.Close()
|
||||
defer agentResp.Body.Close()
|
||||
|
||||
var agentInfo struct {
|
||||
Agent struct {
|
||||
UpdateRequested bool `json:"update_requested"`
|
||||
} `json:"agent"`
|
||||
}
|
||||
if err := json.NewDecoder(hbResp.Body).Decode(&agentInfo); err != nil {
|
||||
if err := json.NewDecoder(agentResp.Body).Decode(&agentInfo); err != nil {
|
||||
return
|
||||
}
|
||||
if !agentInfo.Agent.UpdateRequested {
|
||||
return // Kein Update angefordert
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Update verfuegbar: v%s (%d bytes)", info.Version, info.Size)
|
||||
log.Printf("Update verfuegbar: v%s (%d bytes, platform=%s)", info.Version, info.Size, platformName)
|
||||
|
||||
// Binary herunterladen
|
||||
if err := downloadAndApply(client, cfg, agentID, info.Hash); err != nil {
|
||||
log.Printf("Update fehlgeschlagen: %v", err)
|
||||
return
|
||||
@@ -166,8 +192,7 @@ func checkAndUpdate(client *http.Client, cfg *Config, agentID string) {
|
||||
}
|
||||
|
||||
func downloadAndApply(client *http.Client, cfg *Config, agentID, expectedHash string) error {
|
||||
// Download
|
||||
url := fmt.Sprintf("%s/api/v1/firmware/download", cfg.BackendURL)
|
||||
url := fmt.Sprintf("%s/api/v1/firmware/download?platform=%s", cfg.BackendURL, platformName)
|
||||
req, _ := http.NewRequest("GET", url, nil)
|
||||
req.Header.Set("X-API-Key", cfg.APIKey)
|
||||
|
||||
@@ -203,16 +228,15 @@ func downloadAndApply(client *http.Client, cfg *Config, agentID, expectedHash st
|
||||
|
||||
// Agent stoppen
|
||||
log.Println("Agent wird gestoppt...")
|
||||
exec.Command("/usr/sbin/service", "rmm_agent", "stop").Run()
|
||||
stopAgent()
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Binary ersetzen
|
||||
// Binary ersetzen mit Rollback
|
||||
backupPath := agentBinaryPath + ".bak"
|
||||
if _, err := os.Stat(agentBinaryPath); err == nil {
|
||||
os.Rename(agentBinaryPath, backupPath)
|
||||
}
|
||||
if err := os.Rename(tmpPath, agentBinaryPath); err != nil {
|
||||
// Rollback
|
||||
os.Rename(backupPath, agentBinaryPath)
|
||||
return fmt.Errorf("binary ersetzen fehlgeschlagen: %v", err)
|
||||
}
|
||||
@@ -220,10 +244,10 @@ func downloadAndApply(client *http.Client, cfg *Config, agentID, expectedHash st
|
||||
|
||||
// Agent starten
|
||||
log.Println("Agent wird gestartet...")
|
||||
if err := exec.Command("/usr/sbin/service", "rmm_agent", "start").Run(); err != nil {
|
||||
if err := startAgent(); err != nil {
|
||||
log.Printf("WARNUNG: Agent-Start fehlgeschlagen: %v — versuche Rollback", err)
|
||||
os.Rename(backupPath, agentBinaryPath)
|
||||
exec.Command("/usr/sbin/service", "rmm_agent", "start").Run()
|
||||
startAgent()
|
||||
return fmt.Errorf("agent start fehlgeschlagen")
|
||||
}
|
||||
|
||||
@@ -247,8 +271,3 @@ func hashFile(path string) (string, error) {
|
||||
hash := sha256.Sum256(data)
|
||||
return hex.EncodeToString(hash[:]), nil
|
||||
}
|
||||
|
||||
// resolveDir gibt das Verzeichnis eines Pfads zurueck
|
||||
func resolveDir(path string) string {
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user