Mining/pkg/mining/xmrig.go
Virgil 9ed6c33c42
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
ax(mining): standardize command and repository names
Co-authored-by: Virgil <virgil@lethean.io>
2026-04-04 07:08:47 +00:00

201 lines
5.3 KiB
Go

package mining
import (
"bytes"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/adrg/xdg"
)
// miner := &XMRigMiner{}
// miner.Name = "xmrig"
// miner.BinaryPath = "/usr/local/bin/xmrig"
type XMRigMiner struct {
BaseMiner
FullStats *XMRigSummary `json:"-"` // Excluded from JSON to prevent race during marshaling
}
var (
httpClient = &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
}
httpClientMutex sync.RWMutex
)
// client := getHTTPClient()
// response, err := client.Get(statsURL)
func getHTTPClient() *http.Client {
httpClientMutex.RLock()
defer httpClientMutex.RUnlock()
return httpClient
}
// setHTTPClient(httptest.NewServer(handler).Client())
func setHTTPClient(client *http.Client) {
httpClientMutex.Lock()
defer httpClientMutex.Unlock()
httpClient = client
}
// MinerTypeXMRig = "xmrig" — used as Config.MinerType to identify XMRig instances
const MinerTypeXMRig = "xmrig"
// miner := NewXMRigMiner()
// miner.Start(ctx)
func NewXMRigMiner() *XMRigMiner {
return &XMRigMiner{
BaseMiner: BaseMiner{
Name: "miner",
MinerType: MinerTypeXMRig,
ExecutableName: "miner",
Version: "latest",
URL: "", // Local build only - no remote download
API: &API{
Enabled: true,
ListenHost: "127.0.0.1",
},
HashrateHistory: make([]HashratePoint, 0),
LowResHashrateHistory: make([]HashratePoint, 0),
LastLowResAggregation: time.Now(),
LogBuffer: NewLogBuffer(500), // Keep last 500 lines
},
}
}
// path, err := getXMRigConfigPath("") // "~/.config/lethean-desktop/xmrig.json"
// path, err := getXMRigConfigPath("xmrig-78") // "~/.config/lethean-desktop/xmrig-78.json"
var getXMRigConfigPath = func(instanceName string) (string, error) {
configFileName := "xmrig.json"
if instanceName != "" && instanceName != "xmrig" {
// Use an instance-specific config file such as `xmrig-78.json`.
configFileName = instanceName + ".json"
}
path, err := xdg.ConfigFile("lethean-desktop/" + configFileName)
if err != nil {
// Fallback for non-XDG environments or when XDG variables are not set
homeDir, homeErr := os.UserHomeDir()
if homeErr != nil {
return "", homeErr
}
return filepath.Join(homeDir, ".config", "lethean-desktop", configFileName), nil
}
return path, nil
}
// version, err := miner.GetLatestVersion() // => "v6.22.2", nil
func (m *XMRigMiner) GetLatestVersion() (string, error) {
return FetchLatestGitHubVersion("xmrig", "xmrig")
}
// if err := miner.Install(); err != nil { return err }
func (m *XMRigMiner) Install() error {
version, err := m.GetLatestVersion()
if err != nil {
return err
}
m.Version = version
trimmedVersion := strings.TrimPrefix(version, "v")
baseURL := "https://github.com/xmrig/xmrig/releases/download/" + version + "/xmrig-" + trimmedVersion
var url string
switch runtime.GOOS {
case "windows":
url = baseURL + "-windows-x64.zip"
case "linux":
url = baseURL + "-linux-static-x64.tar.gz"
case "darwin":
url = baseURL + "-macos-x64.tar.gz"
default:
return ErrUnsupportedMiner(runtime.GOOS)
}
if err := m.InstallFromURL(url); err != nil {
return err
}
// After installation, verify it.
_, err = m.CheckInstallation()
if err != nil {
return ErrInstallFailed("xmrig").WithDetails("verification after extraction failed").WithCause(err)
}
return nil
}
// if err := miner.Uninstall(); err != nil { return err }
func (m *XMRigMiner) Uninstall() error {
// Remove the instance-specific config file
configPath, err := getXMRigConfigPath(m.Name)
if err == nil {
os.Remove(configPath) // Ignore error if it doesn't exist
}
// Call the base uninstall method to remove the installation directory
return m.BaseMiner.Uninstall()
}
// details, err := xmrig.CheckInstallation()
// if err != nil { return nil, err }
// logging.Info("installed", logging.Fields{"version": details.Version, "binary": details.MinerBinary})
func (m *XMRigMiner) CheckInstallation() (*InstallationDetails, error) {
binaryPath, err := m.findMinerBinary()
if err != nil {
return &InstallationDetails{IsInstalled: false}, err
}
// Run version command before acquiring lock (I/O operation)
command := exec.Command(binaryPath, "--version")
var output bytes.Buffer
command.Stdout = &output
var version string
if err := command.Run(); err != nil {
version = "Unknown (could not run executable)"
} else {
fields := strings.Fields(output.String())
if len(fields) >= 2 {
version = fields[1]
} else {
version = "Unknown (could not parse version)"
}
}
// Get the config path using the helper (use instance name if set)
m.mutex.RLock()
instanceName := m.Name
m.mutex.RUnlock()
configPath, err := getXMRigConfigPath(instanceName)
if err != nil {
// Log the error but don't fail CheckInstallation if config path can't be determined
configPath = "Error: Could not determine config path"
}
// Update shared fields under lock
m.mutex.Lock()
m.MinerBinary = binaryPath
m.Path = filepath.Dir(binaryPath)
m.Version = version
m.mutex.Unlock()
return &InstallationDetails{
IsInstalled: true,
MinerBinary: binaryPath,
Path: filepath.Dir(binaryPath),
Version: version,
ConfigPath: configPath,
}, nil
}