feat: Windows Agent OTA-Updater
- PowerShell-Script-Methode: Binary herunterladen, detached PS1 tauscht Binary nach Dienst-Stop - SHA256-Verifikation vor Update - v1.0.1 im Firmware-Store
This commit is contained in:
parent
2fe83e261a
commit
e94fea3e91
@ -3,11 +3,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -124,7 +130,7 @@ func runAgent(cfg *config.Config, cfgPath string, stop <-chan struct{}) {
|
|||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
// Ersten Heartbeat sofort
|
// Ersten Heartbeat sofort
|
||||||
sendHeartbeat(c, agentID)
|
sendHeartbeat(c, cfg, agentID)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -132,12 +138,12 @@ func runAgent(cfg *config.Config, cfgPath string, stop <-chan struct{}) {
|
|||||||
log.Println("Agent wird gestoppt")
|
log.Println("Agent wird gestoppt")
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
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()
|
sysData, err := collector.Collect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Collector-Fehler: %v", err)
|
log.Printf("Collector-Fehler: %v", err)
|
||||||
@ -160,12 +166,104 @@ func sendHeartbeat(c *client.Client, agentID string) {
|
|||||||
}
|
}
|
||||||
log.Printf("Heartbeat OK")
|
log.Printf("Heartbeat OK")
|
||||||
|
|
||||||
if resp.UpdateAvailable {
|
if resp.UpdateAvailable && resp.UpdateVersion != "" && resp.UpdateVersion != Version {
|
||||||
log.Printf("Update verfuegbar: v%s — OTA-Update noch nicht implementiert", resp.UpdateVersion)
|
log.Printf("Update verfuegbar: v%s → v%s", Version, resp.UpdateVersion)
|
||||||
// TODO: OTA-Updater implementieren (aehnlich rmm-updater auf Linux)
|
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
|
// loadOrRegister liest die Agent-ID aus Datei oder registriert sich neu
|
||||||
func loadOrRegister(c *client.Client, cfg *config.Config, cfgPath string) (string, error) {
|
func loadOrRegister(c *client.Client, cfg *config.Config, cfgPath string) (string, error) {
|
||||||
idFile := filepath.Join(filepath.Dir(cfgPath), AgentIDFile)
|
idFile := filepath.Join(filepath.Dir(cfgPath), AgentIDFile)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user