Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
Some checks failed
Test / test (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
This commit is contained in:
101
internal/api/handlers/fleet.go
Normal file
101
internal/api/handlers/fleet.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"forge-mesh/internal/alerts"
|
||||
"forge-mesh/internal/auth"
|
||||
"forge-mesh/internal/config"
|
||||
"forge-mesh/internal/fleet"
|
||||
)
|
||||
|
||||
// FleetHandler serves fleet REST endpoints.
|
||||
type FleetHandler struct {
|
||||
Store *fleet.Store
|
||||
Hub *fleet.Hub
|
||||
Alerts *alerts.Notifier
|
||||
Cfg *config.Config
|
||||
OperatorClearance int
|
||||
}
|
||||
|
||||
func (h *FleetHandler) List(w http.ResponseWriter, r *http.Request) {
|
||||
summary, err := h.Store.BuildFleetSummary()
|
||||
if err != nil {
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusOK, summary)
|
||||
}
|
||||
|
||||
type registerRequest struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Fingerprint string `json:"fingerprint"`
|
||||
Arch string `json:"arch"`
|
||||
}
|
||||
|
||||
func (h *FleetHandler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
var req registerRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if req.Hostname == "" {
|
||||
req.Hostname = "enrolled-host"
|
||||
}
|
||||
|
||||
host, err := h.Store.TouchHost(req.Hostname, req.Fingerprint, req.Arch)
|
||||
if err != nil {
|
||||
http.Error(w, "register failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
auth.JSON(w, http.StatusOK, map[string]any{
|
||||
"ok": true,
|
||||
"host_id": host.ID,
|
||||
"host": fleet.ToFleetCard(host),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *FleetHandler) Command(w http.ResponseWriter, r *http.Request) {
|
||||
hostID := r.PathValue("id")
|
||||
if hostID == "" {
|
||||
http.Error(w, "host id required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Action string `json:"action"`
|
||||
Args map[string]any `json:"args"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
action := strings.ToLower(req.Action)
|
||||
if err := fleet.CheckAction(h.OperatorClearance, action); err != nil {
|
||||
auth.JSON(w, http.StatusForbidden, map[string]any{
|
||||
"error": err.Error(),
|
||||
"required_clearance": fleet.RequiredClearanceOrZero(action),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cmd, err := h.Hub.DispatchCommand(hostID, action, req.Args)
|
||||
if err != nil {
|
||||
http.Error(w, "dispatch failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if h.Alerts != nil && h.Alerts.Enabled() {
|
||||
h.Alerts.Send(alerts.FleetEvent(action, hostID, fmt.Sprintf("cmd: `%s`", cmd.ID)))
|
||||
}
|
||||
|
||||
auth.JSON(w, http.StatusOK, map[string]any{
|
||||
"ok": true,
|
||||
"command": cmd,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user