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:
@@ -0,0 +1,219 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/cynfo/rmm-backend/ws"
|
||||
)
|
||||
|
||||
// POST /api/v1/firmware/upload — Neues Agent-Binary hochladen
|
||||
func (h *Handler) uploadFirmware(w http.ResponseWriter, r *http.Request) {
|
||||
version := r.URL.Query().Get("version")
|
||||
if version == "" {
|
||||
writeError(w, http.StatusBadRequest, "version Parameter erforderlich")
|
||||
return
|
||||
}
|
||||
|
||||
platform := r.URL.Query().Get("platform")
|
||||
if platform == "" {
|
||||
platform = "freebsd" // Default fuer OPNsense
|
||||
}
|
||||
// Validieren
|
||||
validPlatforms := map[string]bool{"freebsd": true, "linux": true, "windows": true}
|
||||
if !validPlatforms[platform] {
|
||||
writeError(w, http.StatusBadRequest, "Ungueltige Plattform (freebsd, linux, windows)")
|
||||
return
|
||||
}
|
||||
|
||||
// Binary aus Body lesen (max 50MB)
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 50*1024*1024)
|
||||
binary, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Binary lesen fehlgeschlagen")
|
||||
return
|
||||
}
|
||||
|
||||
if len(binary) == 0 {
|
||||
writeError(w, http.StatusBadRequest, "Leeres Binary")
|
||||
return
|
||||
}
|
||||
|
||||
hash := sha256.Sum256(binary)
|
||||
hashStr := hex.EncodeToString(hash[:])
|
||||
|
||||
if err := h.db.SaveAgentFirmware(version, platform, hashStr, binary); err != nil {
|
||||
log.Printf("Firmware speichern fehlgeschlagen: %v", err)
|
||||
writeError(w, http.StatusInternalServerError, "Speichern fehlgeschlagen")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Agent-Firmware v%s (%s) hochgeladen (%d bytes, Hash: %s)", version, platform, len(binary), hashStr[:16])
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"version": version,
|
||||
"platform": platform,
|
||||
"hash": hashStr,
|
||||
"size": len(binary),
|
||||
"message": "Firmware hochgeladen",
|
||||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/firmware — Firmware-Info (alle Plattformen oder spezifisch)
|
||||
func (h *Handler) getFirmwareInfo(w http.ResponseWriter, r *http.Request) {
|
||||
platform := r.URL.Query().Get("platform")
|
||||
|
||||
if platform == "" {
|
||||
// Alle Plattformen auflisten
|
||||
all, err := h.db.GetAllFirmwareInfo()
|
||||
if err != nil || len(all) == 0 {
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"available": false,
|
||||
"message": "Keine Firmware hochgeladen",
|
||||
"platforms": []interface{}{},
|
||||
})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"available": true,
|
||||
"platforms": all,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
version, hash, size, err := h.db.GetFirmwareInfo(platform)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"available": false,
|
||||
"platform": platform,
|
||||
"message": "Keine Firmware fuer diese Plattform",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"available": true,
|
||||
"platform": platform,
|
||||
"version": version,
|
||||
"hash": hash,
|
||||
"size": size,
|
||||
})
|
||||
}
|
||||
|
||||
// GET /api/v1/firmware/download — Binary herunterladen (fuer Updater)
|
||||
func (h *Handler) downloadFirmware(w http.ResponseWriter, r *http.Request) {
|
||||
platform := r.URL.Query().Get("platform")
|
||||
if platform == "" {
|
||||
platform = "freebsd"
|
||||
}
|
||||
version, hash, binary, err := h.db.GetLatestFirmware(platform)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "Keine Firmware verfuegbar")
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=rmm-agent")
|
||||
w.Header().Set("X-Firmware-Version", version)
|
||||
w.Header().Set("X-Firmware-Hash", hash)
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(binary)))
|
||||
w.Write(binary)
|
||||
}
|
||||
|
||||
// POST /api/v1/agents/{id}/request-update — Update-Flag setzen
|
||||
func (h *Handler) requestUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
agentID := r.PathValue("id")
|
||||
if err := h.db.SetUpdateRequest(agentID, true); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Fehler beim Setzen des Update-Flags")
|
||||
return
|
||||
}
|
||||
log.Printf("Update angefordert fuer Agent %s", agentID)
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Update angefordert", "agent_id": agentID})
|
||||
}
|
||||
|
||||
// DELETE /api/v1/agents/{id}/request-update — Update-Flag loeschen
|
||||
func (h *Handler) cancelUpdate(w http.ResponseWriter, r *http.Request) {
|
||||
agentID := r.PathValue("id")
|
||||
h.db.ClearUpdateRequest(agentID)
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{"message": "Update-Anforderung geloescht", "agent_id": agentID})
|
||||
}
|
||||
|
||||
// POST /api/v1/agents/request-update-all — Update-Flag fuer alle setzen
|
||||
func (h *Handler) requestUpdateAll(w http.ResponseWriter, r *http.Request) {
|
||||
count, err := h.db.SetUpdateRequestAll(true)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "Fehler")
|
||||
return
|
||||
}
|
||||
log.Printf("Update angefordert fuer %d Agents", count)
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{"message": fmt.Sprintf("Update fuer %d Agents angefordert", count), "count": count})
|
||||
}
|
||||
|
||||
// POST /api/v1/agents/{id}/agent-update — Update an einzelnen Agent pushen
|
||||
func (h *Handler) pushAgentUpdate(hub *ws.Hub) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
agentID := r.PathValue("id")
|
||||
|
||||
// TODO: Agent-Plattform aus DB lesen; fuer jetzt default freebsd
|
||||
version, hash, binary, err := h.db.GetLatestFirmware("freebsd")
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "Keine Firmware verfuegbar")
|
||||
return
|
||||
}
|
||||
|
||||
// Agent-Version pruefen
|
||||
agent, _, err := h.db.GetAgent(agentID)
|
||||
if err != nil || agent == nil {
|
||||
writeError(w, http.StatusNotFound, "Agent nicht gefunden")
|
||||
return
|
||||
}
|
||||
|
||||
if agent.AgentVersion == version {
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"message": "Agent ist bereits auf dem neuesten Stand",
|
||||
"current_version": agent.AgentVersion,
|
||||
"target_version": version,
|
||||
"updated": false,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Binary als Base64 an Agent senden
|
||||
binaryB64 := base64.StdEncoding.EncodeToString(binary)
|
||||
|
||||
cmdID := fmt.Sprintf("agent-update-%s", agentID[:8])
|
||||
cmd := map[string]interface{}{
|
||||
"type": "command",
|
||||
"id": cmdID,
|
||||
"command": "agent_update",
|
||||
"data": map[string]interface{}{
|
||||
"binary": binaryB64,
|
||||
"hash": hash,
|
||||
"version": version,
|
||||
"old_version": agent.AgentVersion,
|
||||
},
|
||||
}
|
||||
|
||||
cmdJSON, _ := json.Marshal(cmd)
|
||||
if err := hub.SendToAgent(agentID, cmdJSON); err != nil {
|
||||
writeError(w, http.StatusBadGateway, fmt.Sprintf("Agent nicht erreichbar: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Agent-Update v%s an %s gesendet (%d bytes)", version, agent.Name, len(binary))
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"message": fmt.Sprintf("Update v%s an %s gesendet", version, agent.Name),
|
||||
"current_version": agent.AgentVersion,
|
||||
"target_version": version,
|
||||
"updated": true,
|
||||
"size": len(binary),
|
||||
})
|
||||
}
|
||||
}
|
||||
+32
-1
@@ -53,6 +53,15 @@ func (h *Handler) SetupRoutes(mux *http.ServeMux, hub *ws.Hub) {
|
||||
mux.HandleFunc("PUT /api/v1/users/{id}/password", h.changePassword)
|
||||
mux.HandleFunc("DELETE /api/v1/users/{id}", h.deleteUser)
|
||||
|
||||
// Firmware/Agent-Update Routes
|
||||
mux.HandleFunc("GET /api/v1/firmware", h.getFirmwareInfo)
|
||||
mux.HandleFunc("POST /api/v1/firmware/upload", h.uploadFirmware)
|
||||
mux.HandleFunc("GET /api/v1/firmware/download", h.downloadFirmware)
|
||||
mux.HandleFunc("POST /api/v1/agents/{id}/agent-update", h.pushAgentUpdate(hub))
|
||||
mux.HandleFunc("POST /api/v1/agents/{id}/request-update", h.requestUpdate)
|
||||
mux.HandleFunc("DELETE /api/v1/agents/{id}/request-update", h.cancelUpdate)
|
||||
mux.HandleFunc("POST /api/v1/agents/request-update-all", h.requestUpdateAll)
|
||||
|
||||
// WebSocket und Tunnel-Routes
|
||||
h.setupTunnelRoutes(mux, hub)
|
||||
}
|
||||
@@ -115,6 +124,7 @@ func (h *Handler) registerAgent(w http.ResponseWriter, r *http.Request) {
|
||||
Hostname: req.Hostname,
|
||||
IP: req.IP,
|
||||
OPNsenseVersion: req.OPNsenseVersion,
|
||||
AgentVersion: req.AgentVersion,
|
||||
RegisteredAt: time.Now().UTC(),
|
||||
}
|
||||
|
||||
@@ -148,6 +158,11 @@ func (h *Handler) heartbeat(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Agent-Version aktualisieren
|
||||
if req.AgentVersion != "" {
|
||||
h.db.UpdateAgentVersion(req.AgentID, req.AgentVersion)
|
||||
}
|
||||
|
||||
if err := h.db.SaveSystemData(req.AgentID, &req.SystemData); err != nil {
|
||||
if strings.Contains(err.Error(), "nicht gefunden") {
|
||||
writeError(w, http.StatusNotFound, "Agent nicht gefunden")
|
||||
@@ -158,7 +173,23 @@ func (h *Handler) heartbeat(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, models.HeartbeatResponse{Message: "OK"})
|
||||
resp := models.HeartbeatResponse{Message: "OK"}
|
||||
|
||||
// Pruefen ob Update ansteht (default freebsd fuer OPNsense)
|
||||
if h.db.IsUpdateRequested(req.AgentID) {
|
||||
if version, hash, _, err := h.db.GetFirmwareInfo("freebsd"); err == nil {
|
||||
if version != req.AgentVersion {
|
||||
resp.UpdateAvailable = true
|
||||
resp.UpdateVersion = version
|
||||
resp.UpdateHash = hash
|
||||
} else {
|
||||
// Gleiche Version — Flag zuruecksetzen
|
||||
h.db.ClearUpdateRequest(req.AgentID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// GET /api/v1/agents
|
||||
|
||||
+121
-8
@@ -67,6 +67,7 @@ func (d *Database) migrate() error {
|
||||
hostname TEXT NOT NULL,
|
||||
ip TEXT NOT NULL,
|
||||
opnsense_version TEXT NOT NULL DEFAULT '',
|
||||
agent_version TEXT NOT NULL DEFAULT '',
|
||||
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_heartbeat TIMESTAMPTZ
|
||||
);
|
||||
@@ -124,6 +125,27 @@ func (d *Database) migrate() error {
|
||||
d.db.Exec("ALTER TABLE agents ADD COLUMN IF NOT EXISTS customer_id INTEGER REFERENCES customers(id)")
|
||||
d.db.Exec("CREATE INDEX IF NOT EXISTS idx_agents_customer ON agents(customer_id)")
|
||||
|
||||
// Agents um agent_version und update_requested erweitern
|
||||
d.db.Exec("ALTER TABLE agents ADD COLUMN IF NOT EXISTS agent_version TEXT NOT NULL DEFAULT ''")
|
||||
d.db.Exec("ALTER TABLE agents ADD COLUMN IF NOT EXISTS update_requested BOOLEAN NOT NULL DEFAULT FALSE")
|
||||
|
||||
// Agent-Firmware Tabelle (Multi-Plattform)
|
||||
d.db.Exec(`CREATE TABLE IF NOT EXISTS agent_firmware (
|
||||
version TEXT PRIMARY KEY,
|
||||
hash TEXT NOT NULL,
|
||||
binary_data BYTEA NOT NULL,
|
||||
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)`)
|
||||
// Platform-Spalte hinzufuegen (default freebsd fuer bestehende Eintraege)
|
||||
d.db.Exec("ALTER TABLE agent_firmware DROP CONSTRAINT IF EXISTS agent_firmware_pkey")
|
||||
d.db.Exec("ALTER TABLE agent_firmware ADD COLUMN IF NOT EXISTS platform TEXT NOT NULL DEFAULT 'freebsd'")
|
||||
// Neuer Primary Key: version + platform
|
||||
d.db.Exec(`DO $$ BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'agent_firmware_version_platform_key') THEN
|
||||
ALTER TABLE agent_firmware ADD CONSTRAINT agent_firmware_version_platform_key UNIQUE (version, platform);
|
||||
END IF;
|
||||
END $$`)
|
||||
|
||||
// Metrics Hypertable
|
||||
metricsSchema := `
|
||||
CREATE TABLE IF NOT EXISTS metrics (
|
||||
@@ -220,14 +242,15 @@ func (d *Database) Close() error {
|
||||
|
||||
func (d *Database) RegisterAgent(agent *models.Agent) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT INTO agents (id, name, hostname, ip, opnsense_version, registered_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
INSERT INTO agents (id, name, hostname, ip, opnsense_version, agent_version, registered_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
name=EXCLUDED.name,
|
||||
hostname=EXCLUDED.hostname,
|
||||
ip=EXCLUDED.ip,
|
||||
opnsense_version=EXCLUDED.opnsense_version
|
||||
`, agent.ID, agent.Name, agent.Hostname, agent.IP, agent.OPNsenseVersion, agent.RegisteredAt.UTC())
|
||||
opnsense_version=EXCLUDED.opnsense_version,
|
||||
agent_version=EXCLUDED.agent_version
|
||||
`, agent.ID, agent.Name, agent.Hostname, agent.IP, agent.OPNsenseVersion, agent.AgentVersion, agent.RegisteredAt.UTC())
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -342,7 +365,7 @@ func (d *Database) writeMetrics(tx *sql.Tx, agentID string, ts time.Time, data *
|
||||
}
|
||||
|
||||
func (d *Database) GetAgents() ([]models.Agent, error) {
|
||||
rows, err := d.db.Query("SELECT id, name, hostname, ip, opnsense_version, registered_at, last_heartbeat, customer_id FROM agents ORDER BY name")
|
||||
rows, err := d.db.Query("SELECT id, name, hostname, ip, opnsense_version, agent_version, registered_at, last_heartbeat, customer_id, update_requested FROM agents ORDER BY name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -354,7 +377,7 @@ func (d *Database) GetAgents() ([]models.Agent, error) {
|
||||
var lastHB sql.NullTime
|
||||
var custID sql.NullInt64
|
||||
|
||||
if err := rows.Scan(&a.ID, &a.Name, &a.Hostname, &a.IP, &a.OPNsenseVersion, &a.RegisteredAt, &lastHB, &custID); err != nil {
|
||||
if err := rows.Scan(&a.ID, &a.Name, &a.Hostname, &a.IP, &a.OPNsenseVersion, &a.AgentVersion, &a.RegisteredAt, &lastHB, &custID, &a.UpdateRequested); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lastHB.Valid {
|
||||
@@ -399,9 +422,9 @@ func (d *Database) GetAgent(id string) (*models.Agent, *models.SystemData, error
|
||||
var custID sql.NullInt64
|
||||
|
||||
err := d.db.QueryRow(
|
||||
"SELECT id, name, hostname, ip, opnsense_version, registered_at, last_heartbeat, customer_id FROM agents WHERE id = $1",
|
||||
"SELECT id, name, hostname, ip, opnsense_version, agent_version, registered_at, last_heartbeat, customer_id, update_requested FROM agents WHERE id = $1",
|
||||
id,
|
||||
).Scan(&a.ID, &a.Name, &a.Hostname, &a.IP, &a.OPNsenseVersion, &a.RegisteredAt, &lastHB, &custID)
|
||||
).Scan(&a.ID, &a.Name, &a.Hostname, &a.IP, &a.OPNsenseVersion, &a.AgentVersion, &a.RegisteredAt, &lastHB, &custID, &a.UpdateRequested)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil, nil
|
||||
@@ -452,6 +475,96 @@ func (d *Database) GetSystemData(agentID string) (*models.SystemData, error) {
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (d *Database) UpdateAgentVersion(id, version string) error {
|
||||
_, err := d.db.Exec("UPDATE agents SET agent_version = $1 WHERE id = $2", version, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update-Request Flag setzen/lesen/loeschen
|
||||
func (d *Database) SetUpdateRequest(id string, requested bool) error {
|
||||
_, err := d.db.Exec("UPDATE agents SET update_requested = $1 WHERE id = $2", requested, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) IsUpdateRequested(id string) bool {
|
||||
var requested bool
|
||||
err := d.db.QueryRow("SELECT update_requested FROM agents WHERE id = $1", id).Scan(&requested)
|
||||
return err == nil && requested
|
||||
}
|
||||
|
||||
func (d *Database) ClearUpdateRequest(id string) error {
|
||||
return d.SetUpdateRequest(id, false)
|
||||
}
|
||||
|
||||
// Alle Agents mit Update-Flag setzen
|
||||
func (d *Database) SetUpdateRequestAll(requested bool) (int64, error) {
|
||||
res, err := d.db.Exec("UPDATE agents SET update_requested = $1", requested)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return res.RowsAffected()
|
||||
}
|
||||
|
||||
// Agent-Firmware speichern (pro Plattform)
|
||||
func (d *Database) SaveAgentFirmware(version, platform, hash string, binary []byte) error {
|
||||
_, err := d.db.Exec(`
|
||||
INSERT INTO agent_firmware (version, platform, hash, binary_data, uploaded_at)
|
||||
VALUES ($1, $2, $3, $4, NOW())
|
||||
ON CONFLICT(version, platform) DO UPDATE SET hash=EXCLUDED.hash, binary_data=EXCLUDED.binary_data, uploaded_at=NOW()
|
||||
`, version, platform, hash, binary)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Database) GetLatestFirmware(platform string) (string, string, []byte, error) {
|
||||
var version, hash string
|
||||
var binary []byte
|
||||
err := d.db.QueryRow("SELECT version, hash, binary_data FROM agent_firmware WHERE platform = $1 ORDER BY uploaded_at DESC LIMIT 1", platform).Scan(&version, &hash, &binary)
|
||||
if err != nil {
|
||||
return "", "", nil, err
|
||||
}
|
||||
return version, hash, binary, nil
|
||||
}
|
||||
|
||||
func (d *Database) GetFirmwareInfo(platform string) (string, string, int, error) {
|
||||
var version, hash string
|
||||
var size int
|
||||
err := d.db.QueryRow("SELECT version, hash, length(binary_data) FROM agent_firmware WHERE platform = $1 ORDER BY uploaded_at DESC LIMIT 1", platform).Scan(&version, &hash, &size)
|
||||
if err != nil {
|
||||
return "", "", 0, err
|
||||
}
|
||||
return version, hash, size, nil
|
||||
}
|
||||
|
||||
// Alle Plattform-Firmwares auflisten
|
||||
func (d *Database) GetAllFirmwareInfo() ([]map[string]interface{}, error) {
|
||||
rows, err := d.db.Query(`
|
||||
SELECT DISTINCT ON (platform) platform, version, hash, length(binary_data) as size, uploaded_at
|
||||
FROM agent_firmware ORDER BY platform, uploaded_at DESC
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var results []map[string]interface{}
|
||||
for rows.Next() {
|
||||
var platform, version, hash string
|
||||
var size int
|
||||
var uploadedAt time.Time
|
||||
if err := rows.Scan(&platform, &version, &hash, &size, &uploadedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, map[string]interface{}{
|
||||
"platform": platform,
|
||||
"version": version,
|
||||
"hash": hash,
|
||||
"size": size,
|
||||
"uploaded_at": uploadedAt,
|
||||
})
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (d *Database) DeleteAgent(id string) (bool, error) {
|
||||
res, err := d.db.Exec("DELETE FROM agents WHERE id = $1", id)
|
||||
if err != nil {
|
||||
|
||||
@@ -9,9 +9,11 @@ type Agent struct {
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
OPNsenseVersion string `json:"opnsense_version"`
|
||||
AgentVersion string `json:"agent_version,omitempty"`
|
||||
RegisteredAt time.Time `json:"registered_at"`
|
||||
LastHeartbeat *time.Time `json:"last_heartbeat,omitempty"`
|
||||
CustomerID *int `json:"customer_id,omitempty"`
|
||||
UpdateRequested bool `json:"update_requested"`
|
||||
}
|
||||
|
||||
// AgentResponse erweitert Agent um den berechneten Status
|
||||
@@ -52,6 +54,7 @@ type RegisterRequest struct {
|
||||
Hostname string `json:"hostname"`
|
||||
IP string `json:"ip"`
|
||||
OPNsenseVersion string `json:"opnsense_version"`
|
||||
AgentVersion string `json:"agent_version,omitempty"`
|
||||
}
|
||||
|
||||
// RegisterResponse
|
||||
|
||||
@@ -172,11 +172,15 @@ type CronJob struct {
|
||||
|
||||
// HeartbeatRequest
|
||||
type HeartbeatRequest struct {
|
||||
AgentID string `json:"agent_id"`
|
||||
SystemData SystemData `json:"system_data"`
|
||||
AgentID string `json:"agent_id"`
|
||||
AgentVersion string `json:"agent_version,omitempty"`
|
||||
SystemData SystemData `json:"system_data"`
|
||||
}
|
||||
|
||||
// HeartbeatResponse
|
||||
type HeartbeatResponse struct {
|
||||
Message string `json:"message"`
|
||||
Message string `json:"message"`
|
||||
UpdateAvailable bool `json:"update_available,omitempty"`
|
||||
UpdateVersion string `json:"update_version,omitempty"`
|
||||
UpdateHash string `json:"update_hash,omitempty"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user