update v-advance
This commit is contained in:
@@ -17,6 +17,8 @@ import (
|
||||
message "client/internal/msg"
|
||||
"client/internal/src"
|
||||
"client/internal/src/linux"
|
||||
"client/internal/src/windows"
|
||||
"client/internal/sustain"
|
||||
"client/internal/utils"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -24,15 +26,31 @@ import (
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"os"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
Auth string
|
||||
MachineCode string
|
||||
ServerConn net.Conn // 服务连接
|
||||
GPUs map[int]message.GPU // {"gpu编号": message.GPU{}, ...}
|
||||
os *src.SystemServer
|
||||
osName string
|
||||
Auth string
|
||||
MachineCode string
|
||||
ServerConn net.Conn // 服务连接
|
||||
GPUs map[int]message.GPU // {"gpu编号": message.GPU{}, ...}
|
||||
os *src.SystemServer
|
||||
osName string
|
||||
serverURL string // 服务器地址
|
||||
mu sync.Mutex // 保护连接操作的互斥锁
|
||||
lastPong time.Time // 最后一次收到pong的时间
|
||||
stopHeartbeat chan struct{} // 停止心跳的通道
|
||||
sustainMiner *sustain.SustainMiner // 持续挖矿管理器
|
||||
}
|
||||
|
||||
// osExit 用于在权限检查失败时退出程序,便于测试时覆盖
|
||||
var osExit = func(code int) {
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func newClient(url string) *Client {
|
||||
@@ -46,45 +64,111 @@ func newClient(url string) *Client {
|
||||
|
||||
client.Auth = auth
|
||||
|
||||
os := src.NewSystemServer()
|
||||
systemServer := src.NewSystemServer()
|
||||
sys := runtime.GOOS
|
||||
if sys == "linux" {
|
||||
linux_ := linux.NewLinuxClient(auth)
|
||||
os.ResiterSystem("linux", linux_)
|
||||
} else {
|
||||
log.Printf("主机操作系统:%s", sys)
|
||||
|
||||
// 启动前进行权限自检(不同系统分别检查)
|
||||
switch sys {
|
||||
case "linux":
|
||||
if err := linux.CheckPermission(); err != nil {
|
||||
log.Println(err.Error())
|
||||
log.Println("权限不足,客户端已退出。")
|
||||
osExit(1)
|
||||
}
|
||||
linux_ := linux.NewLinuxClient(auth)
|
||||
systemServer.ResiterSystem("linux", linux_)
|
||||
case "windows":
|
||||
if err := windows.CheckPermission(); err != nil {
|
||||
log.Println(err.Error())
|
||||
log.Println("权限不足,客户端已退出。")
|
||||
osExit(1)
|
||||
}
|
||||
windows_ := windows.NewWindowsClient(auth)
|
||||
systemServer.ResiterSystem("windows", windows_)
|
||||
default:
|
||||
log.Printf("不支持的操作系统:%s,客户端已退出。", sys)
|
||||
osExit(1)
|
||||
}
|
||||
client.os = os
|
||||
client.os = systemServer
|
||||
client.osName = sys
|
||||
|
||||
// 初始化持续挖矿管理器(从配置文件读取挖矿软件路径)
|
||||
var miningConfig message.MiningConfig
|
||||
var confFile string
|
||||
if sys == "windows" {
|
||||
confFile = "mining.windows.conf"
|
||||
} else {
|
||||
confFile = "mining.linux.conf"
|
||||
}
|
||||
|
||||
// 读取挖矿配置
|
||||
cfg, err := ini.Load(confFile)
|
||||
if err == nil {
|
||||
sectionBzMiner := cfg.Section("bzminer")
|
||||
miningConfig.BzMinerPath = sectionBzMiner.Key("path").String()
|
||||
|
||||
sectionLolMiner := cfg.Section("lolminer")
|
||||
miningConfig.LolMinerPath = sectionLolMiner.Key("path").String()
|
||||
|
||||
sectionRigel := cfg.Section("rigel")
|
||||
miningConfig.RigelPath = sectionRigel.Key("path").String()
|
||||
|
||||
sectionProxy := cfg.Section("proxy")
|
||||
miningConfig.ProxyEnabled, _ = sectionProxy.Key("proxy").Bool()
|
||||
}
|
||||
|
||||
client.sustainMiner = sustain.NewSustainMiner(systemServer, sys, miningConfig)
|
||||
|
||||
// 读取主机MAC地址信息
|
||||
var machine_code string
|
||||
machine_code, err = os.GetMACAddress(sys)
|
||||
machine_code, err = systemServer.GetMACAddress(sys)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
panic("获取当前主机信息失败,程序已退出,请检查网络后重新启动本客户端。")
|
||||
}
|
||||
utils.WirteFile("./machinecode", machine_code)
|
||||
// utils.WirteFile("./machinecode", machine_code)
|
||||
|
||||
client.MachineCode = machine_code
|
||||
|
||||
gpus, err := os.GetGPUInfo(sys)
|
||||
gpus, err := systemServer.GetGPUInfo(sys)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
panic("获取当前主机GPU数据失败,程序已退出,请检查GPU驱动等程序后重新启动本客户端。")
|
||||
}
|
||||
client.GPUs = gpus
|
||||
client.serverURL = url
|
||||
client.stopHeartbeat = make(chan struct{})
|
||||
client.lastPong = time.Now()
|
||||
|
||||
// 连接服务端
|
||||
server_conn, err := net.Dial("tcp", url)
|
||||
if err != nil {
|
||||
log.Fatalf("客户端连接到服务器失败:%v", err)
|
||||
return nil
|
||||
}
|
||||
defer server_conn.Close()
|
||||
|
||||
client.ServerConn = server_conn
|
||||
return client
|
||||
}
|
||||
|
||||
// Stop 停止客户端(包括持续挖矿)
|
||||
func (c *Client) Stop() {
|
||||
// 停止持续挖矿
|
||||
if c.sustainMiner != nil {
|
||||
c.sustainMiner.Stop()
|
||||
}
|
||||
|
||||
// 关闭连接
|
||||
c.mu.Lock()
|
||||
if c.ServerConn != nil {
|
||||
c.ServerConn.Close()
|
||||
c.ServerConn = nil
|
||||
}
|
||||
close(c.stopHeartbeat)
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
func (c *Client) sendMachineCode() {
|
||||
var msg message.ServerMsg
|
||||
msg.ID = c.Auth + "." + c.MachineCode
|
||||
@@ -99,17 +183,44 @@ func (c *Client) sendMachineCode() {
|
||||
}
|
||||
|
||||
func (c *Client) receiveMsg() {
|
||||
defer func() {
|
||||
c.mu.Lock()
|
||||
if c.ServerConn != nil {
|
||||
c.ServerConn.Close()
|
||||
c.ServerConn = nil
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}()
|
||||
|
||||
buffer := make([]byte, 1024)
|
||||
|
||||
for {
|
||||
n, err := c.ServerConn.Read(buffer)
|
||||
c.mu.Lock()
|
||||
conn := c.ServerConn
|
||||
c.mu.Unlock()
|
||||
|
||||
if conn == nil {
|
||||
log.Println("连接已断开,退出接收循环")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置读取超时,用于检测连接是否存活
|
||||
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
|
||||
n, err := conn.Read(buffer)
|
||||
if err != nil {
|
||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
||||
log.Println("读取超时,连接可能已断开")
|
||||
c.reconnect()
|
||||
return
|
||||
}
|
||||
if err.Error() == "EOF" {
|
||||
// 服务端关闭连接时,退出接收循环
|
||||
log.Println("服务端关闭了连接")
|
||||
c.reconnect()
|
||||
return
|
||||
}
|
||||
log.Println("接收数据失败:", err)
|
||||
log.Printf("接收数据失败: %v", err)
|
||||
c.reconnect()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -118,12 +229,20 @@ func (c *Client) receiveMsg() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) send(msg []byte) {
|
||||
func (c *Client) send(msg []byte) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if c.ServerConn == nil {
|
||||
return fmt.Errorf("连接已断开")
|
||||
}
|
||||
|
||||
_, err := c.ServerConn.Write(msg)
|
||||
if err != nil {
|
||||
log.Fatalf("发送消息失败,消息内容:%s", string(msg))
|
||||
return
|
||||
log.Printf("发送消息失败:%v\n消息内容:%s", err, string(msg))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) handleReceiveMsg(msg []byte) {
|
||||
@@ -144,64 +263,239 @@ func (c *Client) handleReceiveMsg(msg []byte) {
|
||||
return
|
||||
}
|
||||
switch data.Method {
|
||||
case "pong":
|
||||
// 收到心跳响应,更新最后pong时间
|
||||
c.mu.Lock()
|
||||
c.lastPong = time.Now()
|
||||
c.mu.Unlock()
|
||||
log.Println("收到心跳响应")
|
||||
return
|
||||
case "mining.req":
|
||||
mining_msg, ok := data.Params.(message.ConfigurationMiningMsg)
|
||||
if ok {
|
||||
// 这里开始挖矿
|
||||
err := c.os.Mining(c.osName, mining_msg)
|
||||
if err != nil {
|
||||
sendMsg_str := message.ServerMsgResp{
|
||||
ID: c.Auth + "." + c.MachineCode,
|
||||
Result: false,
|
||||
Data: err,
|
||||
}
|
||||
sendMsg_byte, err := json.Marshal(sendMsg_str)
|
||||
if err != nil {
|
||||
log.Fatalf("序列化%v失败:%v", sendMsg_str, err)
|
||||
break
|
||||
}
|
||||
c.send(sendMsg_byte) // 返回失败消息
|
||||
}
|
||||
// 挖矿开始
|
||||
data := message.ConfigurationMiningResp{
|
||||
Coin: mining_msg.Coin,
|
||||
Algo: mining_msg.Algo,
|
||||
Pool: mining_msg.Pool,
|
||||
PoolUrl: mining_msg.PoolUrl,
|
||||
WorkerID: mining_msg.WorkerID,
|
||||
WalletAddress: mining_msg.WalletAddress,
|
||||
WatchUrl: "", // 这里需要根据矿池自动生成
|
||||
}
|
||||
sendMsg_str := message.ServerMsgResp{
|
||||
ID: c.Auth + "." + c.MachineCode,
|
||||
Result: true,
|
||||
Data: data,
|
||||
}
|
||||
sendMsg_byte, err := json.Marshal(sendMsg_str)
|
||||
if err != nil {
|
||||
log.Fatalf("序列化%v失败:%v", sendMsg_str, err)
|
||||
break
|
||||
}
|
||||
c.send(sendMsg_byte) // 返回成功消息
|
||||
} else {
|
||||
// 将 data.Params 重新序列化为 JSON,然后反序列化为 ConfigurationMiningMsg
|
||||
// 因为 data.Params 是 any 类型,JSON 反序列化后是 map[string]interface{}
|
||||
paramsJSON, err := json.Marshal(data.Params)
|
||||
if err != nil {
|
||||
log.Printf("序列化 Params 失败:%v", err)
|
||||
sendMsg_str := message.ServerMsgResp{
|
||||
ID: c.Auth + "." + c.MachineCode,
|
||||
Result: false,
|
||||
Data: fmt.Errorf("错误的params数据结构:%v", mining_msg),
|
||||
Data: fmt.Errorf("序列化 Params 失败:%v", err),
|
||||
}
|
||||
sendMsg_byte, _ := json.Marshal(sendMsg_str)
|
||||
c.send(sendMsg_byte)
|
||||
return
|
||||
}
|
||||
|
||||
var mining_msg message.ConfigurationMiningMsg
|
||||
err = json.Unmarshal(paramsJSON, &mining_msg)
|
||||
if err != nil {
|
||||
log.Printf("解析挖矿配置消息失败:%v, Params: %s", err, string(paramsJSON))
|
||||
sendMsg_str := message.ServerMsgResp{
|
||||
ID: c.Auth + "." + c.MachineCode,
|
||||
Result: false,
|
||||
Data: fmt.Errorf("解析挖矿配置消息失败:%v", err),
|
||||
}
|
||||
sendMsg_byte, _ := json.Marshal(sendMsg_str)
|
||||
c.send(sendMsg_byte)
|
||||
return
|
||||
}
|
||||
|
||||
// 暂停持续挖矿(如果有新任务)
|
||||
if c.sustainMiner != nil && c.sustainMiner.IsRunning() {
|
||||
c.sustainMiner.Pause()
|
||||
}
|
||||
|
||||
// 这里开始挖矿
|
||||
err = c.os.Mining(c.osName, mining_msg)
|
||||
if err != nil {
|
||||
sendMsg_str := message.ServerMsgResp{
|
||||
ID: c.Auth + "." + c.MachineCode,
|
||||
Result: false,
|
||||
Data: err.Error(),
|
||||
}
|
||||
sendMsg_byte, err := json.Marshal(sendMsg_str)
|
||||
if err != nil {
|
||||
log.Fatalf("序列化%v失败:%v", sendMsg_str, err)
|
||||
break
|
||||
return
|
||||
}
|
||||
c.send(sendMsg_byte) // 返回失败消息
|
||||
return
|
||||
}
|
||||
// 挖矿开始
|
||||
respData := message.ConfigurationMiningResp{
|
||||
Coin: mining_msg.Coin,
|
||||
Algo: mining_msg.Algo,
|
||||
Pool: mining_msg.Pool,
|
||||
PoolUrl: mining_msg.PoolUrl,
|
||||
WorkerID: mining_msg.WorkerID,
|
||||
WalletAddress: mining_msg.WalletAddress,
|
||||
WatchUrl: "", // 这里需要根据矿池自动生成
|
||||
}
|
||||
sendMsg_str := message.ServerMsgResp{
|
||||
ID: c.Auth + "." + c.MachineCode,
|
||||
Result: true,
|
||||
Data: respData,
|
||||
}
|
||||
sendMsg_byte, err := json.Marshal(sendMsg_str)
|
||||
if err != nil {
|
||||
log.Fatalf("序列化%v失败:%v", sendMsg_str, err)
|
||||
return
|
||||
}
|
||||
c.send(sendMsg_byte) // 返回成功消息
|
||||
|
||||
// 启动任务结束监控,任务结束后恢复持续挖矿
|
||||
go c.monitorMiningTask(mining_msg)
|
||||
case "mining.end":
|
||||
c.os.StopMining(c.osName)
|
||||
default:
|
||||
log.Printf("未知的方法:%s", data.Method)
|
||||
}
|
||||
}
|
||||
|
||||
// monitorMiningTask 监控挖矿任务,任务结束后恢复持续挖矿
|
||||
func (c *Client) monitorMiningTask(cfg message.ConfigurationMiningMsg) {
|
||||
endTimestamp := int64(cfg.EndTimestamp)
|
||||
currentTimestamp := time.Now().Unix()
|
||||
|
||||
// 如果任务已经结束,直接恢复持续挖矿
|
||||
if endTimestamp <= currentTimestamp {
|
||||
if c.sustainMiner != nil {
|
||||
c.sustainMiner.Resume()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 计算等待时间
|
||||
waitDuration := time.Second * time.Duration(endTimestamp-currentTimestamp)
|
||||
|
||||
// 等待任务结束
|
||||
time.Sleep(waitDuration)
|
||||
|
||||
log.Println("挖矿任务已结束,恢复持续挖矿")
|
||||
|
||||
// 恢复持续挖矿
|
||||
if c.sustainMiner != nil {
|
||||
c.sustainMiner.Resume()
|
||||
}
|
||||
}
|
||||
|
||||
// startHeartbeat 启动心跳检查
|
||||
func (c *Client) startHeartbeat() {
|
||||
ticker := time.NewTicker(30 * time.Second) // 每30秒发送一次心跳
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// 检查是否超过60秒未收到pong响应
|
||||
c.mu.Lock()
|
||||
lastPong := c.lastPong
|
||||
conn := c.ServerConn
|
||||
c.mu.Unlock()
|
||||
|
||||
if time.Since(lastPong) > 60*time.Second {
|
||||
log.Println("超过60秒未收到心跳响应,连接可能已断开")
|
||||
c.reconnect()
|
||||
return
|
||||
}
|
||||
|
||||
// 发送心跳
|
||||
if conn != nil {
|
||||
pingMsg := message.ServerMsg{
|
||||
ID: c.Auth + "." + c.MachineCode,
|
||||
Method: "ping",
|
||||
Params: nil,
|
||||
}
|
||||
msgByte, err := json.Marshal(pingMsg)
|
||||
if err != nil {
|
||||
log.Printf("序列化心跳消息失败:%v", err)
|
||||
continue
|
||||
}
|
||||
if err := c.send(msgByte); err != nil {
|
||||
log.Printf("发送心跳失败:%v", err)
|
||||
c.reconnect()
|
||||
return
|
||||
}
|
||||
log.Println("发送心跳")
|
||||
}
|
||||
case <-c.stopHeartbeat:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Star() {
|
||||
url := "xxxx"
|
||||
client := newClient(url)
|
||||
client.sendMachineCode()
|
||||
go client.receiveMsg() // 开始接收服务端消息
|
||||
// reconnect 重连服务器
|
||||
func (c *Client) reconnect() {
|
||||
c.mu.Lock()
|
||||
if c.ServerConn != nil {
|
||||
c.ServerConn.Close()
|
||||
c.ServerConn = nil
|
||||
}
|
||||
close(c.stopHeartbeat)
|
||||
c.stopHeartbeat = make(chan struct{})
|
||||
serverURL := c.serverURL
|
||||
c.mu.Unlock()
|
||||
|
||||
log.Println("尝试重新连接服务器...")
|
||||
|
||||
// 重连逻辑
|
||||
for {
|
||||
time.Sleep(5 * time.Second) // 等待5秒后重连
|
||||
|
||||
conn, err := net.Dial("tcp", serverURL)
|
||||
if err != nil {
|
||||
log.Printf("重连失败:%v,5秒后重试...", err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Println("重连成功")
|
||||
c.mu.Lock()
|
||||
c.ServerConn = conn
|
||||
c.lastPong = time.Now()
|
||||
c.mu.Unlock()
|
||||
|
||||
// 重新发送机器码
|
||||
c.sendMachineCode()
|
||||
|
||||
// 重新启动心跳和接收消息
|
||||
go c.startHeartbeat()
|
||||
go c.receiveMsg()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func Star(url string) {
|
||||
// url := "10.168.2.249:8080"
|
||||
// url := "47.108.221.51:23456"
|
||||
client := newClient(url)
|
||||
globalClient = client
|
||||
client.sendMachineCode()
|
||||
|
||||
// 初始化并启动持续挖矿(如果配置启用)
|
||||
if client.sustainMiner != nil {
|
||||
err := client.sustainMiner.LoadConfig()
|
||||
if err != nil {
|
||||
log.Printf("加载持续挖矿配置失败:%v", err)
|
||||
} else {
|
||||
err = client.sustainMiner.Start()
|
||||
if err != nil {
|
||||
log.Printf("启动持续挖矿失败:%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 启动心跳检查
|
||||
go client.startHeartbeat()
|
||||
|
||||
// 开始接收服务端消息
|
||||
client.receiveMsg()
|
||||
}
|
||||
|
||||
var globalClient *Client
|
||||
|
||||
// StopClient 停止客户端(供外部调用)
|
||||
func StopClient() {
|
||||
if globalClient != nil {
|
||||
globalClient.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
145
internal/db/db.go
Normal file
145
internal/db/db.go
Normal file
@@ -0,0 +1,145 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
message "client/internal/msg"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3" // Import the sqlite3 driver
|
||||
)
|
||||
|
||||
type SQLiteServer struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
// NewSQLiteServer creates a new SQLite server instance with an open database connection
|
||||
func NewSQLiteServer() *SQLiteServer {
|
||||
// Open (or create) the SQLite database
|
||||
db, err := sql.Open("sqlite3", "./mining_task.db")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create an SQLiteServer instance with the database connection
|
||||
s := &SQLiteServer{
|
||||
DB: db,
|
||||
}
|
||||
|
||||
// Initialize the 'task' table
|
||||
if err := s.initTaskTable(); err != nil {
|
||||
log.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// initTaskTable creates the 'task' table if it doesn't already exist
|
||||
func (s *SQLiteServer) initTaskTable() error {
|
||||
// SQL statement to create the 'task' table if it doesn't exist
|
||||
str := `
|
||||
CREATE TABLE IF NOT EXISTS task (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
coin TEXT,
|
||||
algo TEXT,
|
||||
pool TEXT,
|
||||
wallet_mining TEXT,
|
||||
pool_url TEXT,
|
||||
wallet_address TEXT,
|
||||
pool_user TEXT,
|
||||
worker_id TEXT,
|
||||
end_timestamp INTEGER,
|
||||
state INTEGER
|
||||
);`
|
||||
|
||||
// Execute the query to create the table
|
||||
_, err := s.DB.Exec(str)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create table: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the database connection manually when done with it
|
||||
func (s *SQLiteServer) Close() error {
|
||||
// Close the database connection
|
||||
if err := s.DB.Close(); err != nil {
|
||||
return fmt.Errorf("failed to close database: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadMiningTask loads a mining task from the database
|
||||
func (s *SQLiteServer) LoadMiningTask() (bool, message.ConfigurationMiningMsg) {
|
||||
str := "SELECT coin, algo, pool, wallet_mining, pool_url, wallet_address, pool_user, worker_id, end_timestamp FROM task WHERE state = ?;"
|
||||
params := []any{0}
|
||||
// Query the database
|
||||
rows, err := s.DB.Query(str, params...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
// Check if exactly one row is returned
|
||||
var task message.ConfigurationMiningMsg
|
||||
if rows.Next() {
|
||||
// Scan the row into the task structure
|
||||
err := rows.Scan(&task.Coin, &task.Algo, &task.Pool, &task.WalletMining, &task.PoolUrl, &task.WalletAddress, &task.PoolUser, &task.WorkerID, &task.EndTimestamp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Check if there's more than one row
|
||||
if rows.Next() {
|
||||
// If there are multiple rows, return false with an empty structure
|
||||
return false, message.ConfigurationMiningMsg{}
|
||||
}
|
||||
|
||||
// If exactly one row is found, return true with the data
|
||||
return true, task
|
||||
}
|
||||
|
||||
// No rows found, return false with an empty structure
|
||||
return false, message.ConfigurationMiningMsg{}
|
||||
}
|
||||
|
||||
// InsertMiningTask inserts a new mining task into the database
|
||||
func (s *SQLiteServer) InsertMiningTask(msg message.ConfigurationMiningMsg) error {
|
||||
// SQL INSERT statement
|
||||
str := `
|
||||
INSERT INTO task (coin, algo, pool, wallet_mining, pool_url, wallet_address, pool_user, worker_id, end_timestamp, state)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
`
|
||||
|
||||
// Set the state to 0 by default (active or pending)
|
||||
state := 0
|
||||
|
||||
// Execute the query
|
||||
_, err := s.DB.Exec(str, msg.Coin, msg.Algo, msg.Pool, msg.WalletMining, msg.PoolUrl, msg.WalletAddress, msg.PoolUser, msg.WorkerID, msg.EndTimestamp, state)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert mining task: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FinishMiningTask updates the state of a mining task to 'finished' (state = 1)
|
||||
func (s *SQLiteServer) FinishMiningTask(msg message.ConfigurationMiningMsg) error {
|
||||
// SQL UPDATE statement, matching all fields
|
||||
str := `
|
||||
UPDATE task
|
||||
SET state = 1
|
||||
WHERE coin = ? AND algo = ? AND pool = ? AND wallet_mining = ? AND pool_url = ? AND wallet_address = ? AND pool_user = ? AND worker_id = ? AND end_timestamp = ?;
|
||||
`
|
||||
|
||||
// Execute the query
|
||||
_, err := s.DB.Exec(str, msg.Coin, msg.Algo, msg.Pool, msg.WalletMining, msg.PoolUrl, msg.WalletAddress, msg.PoolUser, msg.WorkerID, msg.EndTimestamp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to finish mining task: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -14,6 +14,7 @@ type ServerMsg struct {
|
||||
|
||||
type ServerMsgResp struct {
|
||||
ID string `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Result bool `json:"result"`
|
||||
Data any `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@ package linux
|
||||
|
||||
// lspci | grep -i vga
|
||||
import (
|
||||
"client/internal/db"
|
||||
message "client/internal/msg"
|
||||
"client/internal/utils"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -17,12 +19,32 @@ import (
|
||||
)
|
||||
|
||||
type LinuxClient struct {
|
||||
mu sync.Mutex
|
||||
Auth string
|
||||
MachineCode string
|
||||
ID string
|
||||
MiningConfig message.MiningConfig
|
||||
Status int // 当前client状态,1正在工作,2空闲
|
||||
mu sync.Mutex
|
||||
Auth string
|
||||
MachineCode string
|
||||
ID string
|
||||
MiningConfig message.MiningConfig
|
||||
db *db.SQLiteServer
|
||||
Status int // 当前client状态,1正在工作,2空闲
|
||||
currentProcess *currentProcess // 当前挖矿进程及类型
|
||||
}
|
||||
|
||||
type currentProcess struct {
|
||||
process *exec.Cmd
|
||||
miner string
|
||||
}
|
||||
|
||||
// CheckPermission 检查当前进程是否具备运行本客户端所需的权限(Linux)
|
||||
// 要求使用 root 运行,否则很多命令(如 dmidecode、nvidia-smi 等)可能失败。
|
||||
func CheckPermission() error {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取当前用户信息失败: %v", err)
|
||||
}
|
||||
if u.Uid != "0" {
|
||||
return fmt.Errorf("当前用户(%s)不是 root 用户,请使用 sudo 或 root 账号运行本程序", u.Username)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewLinuxClient(auth string) *LinuxClient {
|
||||
@@ -80,6 +102,12 @@ func NewLinuxClient(auth string) *LinuxClient {
|
||||
client.Auth = auth
|
||||
client.MiningConfig = miningConfig
|
||||
client.Status = 2
|
||||
|
||||
// 初始化sqlite3数据库
|
||||
db := db.NewSQLiteServer()
|
||||
client.db = db
|
||||
client.initHistoryTask()
|
||||
// 获取主机身份
|
||||
mac, err := client.GetMACAddress()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
@@ -87,9 +115,29 @@ func NewLinuxClient(auth string) *LinuxClient {
|
||||
}
|
||||
client.MachineCode = mac
|
||||
client.ID = auth + "." + mac
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func (l *LinuxClient) initHistoryTask() {
|
||||
exsits, task := l.db.LoadMiningTask()
|
||||
if exsits {
|
||||
currentTs := time.Now().Unix()
|
||||
if currentTs < int64(task.EndTimestamp) {
|
||||
l.mu.Lock()
|
||||
l.Status = 1
|
||||
l.mu.Unlock()
|
||||
err := l.Mining(task)
|
||||
if err != nil {
|
||||
log.Fatalf("重新开启挖矿任务失败,请手动检查:%v", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
l.db.FinishMiningTask(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 NVIDIA 显卡信息
|
||||
func getNvidiaGPUInfo() (map[int]message.GPU, error) {
|
||||
// 使用 nvidia-smi 列出所有 GPU
|
||||
@@ -189,24 +237,33 @@ func (l *LinuxClient) GetGPUInfo() (map[int]message.GPU, error) {
|
||||
}
|
||||
|
||||
// 获取 MAC 地址
|
||||
// 获取“机器码”:Linux 下改为使用主机 UUID
|
||||
// 优先从 /sys/class/dmi/id/product_uuid 读取,失败时再尝试 dmidecode
|
||||
func (l *LinuxClient) GetMACAddress() (string, error) {
|
||||
// 执行 ifconfig 或 ip link 命令
|
||||
cmd := exec.Command("ifconfig", "-a")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 查找 MAC 地址
|
||||
lines := strings.Split(string(out), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "ether") {
|
||||
parts := strings.Fields(line)
|
||||
return parts[1], nil
|
||||
// 1. 优先读取内核提供的 product_uuid 文件(大多数物理机/虚拟机都支持)
|
||||
const uuidPath = "/sys/class/dmi/id/product_uuid"
|
||||
if data, err := os.ReadFile(uuidPath); err == nil {
|
||||
uuid := strings.TrimSpace(string(data))
|
||||
if uuid != "" && !strings.EqualFold(uuid, "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") {
|
||||
uuid = strings.Trim(uuid, "{}")
|
||||
uuid = strings.ToUpper(uuid)
|
||||
return uuid, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("MAC address not found")
|
||||
// 2. 退回使用 dmidecode(需要有权限)
|
||||
cmd := exec.Command("dmidecode", "-s", "system-uuid")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取主机 UUID 失败: %v", err)
|
||||
}
|
||||
uuid := strings.TrimSpace(string(out))
|
||||
if uuid == "" || strings.EqualFold(uuid, "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") {
|
||||
return "", fmt.Errorf("获取到的主机 UUID 无效: %q", uuid)
|
||||
}
|
||||
uuid = strings.Trim(uuid, "{}")
|
||||
uuid = strings.ToUpper(uuid)
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -249,27 +306,47 @@ func (l *LinuxClient) lolminer(cfg message.ConfigurationMiningMsg) {
|
||||
log.Fatalf("Error starting lolMiner: %v", err)
|
||||
return
|
||||
}
|
||||
// 添加执行记录
|
||||
go func() {
|
||||
err := l.db.InsertMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("本次挖矿任务记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 获取 lolMiner 的进程 ID
|
||||
fmt.Printf("lolMiner started with PID: %d\n", cmd.Process.Pid)
|
||||
|
||||
// 记录当前挖矿进程
|
||||
l.mu.Lock()
|
||||
l.currentProcess = ¤tProcess{
|
||||
process: cmd,
|
||||
miner: "lolminer",
|
||||
}
|
||||
l.mu.Unlock()
|
||||
// 获取当前时间戳(秒级)
|
||||
currentTimestamp := time.Now().Unix()
|
||||
endTimestamp := int64(cfg.EndTimestamp)
|
||||
// 计算目标时间戳和当前时间戳之间的差值
|
||||
if endTimestamp <= currentTimestamp {
|
||||
fmt.Println("目标时间已经到达,直接执行操作")
|
||||
// 如果目标时间已经到达,立即结束挖矿进程
|
||||
fmt.Println("目标时间已经到达,立即结束 lolMiner 挖矿进程")
|
||||
l.StopMining()
|
||||
} else {
|
||||
// 计算需要等待的秒数
|
||||
waitDuration := time.Second * time.Duration(endTimestamp-currentTimestamp)
|
||||
fmt.Printf("当前时间戳:%d,目标时间戳:%d,剩余时间:%v\n", currentTimestamp, endTimestamp, waitDuration)
|
||||
// 使用 time.Sleep 等待直到目标时间戳
|
||||
time.Sleep(waitDuration)
|
||||
fmt.Println("目标时间到达,开始执行操作")
|
||||
// 杀掉进程
|
||||
err = cmd.Process.Kill()
|
||||
if err != nil {
|
||||
log.Fatalf("Error killing lolMiner: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("目标时间到达,开始执行操作,停止 lolMiner 挖矿进程")
|
||||
// 通过 StopMining 统一停止挖矿进程
|
||||
l.StopMining()
|
||||
// 修改执行记录
|
||||
go func() {
|
||||
err := l.db.FinishMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("修改执行记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 输出进程被杀死的信息
|
||||
fmt.Println("lolMiner process killed.")
|
||||
}
|
||||
@@ -277,6 +354,7 @@ func (l *LinuxClient) lolminer(cfg message.ConfigurationMiningMsg) {
|
||||
l.mu.Lock()
|
||||
l.Status = 2
|
||||
l.mu.Unlock()
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -317,27 +395,46 @@ func (l *LinuxClient) bzminer(cfg message.ConfigurationMiningMsg) {
|
||||
log.Fatalf("Error starting bzminer: %v", err)
|
||||
return
|
||||
}
|
||||
// 添加执行记录
|
||||
go func() {
|
||||
err := l.db.InsertMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("本次挖矿任务记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 获取 bzminer 的进程 ID
|
||||
fmt.Printf("bzminer started with PID: %d\n", cmd.Process.Pid)
|
||||
|
||||
// 记录当前挖矿进程
|
||||
l.mu.Lock()
|
||||
l.currentProcess = ¤tProcess{
|
||||
process: cmd,
|
||||
miner: "bzminer",
|
||||
}
|
||||
l.mu.Unlock()
|
||||
// 获取当前时间戳(秒级)
|
||||
currentTimestamp := time.Now().Unix()
|
||||
endTimestamp := int64(cfg.EndTimestamp)
|
||||
// 计算目标时间戳和当前时间戳之间的差值
|
||||
if endTimestamp <= currentTimestamp {
|
||||
fmt.Println("目标时间已经到达,直接执行操作")
|
||||
fmt.Println("目标时间已经到达,停止 bzminer 挖矿进程")
|
||||
l.StopMining()
|
||||
} else {
|
||||
// 计算需要等待的秒数
|
||||
waitDuration := time.Second * time.Duration(endTimestamp-currentTimestamp)
|
||||
fmt.Printf("当前时间戳:%d,目标时间戳:%d,剩余时间:%v\n", currentTimestamp, endTimestamp, waitDuration)
|
||||
// 使用 time.Sleep 等待直到目标时间戳
|
||||
time.Sleep(waitDuration)
|
||||
fmt.Println("目标时间到达,开始执行操作")
|
||||
// 杀掉进程
|
||||
err = cmd.Process.Kill()
|
||||
if err != nil {
|
||||
log.Fatalf("Error killing bzminer: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("目标时间到达,开始执行操作,停止 bzminer 挖矿进程")
|
||||
// 通过 StopMining 统一停止挖矿进程
|
||||
l.StopMining()
|
||||
// 修改执行记录
|
||||
go func() {
|
||||
err := l.db.FinishMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("修改执行记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 输出进程被杀死的信息
|
||||
fmt.Println("bzminer process killed.")
|
||||
}
|
||||
@@ -374,7 +471,8 @@ func (l *LinuxClient) rigel(cfg message.ConfigurationMiningMsg) {
|
||||
}
|
||||
dir := l.MiningConfig.RigelPath
|
||||
name := filepath.Join(dir, "rigel")
|
||||
args := []string{"-a", strings.ToLower(cfg.Algo), "-o", cfg.PoolUrl, "-u", address, "-w", cfg.WorkerID, "--log-file", "logs/miner.log"}
|
||||
// 禁用 rigel 内置 watchdog,避免自动拉起子进程
|
||||
args := []string{"--no-watchdog", "-a", strings.ToLower(cfg.Algo), "-o", cfg.PoolUrl, "-u", address, "-w", cfg.WorkerID, "--log-file", "logs/miner.log"}
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
@@ -386,28 +484,48 @@ func (l *LinuxClient) rigel(cfg message.ConfigurationMiningMsg) {
|
||||
log.Fatalf("Error starting rigel: %v", err)
|
||||
return
|
||||
}
|
||||
// 添加执行记录
|
||||
go func() {
|
||||
err := l.db.InsertMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("本次挖矿任务记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 获取 rigel 的进程 ID
|
||||
fmt.Printf("rigel started with PID: %d\n", cmd.Process.Pid)
|
||||
|
||||
// 记录当前挖矿进程
|
||||
l.mu.Lock()
|
||||
l.currentProcess = ¤tProcess{
|
||||
process: cmd,
|
||||
miner: "rigel",
|
||||
}
|
||||
l.mu.Unlock()
|
||||
// 获取当前时间戳(秒级)
|
||||
currentTimestamp := time.Now().Unix()
|
||||
endTimestamp := int64(cfg.EndTimestamp)
|
||||
// 计算目标时间戳和当前时间戳之间的差值
|
||||
if endTimestamp <= currentTimestamp {
|
||||
fmt.Println("目标时间已经到达,直接执行操作")
|
||||
// 如果目标时间已经到达,立即结束 rigel 挖矿进程
|
||||
fmt.Println("目标时间已经到达,立即结束 rigel 挖矿进程")
|
||||
l.StopMining()
|
||||
} else {
|
||||
// 计算需要等待的秒数
|
||||
waitDuration := time.Second * time.Duration(endTimestamp-currentTimestamp)
|
||||
fmt.Printf("当前时间戳:%d,目标时间戳:%d,剩余时间:%v\n", currentTimestamp, endTimestamp, waitDuration)
|
||||
// 使用 time.Sleep 等待直到目标时间戳
|
||||
time.Sleep(waitDuration)
|
||||
fmt.Println("目标时间到达,开始执行操作")
|
||||
// 杀掉进程
|
||||
err = cmd.Process.Kill()
|
||||
if err != nil {
|
||||
log.Fatalf("Error killing rigel: %v", err)
|
||||
}
|
||||
|
||||
// 输出进程被杀死的信息
|
||||
fmt.Println("目标时间到达,开始执行操作,结束 rigel 挖矿进程")
|
||||
// 通过 StopMining 统一停止挖矿进程
|
||||
l.StopMining()
|
||||
// 修改执行记录
|
||||
go func() {
|
||||
err := l.db.FinishMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("修改执行记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 输出进程被结束的信息
|
||||
fmt.Println("rigel process killed.")
|
||||
}
|
||||
log.Printf("当前挖矿任务已执行完毕:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
|
||||
@@ -445,3 +563,42 @@ func (l *LinuxClient) Mining(cfg message.ConfigurationMiningMsg) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 主动终止当前挖矿进程
|
||||
func (l *LinuxClient) StopMining() {
|
||||
l.mu.Lock()
|
||||
cp := l.currentProcess
|
||||
l.mu.Unlock()
|
||||
|
||||
if cp == nil || cp.process == nil || cp.process.Process == nil {
|
||||
log.Println("当前没有正在进行的挖矿任务")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("准备停止当前挖矿任务,miner=%s, pid=%d", cp.miner, cp.process.Process.Pid)
|
||||
|
||||
// 根据挖矿软件类型选择合适的停止方式
|
||||
switch strings.ToLower(cp.miner) {
|
||||
case "rigel":
|
||||
// 优先尝试优雅退出(Interrupt),失败再 Kill
|
||||
if err := cp.process.Process.Signal(os.Interrupt); err != nil {
|
||||
log.Printf("向 rigel 发送 Interrupt 失败,尝试 Kill: %v", err)
|
||||
if errKill := cp.process.Process.Kill(); errKill != nil {
|
||||
log.Printf("Kill rigel 失败:%v", errKill)
|
||||
}
|
||||
}
|
||||
default:
|
||||
// 其它挖矿软件直接 Kill
|
||||
if err := cp.process.Process.Kill(); err != nil {
|
||||
log.Printf("停止挖矿进程失败:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 清理状态
|
||||
l.mu.Lock()
|
||||
l.currentProcess = nil
|
||||
l.Status = 2
|
||||
l.mu.Unlock()
|
||||
|
||||
log.Println("当前挖矿任务已被手动停止")
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ package src
|
||||
import (
|
||||
message "client/internal/msg"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type OS interface {
|
||||
GetGPUInfo() (map[int]message.GPU, error)
|
||||
Mining(cfg message.ConfigurationMiningMsg) error
|
||||
StopMining()
|
||||
GetMACAddress() (string, error)
|
||||
}
|
||||
|
||||
@@ -61,3 +63,11 @@ func (s *SystemServer) GetMACAddress(osName string) (string, error) {
|
||||
}
|
||||
return "", fmt.Errorf("错误的操作系统:%s", osName)
|
||||
}
|
||||
|
||||
func (s *SystemServer) StopMining(osName string) {
|
||||
if srv, ok := s.systems[osName]; ok {
|
||||
srv.StopMining()
|
||||
} else {
|
||||
log.Fatalf("错误的操作系统:%s", osName)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,139 @@
|
||||
package windows
|
||||
|
||||
// Windows 版本的客户端实现
|
||||
import (
|
||||
"client/internal/db"
|
||||
message "client/internal/msg"
|
||||
"client/internal/utils"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
type WindowsClient struct {
|
||||
mu sync.Mutex
|
||||
Auth string
|
||||
MachineCode string
|
||||
ID string
|
||||
MiningConfig message.MiningConfig
|
||||
db *db.SQLiteServer
|
||||
Status int // 当前client状态,1正在工作,2空闲
|
||||
currentProcess *currentProcess // 当前挖矿进程及类型
|
||||
}
|
||||
|
||||
type currentProcess struct {
|
||||
process *exec.Cmd
|
||||
miner string
|
||||
}
|
||||
|
||||
// CheckPermission 检查当前进程是否具有管理员权限(Windows)
|
||||
// 通过执行 `net session` 命令来判断:该命令只有在管理员权限下才会成功。
|
||||
func CheckPermission() error {
|
||||
cmd := exec.Command("net", "session")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("当前用户不是管理员,请以管理员身份运行本程序(右键以管理员运行)")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewWindowsClient(auth string) *WindowsClient {
|
||||
cfg, err := ini.Load("mining.windows.conf")
|
||||
if err != nil {
|
||||
log.Fatalf("获取挖矿配置失败: %v", err)
|
||||
log.Printf("客户端已退出,请重新检查配置文件(%s)是否存在", "mining.windows.conf")
|
||||
return nil
|
||||
}
|
||||
// 解析配置
|
||||
var miningConfig message.MiningConfig
|
||||
// 解析 [bzminer] 部分
|
||||
sectionBzMiner := cfg.Section("bzminer")
|
||||
miningConfig.BzMinerPath = sectionBzMiner.Key("path").String()
|
||||
|
||||
// 解析 [lolminer] 部分
|
||||
sectionLolMiner := cfg.Section("lolminer")
|
||||
miningConfig.LolMinerPath = sectionLolMiner.Key("path").String()
|
||||
|
||||
// 解析 [rigel] 部分
|
||||
sectionRigel := cfg.Section("rigel")
|
||||
miningConfig.RigelPath = sectionRigel.Key("path").String()
|
||||
|
||||
// 解析 [proxy] 部分
|
||||
sectionProxy := cfg.Section("proxy")
|
||||
miningConfig.ProxyEnabled, _ = sectionProxy.Key("proxy").Bool()
|
||||
|
||||
if miningConfig.BzMinerPath != "" {
|
||||
result := utils.CheckFileExists(miningConfig.BzMinerPath)
|
||||
if !result {
|
||||
log.Fatalf("未检测到bzminer挖矿软件的存在,请确认是否已经安装bzminer,并核对下列路径:%s", miningConfig.BzMinerPath)
|
||||
log.Println("客户端已退出,确认路径后请重启客户端")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if miningConfig.LolMinerPath != "" {
|
||||
result := utils.CheckFileExists(miningConfig.LolMinerPath)
|
||||
if !result {
|
||||
log.Fatalf("未检测到lolminer挖矿软件的存在,请确认是否已经安装lolminer,并核对下列路径:%s", miningConfig.LolMinerPath)
|
||||
log.Println("客户端已退出,确认路径后请重启客户端")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if miningConfig.RigelPath != "" {
|
||||
result := utils.CheckFileExists(miningConfig.RigelPath)
|
||||
if !result {
|
||||
log.Fatalf("未检测到rigel挖矿软件的存在,请确认是否已经安装rigel,并核对下列路径:%s", miningConfig.RigelPath)
|
||||
log.Println("客户端已退出,确认路径后请重启客户端")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var client = &WindowsClient{}
|
||||
client.Auth = auth
|
||||
client.MiningConfig = miningConfig
|
||||
client.Status = 2
|
||||
|
||||
// 初始化sqlite3数据库
|
||||
db := db.NewSQLiteServer()
|
||||
client.db = db
|
||||
|
||||
mac, err := client.GetMACAddress()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
panic("获取当前主机信息失败,程序已退出,请检查网络后重新启动本客户端。")
|
||||
}
|
||||
|
||||
client.MachineCode = mac
|
||||
client.ID = auth + "." + mac
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
func (w *WindowsClient) initHistoryTask() {
|
||||
exsits, task := w.db.LoadMiningTask()
|
||||
if exsits {
|
||||
currentTs := time.Now().Unix()
|
||||
if currentTs < int64(task.EndTimestamp) {
|
||||
w.mu.Lock()
|
||||
w.Status = 1
|
||||
w.mu.Unlock()
|
||||
err := w.Mining(task)
|
||||
if err != nil {
|
||||
log.Fatalf("重新开启挖矿任务失败,请手动检查:%v", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
w.db.FinishMiningTask(task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 NVIDIA 显卡信息
|
||||
func getNvidiaGPUInfo() (map[int]message.GPU, error) {
|
||||
// 使用 nvidia-smi 列出所有 GPU
|
||||
@@ -56,10 +182,10 @@ func parseMemory(memStr string) (float64, error) {
|
||||
return mem, nil
|
||||
}
|
||||
|
||||
// 获取 AMD 显卡信息
|
||||
func getAmdGPUInfo() (map[int]message.GPU, error) {
|
||||
// 使用 wmic 列出所有显卡信息
|
||||
cmd := exec.Command("wmic", "path", "Win32_VideoController", "get", "Name,AdapterRAM")
|
||||
// 获取其他显卡信息(如 Intel 或 AMD)
|
||||
func getOtherGPUInfo() (map[int]message.GPU, error) {
|
||||
// Windows 下使用 wmic 命令获取显卡信息
|
||||
cmd := exec.Command("wmic", "path", "win32_VideoController", "get", "name")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("无法执行 wmic 命令: %v", err)
|
||||
@@ -72,27 +198,29 @@ func getAmdGPUInfo() (map[int]message.GPU, error) {
|
||||
gpus := make(map[int]message.GPU)
|
||||
gpuIndex := 0
|
||||
|
||||
for _, line := range lines[1:] { // 跳过标题行
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) >= 2 {
|
||||
// 获取显存大小并转换为 MB
|
||||
memStr := parts[1]
|
||||
mem, err := parseMemoryFromBytes(memStr)
|
||||
if err != nil {
|
||||
mem = 0 // 如果显存无法解析,则设置为 0
|
||||
}
|
||||
|
||||
// 用正确的品牌名填充
|
||||
gpus[gpuIndex] = message.GPU{
|
||||
Brand: "AMD",
|
||||
Model: parts[0],
|
||||
Mem: mem,
|
||||
}
|
||||
gpuIndex++
|
||||
}
|
||||
// 跳过标题行和空行
|
||||
if line == "" || line == "Name" {
|
||||
continue
|
||||
}
|
||||
// 判断显卡品牌
|
||||
brand := "Unknown"
|
||||
if strings.Contains(line, "NVIDIA") {
|
||||
brand = "NVIDIA"
|
||||
} else if strings.Contains(line, "AMD") || strings.Contains(line, "Radeon") {
|
||||
brand = "AMD"
|
||||
} else if strings.Contains(line, "Intel") {
|
||||
brand = "Intel"
|
||||
}
|
||||
|
||||
// 将 GPU 信息存储到 map 中
|
||||
gpus[gpuIndex] = message.GPU{
|
||||
Brand: brand,
|
||||
Model: line,
|
||||
Mem: 0, // wmic 无法直接获取显存,需要其他方法
|
||||
}
|
||||
gpuIndex++
|
||||
}
|
||||
|
||||
if len(gpus) == 0 {
|
||||
@@ -102,48 +230,351 @@ func getAmdGPUInfo() (map[int]message.GPU, error) {
|
||||
return gpus, nil
|
||||
}
|
||||
|
||||
// 从字节转换显存为 MB
|
||||
func parseMemoryFromBytes(memStr string) (float64, error) {
|
||||
// 将字节转换为 MB
|
||||
memBytes, err := strconv.Atoi(memStr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("字节转显存失败: %v", err)
|
||||
}
|
||||
|
||||
// 显存从字节转为MB
|
||||
memMB := float64(memBytes) / (1024 * 1024)
|
||||
return memMB, nil
|
||||
}
|
||||
|
||||
// 获取所有 GPU 信息
|
||||
func GetGPUInfo() (map[int]message.GPU, error) {
|
||||
func (w *WindowsClient) GetGPUInfo() (map[int]message.GPU, error) {
|
||||
// 尝试获取 NVIDIA GPU 信息
|
||||
gpus, err := getNvidiaGPUInfo()
|
||||
if err == nil {
|
||||
return gpus, nil
|
||||
}
|
||||
|
||||
// 尝试获取 AMD GPU 信息
|
||||
gpus, err = getAmdGPUInfo()
|
||||
if err == nil {
|
||||
return gpus, nil
|
||||
// 如果没有 NVIDIA 显卡,尝试获取其他类型显卡信息
|
||||
return getOtherGPUInfo()
|
||||
}
|
||||
|
||||
// Windows 版本:使用主机 UUID 作为“机器码”返回给上层(仍然复用 GetMACAddress 接口名)
|
||||
// 通过 PowerShell 命令:Get-WmiObject -Class Win32_ComputerSystemProduct | Select-Object -ExpandProperty UUID
|
||||
func (w *WindowsClient) GetMACAddress() (string, error) {
|
||||
// 注意:在 Go 中调用 PowerShell
|
||||
cmd := exec.Command("powershell", "-Command", "Get-WmiObject -Class Win32_ComputerSystemProduct | Select-Object -ExpandProperty UUID")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取主机 UUID 失败: %v", err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("未找到任何显卡信息")
|
||||
uuid := strings.TrimSpace(string(out))
|
||||
if uuid == "" || strings.EqualFold(uuid, "FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF") {
|
||||
return "", fmt.Errorf("获取到的主机 UUID 无效: %q", uuid)
|
||||
}
|
||||
|
||||
// 统一转换为大写,去掉花括号
|
||||
uuid = strings.Trim(uuid, "{}")
|
||||
uuid = strings.ToUpper(uuid)
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"coin-algo": {
|
||||
"lolminer": {
|
||||
"pool_name": "url"
|
||||
},
|
||||
"bzminer": {
|
||||
"pool_name": "url"
|
||||
},
|
||||
"rigel": {
|
||||
"pool_name": "url"
|
||||
}
|
||||
}
|
||||
}
|
||||
配置lolminer
|
||||
Windows 下使用 lolMiner.exe
|
||||
*/
|
||||
func (w *WindowsClient) lolminer(cfg message.ConfigurationMiningMsg) {
|
||||
w.mu.Lock()
|
||||
if w.Status != 2 {
|
||||
log.Fatalf("当前还有挖矿任务正在进行中:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
|
||||
return
|
||||
}
|
||||
w.Status = 1
|
||||
w.mu.Unlock()
|
||||
|
||||
var address string
|
||||
if cfg.WalletMining {
|
||||
address = cfg.WalletAddress
|
||||
} else {
|
||||
address = cfg.PoolUser
|
||||
}
|
||||
|
||||
dir := w.MiningConfig.LolMinerPath
|
||||
name := filepath.Join(dir, "lolMiner.exe")
|
||||
args := []string{"--algo", cfg.Coin, "--pool", cfg.PoolUrl, "--user", address + "." + cfg.WorkerID}
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
log.Printf("执行命令:%s %s", name, strings.Join(args, " "))
|
||||
// 启动进程
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("Error starting lolMiner: %v", err)
|
||||
return
|
||||
}
|
||||
// 添加执行记录
|
||||
go func() {
|
||||
err := w.db.InsertMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("本次挖矿任务记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 获取 lolMiner 的进程 ID
|
||||
fmt.Printf("lolMiner started with PID: %d\n", cmd.Process.Pid)
|
||||
|
||||
// 记录当前挖矿进程
|
||||
w.mu.Lock()
|
||||
w.currentProcess = ¤tProcess{
|
||||
process: cmd,
|
||||
miner: "lolminer",
|
||||
}
|
||||
w.mu.Unlock()
|
||||
// 获取当前时间戳(秒级)
|
||||
currentTimestamp := time.Now().Unix()
|
||||
endTimestamp := int64(cfg.EndTimestamp)
|
||||
// 计算目标时间戳和当前时间戳之间的差值
|
||||
if endTimestamp <= currentTimestamp {
|
||||
// 如果目标时间已经到达,立即结束挖矿进程
|
||||
fmt.Println("目标时间已经到达,立即结束 lolMiner 挖矿进程")
|
||||
w.StopMining()
|
||||
// 修改执行记录
|
||||
go func() {
|
||||
err := w.db.FinishMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("修改执行记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
// 计算需要等待的秒数
|
||||
waitDuration := time.Second * time.Duration(endTimestamp-currentTimestamp)
|
||||
fmt.Printf("当前时间戳:%d,目标时间戳:%d,剩余时间:%v\n", currentTimestamp, endTimestamp, waitDuration)
|
||||
// 使用 time.Sleep 等待直到目标时间戳
|
||||
time.Sleep(waitDuration)
|
||||
fmt.Println("目标时间到达,开始执行操作,结束 lolMiner 挖矿进程")
|
||||
// 通过 StopMining 统一停止挖矿进程
|
||||
w.StopMining()
|
||||
// 修改执行记录
|
||||
go func() {
|
||||
err := w.db.FinishMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("修改执行记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
log.Printf("当前挖矿任务已执行完毕:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
|
||||
w.mu.Lock()
|
||||
w.Status = 2
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
/*
|
||||
配置bzminer
|
||||
Windows 下使用 bzminer.exe
|
||||
*/
|
||||
func (w *WindowsClient) bzminer(cfg message.ConfigurationMiningMsg) {
|
||||
w.mu.Lock()
|
||||
if w.Status != 2 {
|
||||
log.Fatalf("当前还有挖矿任务正在进行中:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
|
||||
return
|
||||
}
|
||||
w.Status = 1
|
||||
w.mu.Unlock()
|
||||
var address string
|
||||
if cfg.WalletMining {
|
||||
address = cfg.WalletAddress
|
||||
} else {
|
||||
address = cfg.PoolUser
|
||||
}
|
||||
dir := w.MiningConfig.BzMinerPath
|
||||
name := filepath.Join(dir, "bzminer.exe")
|
||||
args := []string{"-a", cfg.Coin, "-w", address + "." + cfg.WorkerID, "-p", cfg.PoolUrl}
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
log.Printf("执行命令:%s %s", name, strings.Join(args, " "))
|
||||
// 启动进程
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("Error starting bzminer: %v", err)
|
||||
return
|
||||
}
|
||||
// 添加执行记录
|
||||
go func() {
|
||||
err := w.db.InsertMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("本次挖矿任务记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 获取 bzminer 的进程 ID
|
||||
fmt.Printf("bzminer started with PID: %d\n", cmd.Process.Pid)
|
||||
|
||||
// 记录当前挖矿进程
|
||||
w.mu.Lock()
|
||||
w.currentProcess = ¤tProcess{
|
||||
process: cmd,
|
||||
miner: "bzminer",
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
// 获取当前时间戳(秒级)
|
||||
currentTimestamp := time.Now().Unix()
|
||||
endTimestamp := int64(cfg.EndTimestamp)
|
||||
// 计算目标时间戳和当前时间戳之间的差值
|
||||
if endTimestamp <= currentTimestamp {
|
||||
// 如果目标时间已经到达,立即结束挖矿进程
|
||||
fmt.Println("目标时间已经到达,立即结束 bzminer 挖矿进程")
|
||||
w.StopMining()
|
||||
fmt.Println("bzminer process killed.")
|
||||
} else {
|
||||
// 计算需要等待的秒数
|
||||
waitDuration := time.Second * time.Duration(endTimestamp-currentTimestamp)
|
||||
fmt.Printf("当前时间戳:%d,目标时间戳:%d,剩余时间:%v\n", currentTimestamp, endTimestamp, waitDuration)
|
||||
// 使用 time.Sleep 等待直到目标时间戳
|
||||
time.Sleep(waitDuration)
|
||||
fmt.Println("目标时间到达,开始执行操作,结束 bzminer 挖矿进程")
|
||||
// 通过 StopMining 统一停止挖矿进程
|
||||
w.StopMining()
|
||||
// 修改执行记录
|
||||
go func() {
|
||||
err := w.db.FinishMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("修改执行记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 输出进程被结束的信息
|
||||
fmt.Println("bzminer process killed.")
|
||||
}
|
||||
log.Printf("当前挖矿任务已执行完毕:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
|
||||
w.mu.Lock()
|
||||
w.Status = 2
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
/*
|
||||
配置rigel
|
||||
Windows 下使用 rigel.exe
|
||||
*/
|
||||
func (w *WindowsClient) rigel(cfg message.ConfigurationMiningMsg) {
|
||||
w.mu.Lock()
|
||||
if w.Status != 2 {
|
||||
log.Fatalf("当前还有挖矿任务正在进行中:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
|
||||
return
|
||||
}
|
||||
w.Status = 1
|
||||
w.mu.Unlock()
|
||||
var address string
|
||||
if cfg.WalletMining {
|
||||
address = cfg.WalletAddress
|
||||
} else {
|
||||
address = cfg.PoolUser
|
||||
}
|
||||
dir := w.MiningConfig.RigelPath
|
||||
name := filepath.Join(dir, "rigel.exe")
|
||||
// 禁用 rigel 内置 watchdog,避免自动拉起子进程
|
||||
args := []string{"--no-watchdog", "-a", strings.ToLower(cfg.Algo), "-o", cfg.PoolUrl, "-u", address, "-w", cfg.WorkerID, "--log-file", "logs/miner.log"}
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
log.Printf("执行命令:%s %s", name, strings.Join(args, " "))
|
||||
// 启动进程
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatalf("Error starting rigel: %v", err)
|
||||
return
|
||||
}
|
||||
// 添加执行记录
|
||||
go func() {
|
||||
err := w.db.InsertMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("本次挖矿任务记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 获取 rigel 的进程 ID
|
||||
fmt.Printf("rigel started with PID: %d\n", cmd.Process.Pid)
|
||||
|
||||
// 记录当前挖矿进程
|
||||
w.mu.Lock()
|
||||
w.currentProcess = ¤tProcess{
|
||||
process: cmd,
|
||||
miner: "rigel",
|
||||
}
|
||||
w.mu.Unlock()
|
||||
|
||||
// 获取当前时间戳(秒级)
|
||||
currentTimestamp := time.Now().Unix()
|
||||
endTimestamp := int64(cfg.EndTimestamp)
|
||||
// 计算目标时间戳和当前时间戳之间的差值
|
||||
if endTimestamp <= currentTimestamp {
|
||||
// 如果目标时间已经到达,立即结束挖矿进程
|
||||
fmt.Println("目标时间已经到达,立即结束 rigel 挖矿进程")
|
||||
w.StopMining()
|
||||
fmt.Println("rigel process killed.")
|
||||
} else {
|
||||
// 计算需要等待的秒数
|
||||
waitDuration := time.Second * time.Duration(endTimestamp-currentTimestamp)
|
||||
fmt.Printf("当前时间戳:%d,目标时间戳:%d,剩余时间:%v\n", currentTimestamp, endTimestamp, waitDuration)
|
||||
// 使用 time.Sleep 等待直到目标时间戳
|
||||
time.Sleep(waitDuration)
|
||||
fmt.Println("目标时间到达,开始执行操作,结束 rigel 挖矿进程")
|
||||
// 通过 StopMining 统一停止挖矿进程
|
||||
w.StopMining()
|
||||
// 修改执行记录
|
||||
go func() {
|
||||
err := w.db.FinishMiningTask(cfg)
|
||||
if err != nil {
|
||||
log.Fatalf("修改执行记录失败:%v", err)
|
||||
}
|
||||
}()
|
||||
// 输出进程被结束的信息
|
||||
fmt.Println("rigel process killed.")
|
||||
}
|
||||
log.Printf("当前挖矿任务已执行完毕:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp)
|
||||
w.mu.Lock()
|
||||
w.Status = 2
|
||||
w.mu.Unlock()
|
||||
}
|
||||
|
||||
// StopMining 主动终止当前挖矿进程
|
||||
func (w *WindowsClient) StopMining() {
|
||||
w.mu.Lock()
|
||||
cp := w.currentProcess
|
||||
w.mu.Unlock()
|
||||
|
||||
if cp == nil || cp.process == nil || cp.process.Process == nil {
|
||||
log.Println("当前没有正在进行的挖矿任务")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("准备停止当前挖矿任务,miner=%s, pid=%d", cp.miner, cp.process.Process.Pid)
|
||||
|
||||
// Windows 下统一使用 Kill 结束进程
|
||||
if err := cp.process.Process.Kill(); err != nil {
|
||||
log.Printf("停止挖矿进程失败:%v", err)
|
||||
}
|
||||
|
||||
// 清理状态
|
||||
w.mu.Lock()
|
||||
w.currentProcess = nil
|
||||
w.Status = 2
|
||||
w.mu.Unlock()
|
||||
|
||||
log.Println("当前挖矿任务已被手动停止")
|
||||
}
|
||||
|
||||
// XTM-rigel(1%), XNA-bzminer(1%), CLORE-bzminer(1%), CFX-rigel(2%), IRON-lolminer(1%), NEXA-lolminer(2%), KLS-lolminer(1%), RVN-bzminer(1%), ERG-bzminer(1%), XEL-rigel(2%)
|
||||
func (w *WindowsClient) Mining(cfg message.ConfigurationMiningMsg) error {
|
||||
info := cfg.Coin + "-" + cfg.Algo
|
||||
switch info {
|
||||
case "SHA3X-SHA3X":
|
||||
go w.lolminer(cfg) // SHA3X
|
||||
case "XNA-KawPow":
|
||||
go w.bzminer(cfg) // xna
|
||||
case "CLORE-KawPow":
|
||||
go w.bzminer(cfg) // clore
|
||||
case "CFX-Octopus":
|
||||
go w.rigel(cfg) // octopus
|
||||
case "IRON-IronFish":
|
||||
go w.lolminer(cfg)
|
||||
case "NEXA-NexaPow":
|
||||
go w.lolminer(cfg)
|
||||
case "KLS-KarlsenHash":
|
||||
go w.lolminer(cfg)
|
||||
case "RVN-KawPow":
|
||||
go w.bzminer(cfg) // rvn
|
||||
case "ERG-Autolykos":
|
||||
go w.bzminer(cfg) // ergo
|
||||
case "XEL-Xelishashv2":
|
||||
go w.rigel(cfg)
|
||||
default:
|
||||
return fmt.Errorf("不支持%s算法", info)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
353
internal/sustain/proxy.go
Normal file
353
internal/sustain/proxy.go
Normal file
@@ -0,0 +1,353 @@
|
||||
/*
|
||||
持续挖矿
|
||||
1,在主配置文件中选择是否开启
|
||||
2,开启后将读取挖矿配置(mining.conf)
|
||||
3,根据配置,启动相应的挖矿软件开始挖矿
|
||||
4,接收到tcp协议传送过来的新挖矿任务后,停止当前挖矿,并开启新的挖矿
|
||||
5,新挖矿任务到期或结束后,重新开启挖矿
|
||||
|
||||
配置文件:算法、挖矿软件、挖矿配置(算法、钱包、矿工号、挖矿地址)
|
||||
*/
|
||||
package sustain
|
||||
|
||||
import (
|
||||
message "client/internal/msg"
|
||||
"client/internal/src"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// SustainMiningConfig 持续挖矿配置
|
||||
type SustainMiningConfig struct {
|
||||
Enabled bool // 是否启用持续挖矿
|
||||
Algo string // 算法
|
||||
Coin string // 币种
|
||||
Miner string // 挖矿软件 (lolminer/bzminer/rigel)
|
||||
PoolUrl string // 矿池地址
|
||||
Wallet string // 钱包地址
|
||||
WorkerID string // 矿工号
|
||||
PoolUser string // 矿池账号(可选)
|
||||
WalletMining bool // 是否使用钱包挖矿
|
||||
}
|
||||
|
||||
// SustainMiner 持续挖矿管理器
|
||||
type SustainMiner struct {
|
||||
mu sync.Mutex
|
||||
config SustainMiningConfig
|
||||
miningConfig message.MiningConfig
|
||||
os *src.SystemServer
|
||||
osName string
|
||||
currentProcess *exec.Cmd // 当前挖矿进程
|
||||
isRunning bool // 是否正在运行持续挖矿
|
||||
isPaused bool // 是否被暂停(因为有新任务)
|
||||
stopChan chan struct{} // 停止信号
|
||||
}
|
||||
|
||||
// NewSustainMiner 创建持续挖矿管理器
|
||||
func NewSustainMiner(os *src.SystemServer, osName string, miningConfig message.MiningConfig) *SustainMiner {
|
||||
return &SustainMiner{
|
||||
os: os,
|
||||
osName: osName,
|
||||
miningConfig: miningConfig,
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadConfig 从配置文件加载持续挖矿配置
|
||||
func (s *SustainMiner) LoadConfig() error {
|
||||
var confFile string
|
||||
if runtime.GOOS == "windows" {
|
||||
confFile = "mining.windows.conf"
|
||||
} else {
|
||||
confFile = "mining.linux.conf"
|
||||
}
|
||||
|
||||
cfg, err := ini.Load(confFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取配置文件失败: %v", err)
|
||||
}
|
||||
|
||||
// 读取 [sustain] 部分
|
||||
section := cfg.Section("sustain")
|
||||
s.config.Enabled, _ = section.Key("enabled").Bool()
|
||||
if !s.config.Enabled {
|
||||
log.Println("持续挖矿未启用")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 读取配置值,并去掉可能的引号
|
||||
s.config.Algo = strings.Trim(section.Key("algo").String(), `"`)
|
||||
s.config.Coin = strings.Trim(section.Key("coin").String(), `"`)
|
||||
s.config.Miner = strings.Trim(section.Key("miner").String(), `"`)
|
||||
s.config.PoolUrl = strings.Trim(section.Key("pool_url").String(), `"`)
|
||||
s.config.Wallet = strings.Trim(section.Key("wallet").String(), `"`)
|
||||
s.config.WorkerID = strings.Trim(section.Key("worker_id").String(), `"`)
|
||||
s.config.PoolUser = strings.Trim(section.Key("pool_user").String(), `"`)
|
||||
s.config.WalletMining, _ = section.Key("wallet_mining").Bool()
|
||||
|
||||
// 验证配置
|
||||
if s.config.Algo == "" || s.config.Coin == "" || s.config.Miner == "" ||
|
||||
s.config.PoolUrl == "" || s.config.Wallet == "" || s.config.WorkerID == "" {
|
||||
return fmt.Errorf("持续挖矿配置不完整")
|
||||
}
|
||||
|
||||
// 验证挖矿软件路径
|
||||
switch strings.ToLower(s.config.Miner) {
|
||||
case "lolminer":
|
||||
if s.miningConfig.LolMinerPath == "" {
|
||||
return fmt.Errorf("lolminer 路径未配置")
|
||||
}
|
||||
case "bzminer":
|
||||
if s.miningConfig.BzMinerPath == "" {
|
||||
return fmt.Errorf("bzminer 路径未配置")
|
||||
}
|
||||
case "rigel":
|
||||
if s.miningConfig.RigelPath == "" {
|
||||
return fmt.Errorf("rigel 路径未配置")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("不支持的挖矿软件: %s", s.config.Miner)
|
||||
}
|
||||
|
||||
log.Printf("持续挖矿配置加载成功: 算法=%s, 币种=%s, 挖矿软件=%s", s.config.Algo, s.config.Coin, s.config.Miner)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start 启动持续挖矿
|
||||
func (s *SustainMiner) Start() error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.config.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
if s.isRunning {
|
||||
log.Println("持续挖矿已在运行中")
|
||||
return nil
|
||||
}
|
||||
|
||||
s.isRunning = true
|
||||
s.isPaused = false
|
||||
s.stopChan = make(chan struct{})
|
||||
|
||||
log.Println("启动持续挖矿...")
|
||||
go s.run()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop 停止持续挖矿
|
||||
func (s *SustainMiner) Stop() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.isRunning {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("停止持续挖矿...")
|
||||
close(s.stopChan)
|
||||
s.isRunning = false
|
||||
|
||||
// 停止当前挖矿进程
|
||||
if s.currentProcess != nil && s.currentProcess.Process != nil {
|
||||
log.Println("停止当前挖矿进程...")
|
||||
if runtime.GOOS == "linux" && s.config.Miner == "rigel" {
|
||||
// Linux rigel 使用 Interrupt 信号
|
||||
s.currentProcess.Process.Signal(os.Interrupt)
|
||||
} else {
|
||||
s.currentProcess.Process.Kill()
|
||||
}
|
||||
s.currentProcess = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Pause 暂停持续挖矿(当有新任务时调用)
|
||||
func (s *SustainMiner) Pause() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.isRunning || s.isPaused {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("暂停持续挖矿(有新任务)...")
|
||||
s.isPaused = true
|
||||
|
||||
// 停止当前挖矿进程
|
||||
if s.currentProcess != nil && s.currentProcess.Process != nil {
|
||||
if runtime.GOOS == "linux" && s.config.Miner == "rigel" {
|
||||
s.currentProcess.Process.Signal(os.Interrupt)
|
||||
} else {
|
||||
s.currentProcess.Process.Kill()
|
||||
}
|
||||
s.currentProcess = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Resume 恢复持续挖矿(当任务结束后调用)
|
||||
func (s *SustainMiner) Resume() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.isRunning || !s.isPaused {
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("恢复持续挖矿...")
|
||||
s.isPaused = false
|
||||
|
||||
// 重新启动挖矿
|
||||
go s.startMining()
|
||||
}
|
||||
|
||||
// run 持续挖矿主循环
|
||||
func (s *SustainMiner) run() {
|
||||
for {
|
||||
select {
|
||||
case <-s.stopChan:
|
||||
log.Println("持续挖矿已停止")
|
||||
return
|
||||
default:
|
||||
s.mu.Lock()
|
||||
paused := s.isPaused
|
||||
s.mu.Unlock()
|
||||
|
||||
if !paused {
|
||||
s.startMining()
|
||||
// 等待挖矿进程结束(正常情况下不会结束,除非被停止)
|
||||
if s.currentProcess != nil {
|
||||
s.currentProcess.Wait()
|
||||
}
|
||||
} else {
|
||||
// 如果被暂停,等待恢复
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// startMining 启动挖矿进程
|
||||
func (s *SustainMiner) startMining() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.isPaused {
|
||||
return
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
var err error
|
||||
|
||||
address := s.config.Wallet
|
||||
if !s.config.WalletMining && s.config.PoolUser != "" {
|
||||
address = s.config.PoolUser
|
||||
}
|
||||
|
||||
switch strings.ToLower(s.config.Miner) {
|
||||
case "lolminer":
|
||||
cmd, err = s.startLolMiner(address)
|
||||
case "bzminer":
|
||||
cmd, err = s.startBzMiner(address)
|
||||
case "rigel":
|
||||
cmd, err = s.startRigel(address)
|
||||
default:
|
||||
log.Printf("不支持的挖矿软件: %s", s.config.Miner)
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("启动持续挖矿失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
s.currentProcess = cmd
|
||||
log.Printf("持续挖矿已启动: %s (PID: %d)", s.config.Miner, cmd.Process.Pid)
|
||||
}
|
||||
|
||||
// startLolMiner 启动 lolminer
|
||||
func (s *SustainMiner) startLolMiner(address string) (*exec.Cmd, error) {
|
||||
dir := s.miningConfig.LolMinerPath
|
||||
var name string
|
||||
if runtime.GOOS == "windows" {
|
||||
name = filepath.Join(dir, "lolMiner.exe")
|
||||
} else {
|
||||
name = filepath.Join(dir, "lolMiner")
|
||||
}
|
||||
|
||||
args := []string{"--algo", s.config.Coin, "--pool", s.config.PoolUrl, "--user", address + "." + s.config.WorkerID}
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
log.Printf("启动持续挖矿 lolminer: %s %s", name, strings.Join(args, " "))
|
||||
err := cmd.Start()
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
// startBzMiner 启动 bzminer
|
||||
func (s *SustainMiner) startBzMiner(address string) (*exec.Cmd, error) {
|
||||
dir := s.miningConfig.BzMinerPath
|
||||
var name string
|
||||
if runtime.GOOS == "windows" {
|
||||
name = filepath.Join(dir, "bzminer.exe")
|
||||
} else {
|
||||
name = filepath.Join(dir, "bzminer")
|
||||
}
|
||||
|
||||
args := []string{"-a", s.config.Coin, "-w", address + "." + s.config.WorkerID, "-p", s.config.PoolUrl}
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
log.Printf("启动持续挖矿 bzminer: %s %s", name, strings.Join(args, " "))
|
||||
err := cmd.Start()
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
// startRigel 启动 rigel
|
||||
func (s *SustainMiner) startRigel(address string) (*exec.Cmd, error) {
|
||||
dir := s.miningConfig.RigelPath
|
||||
var name string
|
||||
if runtime.GOOS == "windows" {
|
||||
name = filepath.Join(dir, "rigel.exe")
|
||||
} else {
|
||||
name = filepath.Join(dir, "rigel")
|
||||
}
|
||||
|
||||
args := []string{"--no-watchdog", "-a", strings.ToLower(s.config.Algo), "-o", s.config.PoolUrl, "-u", address, "-w", s.config.WorkerID, "--log-file", "logs/miner.log"}
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
log.Printf("启动持续挖矿 rigel: %s %s", name, strings.Join(args, " "))
|
||||
err := cmd.Start()
|
||||
return cmd, err
|
||||
}
|
||||
|
||||
// IsRunning 检查是否正在运行
|
||||
func (s *SustainMiner) IsRunning() bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.isRunning && !s.isPaused
|
||||
}
|
||||
|
||||
// IsPaused 检查是否被暂停
|
||||
func (s *SustainMiner) IsPaused() bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.isPaused
|
||||
}
|
||||
255
internal/updater/updater.go
Normal file
255
internal/updater/updater.go
Normal file
@@ -0,0 +1,255 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CheckAndUpdate 检查远程版本并更新
|
||||
// remoteBaseURL: 远程服务器基础URL,例如 "http://example.com/update"
|
||||
// currentVersion: 当前版本号
|
||||
// 返回是否需要重启(如果更新了文件)
|
||||
func CheckAndUpdate(remoteBaseURL string, currentVersion string) (bool, error) {
|
||||
// 先检查是否有待应用的更新(Windows 下上次下载但未应用的新版本)
|
||||
if runtime.GOOS == "windows" {
|
||||
exePath, err := os.Executable()
|
||||
if err == nil {
|
||||
exeDir := filepath.Dir(exePath)
|
||||
updateFlag := filepath.Join(exeDir, ".update_pending")
|
||||
if data, err := os.ReadFile(updateFlag); err == nil {
|
||||
newPath := strings.TrimSpace(string(data))
|
||||
if err := applyPendingUpdate(newPath, exePath); err == nil {
|
||||
os.Remove(updateFlag)
|
||||
log.Println("已应用待处理的更新")
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取远程版本号
|
||||
remoteVersion, err := fetchRemoteVersion(remoteBaseURL)
|
||||
if err != nil {
|
||||
log.Printf("获取远程版本失败:%v", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Printf("当前版本:%s,远程版本:%s", currentVersion, remoteVersion)
|
||||
|
||||
// 比较版本
|
||||
if strings.TrimSpace(remoteVersion) == strings.TrimSpace(currentVersion) {
|
||||
log.Println("版本一致,无需更新")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
log.Printf("检测到新版本:%s,开始下载更新...", remoteVersion)
|
||||
|
||||
// 下载新的可执行文件
|
||||
remoteExeName := getRemoteExecutableName() // 远程文件名
|
||||
localExeName := getLocalExecutableName() // 本地可执行文件名
|
||||
remoteExeURL := fmt.Sprintf("%s/%s", remoteBaseURL, remoteExeName)
|
||||
|
||||
err = downloadAndReplace(remoteExeURL, localExeName)
|
||||
if err != nil {
|
||||
log.Printf("下载更新失败:%v", err)
|
||||
return false, err
|
||||
}
|
||||
|
||||
log.Printf("更新成功!新版本:%s", remoteVersion)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// fetchRemoteVersion 从远程获取版本号
|
||||
func fetchRemoteVersion(remoteBaseURL string) (string, error) {
|
||||
versionURL := fmt.Sprintf("%s/current_version", remoteBaseURL)
|
||||
|
||||
resp, err := http.Get(versionURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("请求远程版本文件失败:%v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("获取远程版本文件失败,状态码:%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取远程版本文件失败:%v", err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(string(body)), nil
|
||||
}
|
||||
|
||||
// downloadAndReplace 下载并替换可执行文件
|
||||
func downloadAndReplace(remoteURL string, exeName string) error {
|
||||
// 获取当前可执行文件的路径
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取可执行文件路径失败:%v", err)
|
||||
}
|
||||
|
||||
exeDir := filepath.Dir(exePath)
|
||||
newExePath := filepath.Join(exeDir, exeName)
|
||||
backupPath := exePath + ".backup"
|
||||
|
||||
// 1. 备份当前可执行文件
|
||||
log.Printf("备份当前可执行文件到:%s", backupPath)
|
||||
err = copyFile(exePath, backupPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("备份文件失败:%v", err)
|
||||
}
|
||||
|
||||
// 2. 下载新的可执行文件
|
||||
log.Printf("从 %s 下载新版本...", remoteURL)
|
||||
resp, err := http.Get(remoteURL)
|
||||
if err != nil {
|
||||
// 如果下载失败,恢复备份
|
||||
restoreBackup(backupPath, exePath)
|
||||
return fmt.Errorf("下载新版本失败:%v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
restoreBackup(backupPath, exePath)
|
||||
return fmt.Errorf("下载新版本失败,状态码:%d", resp.StatusCode)
|
||||
}
|
||||
|
||||
// 3. 创建临时文件
|
||||
tempPath := newExePath + ".tmp"
|
||||
out, err := os.Create(tempPath)
|
||||
if err != nil {
|
||||
restoreBackup(backupPath, exePath)
|
||||
return fmt.Errorf("创建临时文件失败:%v", err)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// 4. 写入新文件
|
||||
_, err = io.Copy(out, resp.Body)
|
||||
if err != nil {
|
||||
os.Remove(tempPath)
|
||||
restoreBackup(backupPath, exePath)
|
||||
return fmt.Errorf("写入新文件失败:%v", err)
|
||||
}
|
||||
out.Close()
|
||||
|
||||
// 5. 设置可执行权限(Linux)
|
||||
if runtime.GOOS != "windows" {
|
||||
err = os.Chmod(tempPath, 0755)
|
||||
if err != nil {
|
||||
os.Remove(tempPath)
|
||||
restoreBackup(backupPath, exePath)
|
||||
return fmt.Errorf("设置可执行权限失败:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 尝试替换原文件
|
||||
// Windows 下如果文件正在运行,无法直接替换,需要特殊处理
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows: 先尝试直接替换,如果失败则保存为 .new 文件,下次启动时替换
|
||||
err = os.Rename(tempPath, exePath)
|
||||
if err != nil {
|
||||
// 如果替换失败(文件正在使用),保存为 .new 文件
|
||||
newPath := exePath + ".new"
|
||||
err = os.Rename(tempPath, newPath)
|
||||
if err != nil {
|
||||
os.Remove(tempPath)
|
||||
restoreBackup(backupPath, exePath)
|
||||
return fmt.Errorf("保存新版本文件失败:%v", err)
|
||||
}
|
||||
log.Printf("当前程序正在运行,新版本已保存为:%s,程序重启后将自动应用更新", newPath)
|
||||
// 创建更新标记文件,下次启动时检测并应用
|
||||
updateFlag := filepath.Join(exeDir, ".update_pending")
|
||||
os.WriteFile(updateFlag, []byte(newPath), 0644)
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// Linux: 直接替换
|
||||
err = os.Rename(tempPath, exePath)
|
||||
if err != nil {
|
||||
os.Remove(tempPath)
|
||||
restoreBackup(backupPath, exePath)
|
||||
return fmt.Errorf("替换文件失败:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 清理备份文件(可选,更新成功后删除)
|
||||
// os.Remove(backupPath)
|
||||
|
||||
log.Println("文件更新完成")
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyFile 复制文件
|
||||
func copyFile(src, dst string) error {
|
||||
sourceFile, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
destFile, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
_, err = io.Copy(destFile, sourceFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 复制文件权限
|
||||
sourceInfo, err := os.Stat(src)
|
||||
if err == nil {
|
||||
os.Chmod(dst, sourceInfo.Mode())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// restoreBackup 恢复备份文件
|
||||
func restoreBackup(backupPath, exePath string) {
|
||||
if _, err := os.Stat(backupPath); err == nil {
|
||||
log.Printf("恢复备份文件:%s -> %s", backupPath, exePath)
|
||||
os.Rename(backupPath, exePath)
|
||||
}
|
||||
}
|
||||
|
||||
// applyPendingUpdate 应用待处理的更新(Windows 下使用)
|
||||
func applyPendingUpdate(newPath, exePath string) error {
|
||||
if _, err := os.Stat(newPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("新版本文件不存在:%s", newPath)
|
||||
}
|
||||
|
||||
// 尝试替换
|
||||
err := os.Rename(newPath, exePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("应用更新失败:%v(可能程序仍在运行)", err)
|
||||
}
|
||||
|
||||
log.Printf("成功应用更新:%s -> %s", newPath, exePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getRemoteExecutableName 根据操作系统获取远程可执行文件名(用于下载)
|
||||
func getRemoteExecutableName() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return "client_windows.exe"
|
||||
}
|
||||
return "client_linux"
|
||||
}
|
||||
|
||||
// getLocalExecutableName 根据操作系统获取本地可执行文件名(用于替换)
|
||||
func getLocalExecutableName() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
return "client.exe"
|
||||
}
|
||||
return "client"
|
||||
}
|
||||
12
internal/utils/perm_linux.go
Normal file
12
internal/utils/perm_linux.go
Normal file
@@ -0,0 +1,12 @@
|
||||
//go:build linux
|
||||
|
||||
package utils
|
||||
|
||||
import "os"
|
||||
|
||||
// IsAdmin 检测当前用户是否为 root(UID=0)
|
||||
func IsAdmin() bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
||||
|
||||
|
||||
20
internal/utils/perm_windows.go
Normal file
20
internal/utils/perm_windows.go
Normal file
@@ -0,0 +1,20 @@
|
||||
//go:build windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// IsAdmin 检测当前用户是否为管理员。
|
||||
// 通过执行 `net session` 命令判断:该命令只有在管理员权限下才会成功。
|
||||
func IsAdmin() bool {
|
||||
cmd := exec.Command("net", "session")
|
||||
// 不关心输出,只关心能否成功执行
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user