Mining/pkg/mining/ttminer.go
Claude 1f9624279a
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
ax(batch): rename abbreviated variables to predictable names
resp→response, db→database pattern across mining, logging packages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 04:47:58 +01:00

189 lines
5.2 KiB
Go

package mining
import (
"bytes"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
)
// miner := &TTMiner{}; miner.Start(config)
type TTMiner struct {
BaseMiner
FullStats *TTMinerSummary `json:"-"` // Excluded from JSON to prevent race during marshaling
}
// summary, err := FetchJSONStats(ctx, HTTPStatsConfig{Host: "127.0.0.1", Port: 4068, Endpoint: "/summary"}, &summary)
type TTMinerSummary struct {
Name string `json:"name"`
Version string `json:"version"`
Uptime int `json:"uptime"`
Algo string `json:"algo"`
GPUs []struct {
Name string `json:"name"`
ID int `json:"id"`
Hashrate float64 `json:"hashrate"`
Temp int `json:"temp"`
Fan int `json:"fan"`
Power int `json:"power"`
Accepted int `json:"accepted"`
Rejected int `json:"rejected"`
Intensity float64 `json:"intensity"`
} `json:"gpus"`
Results struct {
SharesGood int `json:"shares_good"`
SharesTotal int `json:"shares_total"`
AvgTime int `json:"avg_time"`
} `json:"results"`
Connection struct {
Pool string `json:"pool"`
Ping int `json:"ping"`
Diff int `json:"diff"`
} `json:"connection"`
Hashrate struct {
Total []float64 `json:"total"`
Highest float64 `json:"highest"`
} `json:"hashrate"`
}
// config := Config{MinerType: MinerTypeTTMiner, Pool: "stratum+tcp://pool.lthn.io:3333"}
const MinerTypeTTMiner = "tt-miner"
// miner := mining.NewTTMiner()
// miner.Start(ctx)
func NewTTMiner() *TTMiner {
return &TTMiner{
BaseMiner: BaseMiner{
Name: "tt-miner",
MinerType: MinerTypeTTMiner,
ExecutableName: "TT-Miner",
Version: "latest",
URL: "https://github.com/TrailingStop/TT-Miner-release",
API: &API{
Enabled: true,
ListenHost: "127.0.0.1",
ListenPort: 4068, // TT-Miner default port
},
HashrateHistory: make([]HashratePoint, 0),
LowResHashrateHistory: make([]HashratePoint, 0),
LastLowResAggregation: time.Now(),
LogBuffer: NewLogBuffer(500), // Keep last 500 lines
},
}
}
// configPath, err := getTTMinerConfigPath() // "~/.config/lethean-desktop/tt-miner.json"
func getTTMinerConfigPath() (string, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return "", err
}
return filepath.Join(homeDir, ".config", "lethean-desktop", "tt-miner.json"), nil
}
// version, err := ttminer.GetLatestVersion()
// // version == "v2024.1.26"
func (m *TTMiner) GetLatestVersion() (string, error) {
return FetchLatestGitHubVersion("TrailingStop", "TT-Miner-release")
}
// err := ttminer.Install()
// // downloads and extracts TT-Miner to XDG data dir, verifies binary
func (m *TTMiner) Install() error {
version, err := m.GetLatestVersion()
if err != nil {
return err
}
m.Version = version
baseURL := "https://github.com/TrailingStop/TT-Miner-release/releases/download/" + version + "/TT-Miner-" + version
var url string
switch runtime.GOOS {
case "windows":
url = baseURL + ".zip"
case "linux":
url = baseURL + ".tar.gz"
default:
return ErrUnsupportedMiner(runtime.GOOS).WithDetails("TT-Miner requires CUDA; only Windows and Linux are supported")
}
if err := m.InstallFromURL(url); err != nil {
return err
}
// After installation, verify it.
_, err = m.CheckInstallation()
if err != nil {
return ErrInstallFailed("tt-miner").WithDetails("verification after extraction failed").WithCause(err)
}
return nil
}
// err := ttminer.Uninstall()
// // removes tt-miner binary, XDG data dir, and ~/.config/lethean-desktop/tt-miner.json
func (m *TTMiner) Uninstall() error {
// Remove the specific tt-miner config file
configPath, err := getTTMinerConfigPath()
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 := ttminer.CheckInstallation()
// if err != nil { return nil, err }
// logging.Info("installed", logging.Fields{"version": details.Version, "binary": details.MinerBinary})
func (m *TTMiner) CheckInstallation() (*InstallationDetails, error) {
binaryPath, err := m.findMinerBinary()
if err != nil {
return &InstallationDetails{IsInstalled: false}, err
}
// Run version command before acquiring lock (I/O operation)
cmd := exec.Command(binaryPath, "--version")
var commandOutput bytes.Buffer
cmd.Stdout = &commandOutput
var version string
if err := cmd.Run(); err != nil {
version = "Unknown (could not run executable)"
} else {
// Parse version from output
output := strings.TrimSpace(commandOutput.String())
fields := strings.Fields(output)
if len(fields) >= 2 {
version = fields[1]
} else if len(fields) >= 1 {
version = fields[0]
} else {
version = "Unknown (could not parse version)"
}
}
// Get the config path using the helper
configPath, err := getTTMinerConfigPath()
if err != nil {
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
}