diff --git a/agent-windows/main.go b/agent-windows/main.go index b20765f..b3b16b2 100644 --- a/agent-windows/main.go +++ b/agent-windows/main.go @@ -3,11 +3,17 @@ package main import ( + "crypto/sha256" + "crypto/tls" + "encoding/hex" "encoding/json" "flag" "fmt" + "io" "log" + "net/http" "os" + "os/exec" "path/filepath" "time" @@ -124,7 +130,7 @@ func runAgent(cfg *config.Config, cfgPath string, stop <-chan struct{}) { defer ticker.Stop() // Ersten Heartbeat sofort - sendHeartbeat(c, agentID) + sendHeartbeat(c, cfg, agentID) for { select { @@ -132,12 +138,12 @@ func runAgent(cfg *config.Config, cfgPath string, stop <-chan struct{}) { log.Println("Agent wird gestoppt") return case <-ticker.C: - sendHeartbeat(c, agentID) + sendHeartbeat(c, cfg, agentID) } } } -func sendHeartbeat(c *client.Client, agentID string) { +func sendHeartbeat(c *client.Client, cfg *config.Config, agentID string) { sysData, err := collector.Collect() if err != nil { log.Printf("Collector-Fehler: %v", err) @@ -160,12 +166,104 @@ func sendHeartbeat(c *client.Client, agentID string) { } log.Printf("Heartbeat OK") - if resp.UpdateAvailable { - log.Printf("Update verfuegbar: v%s — OTA-Update noch nicht implementiert", resp.UpdateVersion) - // TODO: OTA-Updater implementieren (aehnlich rmm-updater auf Linux) + if resp.UpdateAvailable && resp.UpdateVersion != "" && resp.UpdateVersion != Version { + log.Printf("Update verfuegbar: v%s → v%s", Version, resp.UpdateVersion) + go doSelfUpdate(cfg, resp.UpdateVersion, resp.UpdateHash) } } +// doSelfUpdate laedt das neue Binary herunter und startet einen +// PowerShell-Script der nach Dienst-Stop das Binary tauscht und neu startet. +func doSelfUpdate(cfg *config.Config, newVersion, expectedHash string) { + log.Printf("OTA-Update: Starte Download v%s", newVersion) + + exe, err := os.Executable() + if err != nil { + log.Printf("OTA-Update: Executable-Pfad: %v", err) + return + } + dir := filepath.Dir(exe) + tmpBin := filepath.Join(dir, "rmm-agent-windows-update.exe") + scriptPath := filepath.Join(dir, "rmm-update.ps1") + + // Binary herunterladen + dlURL := fmt.Sprintf("%s/api/v1/firmware/download?platform=windows&api_key=%s", + cfg.BackendURL, cfg.APIKey) + + tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.Insecure}} + hc := &http.Client{Transport: tr, Timeout: 10 * time.Minute} + + req, err := http.NewRequest("GET", dlURL, nil) + if err != nil { + log.Printf("OTA-Update: Request: %v", err) + return + } + req.Header.Set("X-API-Key", cfg.APIKey) + + resp, err := hc.Do(req) + if err != nil { + log.Printf("OTA-Update: Download: %v", err) + return + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + log.Printf("OTA-Update: HTTP %d", resp.StatusCode) + return + } + + f, err := os.Create(tmpBin) + if err != nil { + log.Printf("OTA-Update: Temp-Datei: %v", err) + return + } + + h := sha256.New() + if _, err := io.Copy(io.MultiWriter(f, h), resp.Body); err != nil { + f.Close() + os.Remove(tmpBin) + log.Printf("OTA-Update: Schreiben: %v", err) + return + } + f.Close() + + // Hash pruefen + gotHash := hex.EncodeToString(h.Sum(nil)) + if expectedHash != "" && gotHash != expectedHash { + os.Remove(tmpBin) + log.Printf("OTA-Update: Hash-Mismatch: erwartet=%s, erhalten=%s", expectedHash, gotHash) + return + } + + log.Printf("OTA-Update: Download OK (hash=%s), starte Update-Script", gotHash[:12]) + + // PowerShell-Script schreiben das nach Dienst-Stop das Binary tauscht + psScript := fmt.Sprintf(` +Start-Sleep -Seconds 3 +Stop-Service -Name RMMAgent -Force -ErrorAction SilentlyContinue +Start-Sleep -Seconds 2 +Copy-Item -Path "%s" -Destination "%s" -Force +Remove-Item -Path "%s" -Force -ErrorAction SilentlyContinue +Remove-Item -Path "%s" -Force -ErrorAction SilentlyContinue +Start-Service -Name RMMAgent +`, tmpBin, exe, tmpBin, scriptPath) + + if err := os.WriteFile(scriptPath, []byte(psScript), 0644); err != nil { + log.Printf("OTA-Update: Script schreiben: %v", err) + return + } + + // Script detached starten (laeuft weiter wenn dieser Prozess beendet wird) + cmd := exec.Command("powershell", "-NonInteractive", "-ExecutionPolicy", "Bypass", + "-WindowStyle", "Hidden", "-File", scriptPath) + if err := cmd.Start(); err != nil { + log.Printf("OTA-Update: Script starten: %v", err) + return + } + // cmd.Wait() nicht aufrufen — Script laeuft unabhaengig weiter + log.Printf("OTA-Update: Script gestartet (PID %d), Dienst wird gestoppt", cmd.Process.Pid) +} + // loadOrRegister liest die Agent-ID aus Datei oder registriert sich neu func loadOrRegister(c *client.Client, cfg *config.Config, cfgPath string) (string, error) { idFile := filepath.Join(filepath.Dir(cfgPath), AgentIDFile)