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:
136
internal/api/handlers/crucible.go
Normal file
136
internal/api/handlers/crucible.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"forge-mesh/internal/alerts"
|
||||
"forge-mesh/internal/auth"
|
||||
"forge-mesh/internal/fleet"
|
||||
)
|
||||
|
||||
// CrucibleHandler serves batch terminal endpoints.
|
||||
type CrucibleHandler struct {
|
||||
Store *fleet.Store
|
||||
Hub *fleet.Hub
|
||||
Crucible *fleet.CrucibleStore
|
||||
Alerts *alerts.Notifier
|
||||
OperatorClearance int
|
||||
}
|
||||
|
||||
type batchRequest struct {
|
||||
Command string `json:"command"`
|
||||
HostIDs []string `json:"host_ids"`
|
||||
All bool `json:"all"`
|
||||
}
|
||||
|
||||
func (h *CrucibleHandler) Dispatch(w http.ResponseWriter, r *http.Request) {
|
||||
if err := fleet.CheckAction(h.OperatorClearance, fleet.ActionCrucible); err != nil {
|
||||
auth.JSON(w, http.StatusForbidden, map[string]any{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var req batchRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
req.Command = strings.TrimSpace(req.Command)
|
||||
if req.Command == "" {
|
||||
http.Error(w, "command required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
hostIDs := req.HostIDs
|
||||
if req.All || len(hostIDs) == 0 {
|
||||
hosts, err := h.Store.ListHosts()
|
||||
if err != nil {
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
for _, host := range hosts {
|
||||
if host.Status == "online" || host.Status == "mining" {
|
||||
hostIDs = append(hostIDs, host.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
job := h.Crucible.Create(req.Command, hostIDs)
|
||||
action := mapCommandToAction(req.Command)
|
||||
|
||||
for _, hostID := range hostIDs {
|
||||
host, _ := h.Store.GetHost(hostID)
|
||||
hostname := hostID
|
||||
if host != nil {
|
||||
hostname = host.Hostname
|
||||
}
|
||||
|
||||
if err := fleet.CheckAction(h.OperatorClearance, action); err != nil {
|
||||
h.Crucible.AddResult(job.ID, fleet.BatchResult{
|
||||
HostID: hostID, Hostname: hostname,
|
||||
Status: "denied", Message: err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, err := h.Hub.DispatchCommand(hostID, action, map[string]any{"raw": req.Command})
|
||||
if err != nil {
|
||||
h.Crucible.AddResult(job.ID, fleet.BatchResult{
|
||||
HostID: hostID, Hostname: hostname,
|
||||
Status: "error", Message: err.Error(),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
h.Crucible.AddResult(job.ID, fleet.BatchResult{
|
||||
HostID: hostID, Hostname: hostname,
|
||||
Status: "dispatched", CommandID: cmd.ID,
|
||||
})
|
||||
}
|
||||
|
||||
h.Crucible.Complete(job.ID, "completed")
|
||||
|
||||
if h.Alerts != nil && h.Alerts.Enabled() {
|
||||
h.Alerts.Send(alerts.CrucibleEvent(job.ID, len(hostIDs), req.Command))
|
||||
}
|
||||
|
||||
_ = h.Store.InsertSeerEvent("", "crucible", fmt.Sprintf(`{"job_id":"%s","command":%q}`, job.ID, req.Command))
|
||||
|
||||
auth.JSON(w, http.StatusOK, job)
|
||||
}
|
||||
|
||||
func (h *CrucibleHandler) GetJob(w http.ResponseWriter, r *http.Request) {
|
||||
id := r.PathValue("id")
|
||||
job, ok := h.Crucible.Get(id)
|
||||
if !ok {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
auth.JSON(w, http.StatusOK, job)
|
||||
}
|
||||
|
||||
func (h *CrucibleHandler) History(w http.ResponseWriter, r *http.Request) {
|
||||
auth.JSON(w, http.StatusOK, map[string]any{
|
||||
"jobs": h.Crucible.History(20),
|
||||
})
|
||||
}
|
||||
|
||||
func mapCommandToAction(cmd string) string {
|
||||
lower := strings.ToLower(strings.TrimSpace(cmd))
|
||||
switch {
|
||||
case strings.HasPrefix(lower, "pause"):
|
||||
return fleet.ActionPause
|
||||
case strings.HasPrefix(lower, "resume"):
|
||||
return fleet.ActionResume
|
||||
case strings.HasPrefix(lower, "reboot"):
|
||||
return fleet.ActionReboot
|
||||
case strings.HasPrefix(lower, "screenshot"):
|
||||
return fleet.ActionScreenshot
|
||||
case strings.HasPrefix(lower, "shell"):
|
||||
return fleet.ActionShell
|
||||
default:
|
||||
return fleet.ActionStatus
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user