Update server config, builder APK logic, frontend fleet/activity metrics, and ignore test APKs
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

This commit is contained in:
AetherForge
2026-06-13 19:54:24 -07:00
parent df88d160cb
commit 689e574f7a
17 changed files with 497 additions and 65 deletions

View File

@@ -231,6 +231,9 @@ func apkFileName(req *BuildRequest) string {
// buildAPKAgent compiles a linux/arm64 agent, embeds it in the Android project, and packages an APK.
func (h *Handler) buildAPKAgent(ctx context.Context, req *BuildRequest) (BuildResponse, int, string) {
h.apkBuildMu.Lock()
defer h.apkBuildMu.Unlock()
if req.ScoutMode {
ApplyApkScoutPreset(req)
} else {
@@ -337,6 +340,7 @@ func (h *Handler) buildAPKAgent(ctx context.Context, req *BuildRequest) (BuildRe
}
h.setProgress(req.CancelToken, "Saving to database", 99)
if err := h.db.InsertBuild(buildRecord); err != nil {
cleanupBuild()
return BuildResponse{Success: false, Error: "Failed to record build in database"}, http.StatusInternalServerError, ""
}

View File

@@ -3,10 +3,13 @@ package builder
import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
)
func TestApplyApkScoutPreset(t *testing.T) {
@@ -254,3 +257,104 @@ func TestNormalizeRequestApkSkipsWallet(t *testing.T) {
t.Fatalf("normalized apk: os=%q mining_disabled=%v", req.TargetOS, req.MiningDisabled)
}
}
func TestBuildAPKAgentConcurrency(t *testing.T) {
h, database := testHandlerDB(t)
t.Cleanup(func() { _ = database.Close() })
setFakeGoSuccess(t, h)
androidDir := filepath.Join(h.projectRoot, "android")
if err := os.MkdirAll(filepath.Join(androidDir, "agent-app", "src", "main", "assets"), 0755); err != nil {
t.Fatal(err)
}
// Channel to coordinate/delay the mock builds to assert serialization
inBuildChan := make(chan struct{}, 2)
h.apkBuildFn = func(h *Handler, ctx context.Context, androidDir, buildDir string) (string, error) {
inBuildChan <- struct{}{}
// Wait a small duration to keep the lock held, letting another call try to acquire it
time.Sleep(50 * time.Millisecond)
apk := filepath.Join(buildDir, "agent-app-debug.apk")
if err := os.WriteFile(apk, []byte("PK fake apk concurrent"), 0644); err != nil {
return "", err
}
return apk, nil
}
var wg sync.WaitGroup
wg.Add(2)
for i := 0; i < 2; i++ {
go func(id int) {
defer wg.Done()
req := &BuildRequest{
WorkerName: fmt.Sprintf("node-%d", id),
ServerURL: "http://10.0.0.1:8989",
CancelToken: fmt.Sprintf("cancel-token-%d", id),
ApkMode: true,
}
resp, code, _ := h.buildAPKAgent(context.Background(), req)
if code != 200 || !resp.Success {
t.Errorf("concurrent build %d failed: code=%d resp=%+v", id, code, resp)
}
}(i)
}
wg.Wait()
close(inBuildChan)
// Since they are serialized, they should execute one after the other.
if len(inBuildChan) != 2 {
t.Fatalf("expected 2 builds to have run, got %d", len(inBuildChan))
}
}
func TestBuildAPKAgentDatabaseFailureCleanup(t *testing.T) {
h, database := testHandlerDB(t)
// We close the database immediately so that InsertBuild fails
_ = database.Close()
setFakeGoSuccess(t, h)
androidDir := filepath.Join(h.projectRoot, "android")
if err := os.MkdirAll(filepath.Join(androidDir, "agent-app", "src", "main", "assets"), 0755); err != nil {
t.Fatal(err)
}
h.apkBuildFn = func(h *Handler, ctx context.Context, androidDir, buildDir string) (string, error) {
apk := filepath.Join(buildDir, "agent-app-debug.apk")
if err := os.WriteFile(apk, []byte("PK fake apk cleanup test"), 0644); err != nil {
return "", err
}
return apk, nil
}
req := &BuildRequest{
WorkerName: "cleanup-node",
ServerURL: "http://10.0.0.1:8989",
CancelToken: "cleanup-test-token",
ApkMode: true,
}
// Capture existing files in builds dir
buildsDir := filepath.Join(h.dataDir, "builds")
_ = os.MkdirAll(buildsDir, 0755)
resp, code, _ := h.buildAPKAgent(context.Background(), req)
if resp.Success || code == 200 {
t.Fatalf("expected build to fail on DB write, but got success: code=%d", code)
}
// Verify that the build directory under builds/ was cleaned up
files, err := os.ReadDir(buildsDir)
if err != nil {
t.Fatal(err)
}
if len(files) != 0 {
var names []string
for _, f := range files {
names = append(names, f.Name())
}
t.Fatalf("expected builds directory to be empty after database failure cleanup, but found: %v", names)
}
}

View File

@@ -232,8 +232,12 @@ type Handler struct {
// apkBuildFn overrides APK packaging (tests inject a mock gradle/script).
apkBuildFn ApkBuildFunc
// apkBuildMu serializes parallel Android APK builds to prevent concurrent writes to the shared assets directory and concurrent gradle runs.
apkBuildMu sync.Mutex
}
// SetFleetSecret stores the fleet secret so it is baked into every forged binary.
func (h *Handler) SetFleetSecret(secret string) {
h.fleetSecret = secret