a0c0fb114f
CI / Lint + build + test (push) Has been cancelled
vetting-agent gains a `host` subcommand that runs as a systemd service
installed by the quick-register one-liner, POSTing every 30s to
/api/v1/hosts/{mac}/heartbeat so the dashboard tile shows "online" or
"Nm ago" without waiting on WoL. Ships dormant client code for the
Phase 2 reboot_for_vetting command so the server can flip it on later
without a binary redeploy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
82 lines
2.6 KiB
Go
82 lines
2.6 KiB
Go
// Package httpserver assembles the chi router. It lives in its own
|
|
// package because it depends on both `api` and `orchestrator`, and
|
|
// those two packages must stay import-independent.
|
|
package httpserver
|
|
|
|
import (
|
|
"io/fs"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
|
|
"vetting/internal/api"
|
|
"vetting/internal/web"
|
|
)
|
|
|
|
type Deps struct {
|
|
UI *api.UI
|
|
Agent *api.Agent
|
|
LiveDir string // directory containing vmlinuz + initrd.img; "" disables /live
|
|
AgentAssetDir string // directory containing vetting-agent-linux-amd64; "" disables /assets
|
|
}
|
|
|
|
func NewRouter(d Deps) http.Handler {
|
|
r := chi.NewRouter()
|
|
r.Use(middleware.RealIP)
|
|
r.Use(middleware.Recoverer)
|
|
r.Use(middleware.Logger)
|
|
|
|
staticFS, err := fs.Sub(web.Static, "static")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
|
|
|
|
if d.LiveDir != "" {
|
|
r.Handle("/live/*", http.StripPrefix("/live/", http.FileServer(http.Dir(d.LiveDir))))
|
|
}
|
|
|
|
// Host-mode agent binary is served here so the quick-register
|
|
// one-liner can curl it without the operator pre-staging anything.
|
|
if d.AgentAssetDir != "" {
|
|
r.Handle("/assets/*", http.StripPrefix("/assets/", http.FileServer(http.Dir(d.AgentAssetDir))))
|
|
}
|
|
|
|
// Agent / PXE endpoints — authenticated per-request by bearer token
|
|
// or by the unforgeable MAC path parameter.
|
|
r.Get("/ipxe/{mac}", d.Agent.IPXEScript)
|
|
r.Route("/api/v1/runs/{id}", func(r chi.Router) {
|
|
r.Post("/hello", d.Agent.Hello)
|
|
r.Post("/claim", d.Agent.Claim)
|
|
r.Post("/heartbeat", d.Agent.Heartbeat)
|
|
r.Post("/log", d.Agent.Log)
|
|
r.Post("/result", d.Agent.Result)
|
|
r.Post("/hold", d.Agent.Hold)
|
|
r.Post("/sensor", d.Agent.Sensor)
|
|
})
|
|
|
|
// Quick-register: the bash one-liner fetched from /register/quick.sh
|
|
// POSTs here from the target host. LAN-trusted, same threat model
|
|
// as the browser UI.
|
|
r.Post("/api/v1/hosts", d.UI.CreateHostJSON)
|
|
|
|
// Host-mode agent heartbeat. Keyed by MAC (no bearer token), same
|
|
// LAN-trust model as /api/v1/hosts.
|
|
r.Post("/api/v1/hosts/{mac}/heartbeat", d.UI.Heartbeat)
|
|
|
|
// Browser UI — no auth; bind to loopback or LAN only, or front
|
|
// with a reverse proxy if you need a password.
|
|
r.Get("/", d.UI.Dashboard)
|
|
r.Get("/hosts/new", d.UI.NewHostForm)
|
|
r.Post("/hosts", d.UI.CreateHost)
|
|
r.Post("/hosts/{id}/delete", d.UI.DeleteHost)
|
|
r.Post("/hosts/{id}/start", d.UI.StartRun)
|
|
r.Post("/hosts/{id}/override-wipe", d.UI.OverrideWipeStorage)
|
|
r.Get("/reports/{runID}", d.UI.Report)
|
|
r.Get("/register/quick.sh", d.UI.QuickRegisterScript)
|
|
r.Get("/events", d.UI.SSE)
|
|
|
|
return r
|
|
}
|