mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-11 14:00:29 +00:00
f356994869
Planning, approval decisions, and execution for typed resource actions move out of the HTTP handlers in internal/api/actions.go into a new internal/actionlifecycle.Service owned by api-contracts. The REST handlers become thin decode/actor/error-mapping adapters over the one shared service, and ResourceHandlers.ActionLifecycle() exposes the same service for in-process consumers, so a future Patrol action broker inherits identical resource lookup, availability checks, plan hashing, audit persistence, remediation locks, plan-drift revalidation, execution, and terminal publication instead of loopback HTTP or a parallel lifecycle. Behavior is preserved: same status codes, error codes, and audit/ lifecycle persistence ordering, backed by the existing api contract tests plus new fail-closed proofs for the service itself (unknown resource/capability, availability refusal, unapproved execution, remediation lock, plan drift, missing executor, missing store). Contract text in api-contracts, agent-lifecycle, and storage-recovery now names the service alongside actions.go and planner.go; the subsystem registry owns internal/actionlifecycle/ under api-contracts with a dedicated path policy; the code-standards and contract source pins follow the moved invariants; and the subsystem_lookup line-number pin shifts with the api-contracts canonical-files list insertion. This is the first slice of making the typed action lifecycle the only autonomous execution route for Patrol, Assistant, and MCP.
84 lines
2.4 KiB
Go
84 lines
2.4 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func main() {
|
|
listen := flag.String("listen", "127.0.0.1:17655", "HTTP listen address")
|
|
agentBinary := flag.String("agent-binary", "", "Windows agent binary to serve")
|
|
version := flag.String("version", "", "Version returned by /api/version")
|
|
flag.Parse()
|
|
|
|
if strings.TrimSpace(*agentBinary) == "" || strings.TrimSpace(*version) == "" {
|
|
log.Fatal("--agent-binary and --version are required")
|
|
}
|
|
|
|
binaryPath, err := filepath.Abs(*agentBinary)
|
|
if err != nil {
|
|
log.Fatalf("resolve agent binary: %v", err)
|
|
}
|
|
binary, err := os.ReadFile(binaryPath)
|
|
if err != nil {
|
|
log.Fatalf("read agent binary: %v", err)
|
|
}
|
|
digest := sha256.Sum256(binary)
|
|
checksum := hex.EncodeToString(digest[:])
|
|
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/version", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"version": *version,
|
|
"agentUpdateTargetVersion": *version,
|
|
"channel": "stable",
|
|
})
|
|
})
|
|
mux.HandleFunc("/download/pulse-agent", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Query().Get("arch") != "windows-amd64" {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(binary)))
|
|
w.Header().Set("X-Checksum-Sha256", checksum)
|
|
w.Header().Set("Cache-Control", "no-store")
|
|
if r.Method == http.MethodHead {
|
|
w.WriteHeader(http.StatusOK)
|
|
return
|
|
}
|
|
if r.Method != http.MethodGet {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
_, _ = w.Write(binary)
|
|
})
|
|
mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"success":true}`))
|
|
})
|
|
|
|
server := &http.Server{
|
|
Addr: *listen,
|
|
Handler: mux,
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 5 * time.Minute,
|
|
IdleTimeout: 30 * time.Second,
|
|
}
|
|
log.Printf("Windows lifecycle server listening on %s with %s (%s)", *listen, filepath.Base(binaryPath), *version)
|
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatal(err)
|
|
}
|
|
}
|