Agent-Updater, Multi-Plattform Firmware, Frontend Firmware-Seite, Agent-Tab, Zertifikate-Redesign
Updater: - Separater Go-Daemon (rmm-updater) als eigener rc.d Service - Pollt alle 60s: Firmware vorhanden + Hash unterschiedlich + Flag gesetzt - Download -> SHA256 Verify -> Agent Stop -> Replace -> Agent Start - Automatisches Rollback bei Fehler - Deployed und getestet auf allen 3 Firewalls (.1, .19, .33) Backend: - Firmware-DB multi-plattform (freebsd/linux/windows) - update_requested Flag pro Agent in agents Tabelle - Heartbeat-Response liefert update_available wenn Flag gesetzt - Neue Endpoints: request-update, request-update-all, firmware/download - Auth: JWT + CombinedAuth Middleware (API-Key OR JWT) - Customers CRUD + Agent-Zuordnung - Users CRUD + Passwort-Aenderung Frontend: - Firmware-Seite: Multi-Plattform Upload, Agent-Liste mit Update-Trigger - Agent-Tab im AgentPanel: Update-Button, nuetzliche Befehle, Agent-Info - Zertifikate-Tab redesigned: Karten-Layout mit Restlaufzeit und Farbbalken - Sidebar: Firmware-Eintrag hinzugefuegt Plugin: - install.sh erweitert: Installiert Agent + Updater + rc.d Services - rmm_updater rc.d Service README: - Architektur-Diagramm erweitert (Frontend + Updater) - Auth, Customers, Users, Firmware API dokumentiert - Updater-Flow dokumentiert - Frontend-Seiten und Deploy beschrieben - DB-Schema aktualisiert (neue Tabellen) - Dateistruktur erweitert (updater/, frontend/, firmware.go, auth.go)
This commit is contained in:
+28
-7
@@ -99,7 +99,6 @@ func (c *Client) reconnectLoop() {
|
||||
|
||||
select {
|
||||
case <-time.After(backoff):
|
||||
// Exponential backoff mit Maximum
|
||||
backoff = time.Duration(float64(backoff) * 1.5)
|
||||
if backoff > maxBackoff {
|
||||
backoff = maxBackoff
|
||||
@@ -110,14 +109,38 @@ func (c *Client) reconnectLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Verbindung erfolgreich - backoff zurücksetzen
|
||||
backoff = c.reconnectInterval
|
||||
connStart := time.Now()
|
||||
log.Println("WebSocket-Verbindung hergestellt")
|
||||
|
||||
// Ping/Pong und Message-Handling starten
|
||||
c.runConnection()
|
||||
|
||||
log.Println("WebSocket-Verbindung getrennt")
|
||||
connDuration := time.Since(connStart)
|
||||
log.Printf("WebSocket-Verbindung getrennt (Dauer: %v)", connDuration)
|
||||
|
||||
// Alte Verbindung explizit schliessen
|
||||
c.connMux.Lock()
|
||||
if c.conn != nil {
|
||||
c.conn.Close()
|
||||
c.conn = nil
|
||||
}
|
||||
c.connMux.Unlock()
|
||||
|
||||
// Minimum-Delay zwischen Reconnects (verhindert Tight-Loop)
|
||||
if connDuration < 5*time.Second {
|
||||
backoff = time.Duration(float64(backoff) * 1.5)
|
||||
if backoff > maxBackoff {
|
||||
backoff = maxBackoff
|
||||
}
|
||||
log.Printf("Verbindung zu kurz, warte %v vor Reconnect", backoff)
|
||||
select {
|
||||
case <-time.After(backoff):
|
||||
case <-c.ctx.Done():
|
||||
return
|
||||
}
|
||||
} else {
|
||||
backoff = c.reconnectInterval
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,9 +229,7 @@ func (c *Client) runConnection() {
|
||||
go c.handleMessage(msg)
|
||||
|
||||
case err := <-errChan:
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
||||
log.Printf("WebSocket-Fehler: %v", err)
|
||||
}
|
||||
log.Printf("WebSocket-Lesefehler: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ func (h *Handler) HandleCommand(msg Message) {
|
||||
response.Data = map[string]interface{}{"message": "tunnel_open deprecated, use tunnel_connect"}
|
||||
case "tunnel_close":
|
||||
response.Status = "ok"
|
||||
case "agent_update":
|
||||
response = h.handleAgentUpdate(msg)
|
||||
default:
|
||||
response.Status = "error"
|
||||
response.Error = fmt.Sprintf("Unbekanntes Command: %s", msg.Command)
|
||||
@@ -558,6 +560,89 @@ func (h *Handler) parsePkgUpgrade(output string) []map[string]string {
|
||||
return packages
|
||||
}
|
||||
|
||||
func (h *Handler) handleAgentUpdate(msg Message) Message {
|
||||
response := Message{Type: "response", ID: msg.ID}
|
||||
|
||||
// Binary kommt als Base64 im Data-Feld
|
||||
data, ok := msg.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
response.Status = "error"
|
||||
response.Error = "Ungueltige Daten"
|
||||
return response
|
||||
}
|
||||
|
||||
binaryB64, _ := data["binary"].(string)
|
||||
expectedHash, _ := data["hash"].(string)
|
||||
newVersion, _ := data["version"].(string)
|
||||
|
||||
if binaryB64 == "" || expectedHash == "" {
|
||||
response.Status = "error"
|
||||
response.Error = "Binary oder Hash fehlt"
|
||||
return response
|
||||
}
|
||||
|
||||
log.Printf("Agent-Update empfangen: Version %s, Hash %s", newVersion, expectedHash[:16])
|
||||
|
||||
// Base64 dekodieren
|
||||
binary, err := base64.StdEncoding.DecodeString(binaryB64)
|
||||
if err != nil {
|
||||
response.Status = "error"
|
||||
response.Error = fmt.Sprintf("Base64-Dekodierung fehlgeschlagen: %v", err)
|
||||
return response
|
||||
}
|
||||
|
||||
// Hash pruefen
|
||||
hash := sha256.Sum256(binary)
|
||||
actualHash := hex.EncodeToString(hash[:])
|
||||
if actualHash != expectedHash {
|
||||
response.Status = "error"
|
||||
response.Error = fmt.Sprintf("Hash-Mismatch: erwartet %s, bekommen %s", expectedHash[:16], actualHash[:16])
|
||||
return response
|
||||
}
|
||||
|
||||
// Aktuelles Binary finden
|
||||
binaryPath := "/usr/local/rmm/rmm-agent"
|
||||
if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
|
||||
// Fallback: eigener Pfad
|
||||
binaryPath, _ = os.Executable()
|
||||
}
|
||||
|
||||
// Backup vom alten Binary
|
||||
backupPath := binaryPath + ".old"
|
||||
if err := os.Rename(binaryPath, backupPath); err != nil {
|
||||
response.Status = "error"
|
||||
response.Error = fmt.Sprintf("Backup fehlgeschlagen: %v", err)
|
||||
return response
|
||||
}
|
||||
|
||||
// Neues Binary schreiben
|
||||
if err := os.WriteFile(binaryPath, binary, 0755); err != nil {
|
||||
// Rollback
|
||||
os.Rename(backupPath, binaryPath)
|
||||
response.Status = "error"
|
||||
response.Error = fmt.Sprintf("Binary schreiben fehlgeschlagen: %v", err)
|
||||
return response
|
||||
}
|
||||
|
||||
log.Printf("Neues Binary geschrieben: %s (%d bytes)", binaryPath, len(binary))
|
||||
|
||||
response.Status = "ok"
|
||||
response.Data = map[string]interface{}{
|
||||
"message": fmt.Sprintf("Update auf %s erfolgreich, Neustart...", newVersion),
|
||||
"old_version": data["old_version"],
|
||||
"new_version": newVersion,
|
||||
}
|
||||
|
||||
// Response senden, dann Neustart
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
log.Printf("Agent-Neustart nach Update auf %s", newVersion)
|
||||
exec.Command("/usr/sbin/service", "rmm_agent", "restart").Run()
|
||||
}()
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// SendSessionData schickt Tunnel-Daten als Binary-Message zurueck zum Backend
|
||||
func (h *Handler) SendSessionData(sessionID string, data []byte) error {
|
||||
idBytes := []byte(sessionID)
|
||||
|
||||
Reference in New Issue
Block a user