package main import ( "crypto/sha256" "crypto/tls" "encoding/hex" "encoding/json" "fmt" "io" "log" "net/http" "os" "os/exec" "runtime" "time" "gopkg.in/yaml.v3" ) type Config struct { BackendURL string `yaml:"backend_url"` APIKey string `yaml:"api_key"` AgentName string `yaml:"agent_name"` Insecure bool `yaml:"insecure"` } // 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 ( checkInterval = 60 * time.Second logPrefix = "[rmm-updater] " ) func main() { log.SetPrefix(logPrefix) log.SetFlags(log.LstdFlags) log.Printf("RMM Updater gestartet (platform=%s, binary=%s)", platformName, agentBinaryPath) cfg, err := loadConfig() if err != nil { log.Fatalf("Config laden fehlgeschlagen: %v", err) } agentID, err := loadAgentID() if err != nil { log.Fatalf("Agent-ID laden fehlgeschlagen: %v", err) } log.Printf("Backend: %s, Agent: %s (%s)", cfg.BackendURL, cfg.AgentName, agentID) client := &http.Client{ Timeout: 120 * time.Second, Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.Insecure}, }, } for { checkAndUpdate(client, cfg, agentID) time.Sleep(checkInterval) } } func loadConfig() (*Config, error) { data, err := os.ReadFile(configPath) if err != nil { return nil, err } var cfg Config if err := yaml.Unmarshal(data, &cfg); err != nil { return nil, err } cfg.Insecure = true return &cfg, nil } func loadAgentID() (string, error) { data, err := os.ReadFile(agentIDPath) if err != nil { return "", fmt.Errorf("agent_id nicht gefunden: %v", err) } id := string(data) if id == "" { return "", fmt.Errorf("agent_id leer") } return id, nil } func checkAndUpdate(client *http.Client, cfg *Config, agentID string) { // 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) resp, err := client.Do(req) if err != nil { log.Printf("Firmware-Info Abfrage fehlgeschlagen: %v", err) return } defer resp.Body.Close() if resp.StatusCode != 200 { return } var info struct { Available bool `json:"available"` Version string `json:"version"` Hash string `json:"hash"` Size int `json:"size"` } if err := json.NewDecoder(resp.Body).Decode(&info); err != nil || !info.Available { return } // Pruefen ob lokale Binary schon die richtige Version hat if localHash, err := hashFile(agentBinaryPath); err == nil && localHash == info.Hash { return } // 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) agentResp, err := client.Do(agentReq) if err != nil { return } defer agentResp.Body.Close() var agentInfo struct { Agent struct { UpdateRequested bool `json:"update_requested"` } `json:"agent"` } if err := json.NewDecoder(agentResp.Body).Decode(&agentInfo); err != nil { return } if !agentInfo.Agent.UpdateRequested { return } log.Printf("Update verfuegbar: v%s (%d bytes, platform=%s)", info.Version, info.Size, platformName) if err := downloadAndApply(client, cfg, agentID, info.Hash); err != nil { log.Printf("Update fehlgeschlagen: %v", err) return } log.Printf("Update auf v%s erfolgreich", info.Version) } func downloadAndApply(client *http.Client, cfg *Config, agentID, expectedHash string) error { 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) resp, err := client.Do(req) if err != nil { return fmt.Errorf("download fehlgeschlagen: %v", err) } defer resp.Body.Close() if resp.StatusCode != 200 { return fmt.Errorf("download HTTP %d", resp.StatusCode) } binary, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("download lesen fehlgeschlagen: %v", err) } // Hash verifizieren hash := sha256.Sum256(binary) hashStr := hex.EncodeToString(hash[:]) if hashStr != expectedHash { return fmt.Errorf("hash mismatch: erwartet %s, bekommen %s", expectedHash[:16], hashStr[:16]) } log.Printf("Binary heruntergeladen (%d bytes, Hash OK)", len(binary)) // Temporaere Datei schreiben tmpPath := agentBinaryPath + ".new" if err := os.WriteFile(tmpPath, binary, 0755); err != nil { return fmt.Errorf("tmp schreiben fehlgeschlagen: %v", err) } // Agent stoppen log.Println("Agent wird gestoppt...") stopAgent() time.Sleep(2 * time.Second) // 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 { os.Rename(backupPath, agentBinaryPath) return fmt.Errorf("binary ersetzen fehlgeschlagen: %v", err) } os.Chmod(agentBinaryPath, 0755) // Agent starten log.Println("Agent wird gestartet...") if err := startAgent(); err != nil { log.Printf("WARNUNG: Agent-Start fehlgeschlagen: %v — versuche Rollback", err) os.Rename(backupPath, agentBinaryPath) startAgent() return fmt.Errorf("agent start fehlgeschlagen") } // Backup aufraeumen os.Remove(backupPath) // Update-Flag loeschen clearURL := fmt.Sprintf("%s/api/v1/agents/%s/request-update", cfg.BackendURL, agentID) clearReq, _ := http.NewRequest("DELETE", clearURL, nil) clearReq.Header.Set("X-API-Key", cfg.APIKey) client.Do(clearReq) return nil } func hashFile(path string) (string, error) { data, err := os.ReadFile(path) if err != nil { return "", err } hash := sha256.Sum256(data) return hex.EncodeToString(hash[:]), nil }