resp→response, db→database pattern across mining, logging packages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
189 lines
5.2 KiB
Go
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
|
|
}
|