From 789be70a53b5044bca82c02f9dedf10ba8756189 Mon Sep 17 00:00:00 2001 From: lzx <393768033@qq.com> Date: Wed, 26 Nov 2025 11:39:20 +0800 Subject: [PATCH] version-1 --- .gitignore | 1 + README.md | 111 ++++++++ bin/mining.linux.conf | 12 + bin/mining.windows.conf | 13 + cmd/linux.sh | 10 + cmd/main.go | 7 + cmd/windows.bat | 10 + go.mod | 8 + go.sum | 4 + internal/client.go | 207 +++++++++++++++ internal/msg/config.go | 8 + internal/msg/msg.go | 43 +++ internal/src/linux/linux.go | 447 ++++++++++++++++++++++++++++++++ internal/src/src.go | 63 +++++ internal/src/windows/windows.go | 149 +++++++++++ internal/utils/utils.go | 89 +++++++ 16 files changed, 1182 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 bin/mining.linux.conf create mode 100644 bin/mining.windows.conf create mode 100644 cmd/linux.sh create mode 100644 cmd/main.go create mode 100644 cmd/windows.bat create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/client.go create mode 100644 internal/msg/config.go create mode 100644 internal/msg/msg.go create mode 100644 internal/src/linux/linux.go create mode 100644 internal/src/src.go create mode 100644 internal/src/windows/windows.go create mode 100644 internal/utils/utils.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..109d31e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/test/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..35d8eb7 --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# 云算力平台卖方客户端 + +本程序为云算力平台卖方客户端,用于将卖方身份与GPU主机绑定,实现自动挖矿匹配功能。 + +## 功能特性 + +1. **身份绑定**:将云算力平台上的卖方身份和GPU主机进行绑定 +2. **GPU信息上报**:自动获取并上报主机GPU详细参数(GPU型号、显存容量等) +3. **自动挖矿匹配**:自动匹配买方的挖矿需求,无需手动操作挖矿 + +## 系统要求 + +- Go 1.25.4 或更高版本 +- Windows 或 Linux 操作系统 +- 已配置好挖矿环境(显卡驱动、挖矿软件、执行权限等) + +## 编译方法 + +### Windows 系统 + +在项目根目录下运行: + +```bash +cmd\windows.bat +``` + +编译后的可执行文件将位于 `bin/client.exe` + +### Linux 系统 + +在项目根目录下运行: + +```bash +chmod +x cmd/linux.sh +./cmd/linux.sh +``` + +编译后的可执行文件将位于 `bin/client` + +## 使用方法 + +1. **准备身份文件**:在 `bin` 目录下创建 `auth` 文件,包含你的卖方身份信息 + +2. **运行客户端**: + - Windows: 运行 `bin\client.exe` + - Linux: 运行 `bin/client` + +3. 客户端将自动: + - 读取身份信息 + - 获取主机MAC地址和GPU信息 + - 连接到云算力平台服务器 + - 等待并处理挖矿任务 + +## 重要注意事项 + +### 启动前准备 + +1. **挖矿环境配置**:确保客户端执行主机已配置好挖矿环境,包括: + - 显卡驱动已正确安装 + - 挖矿软件已安装并配置 + - 执行权限已设置 + - 可以手动通过挖矿软件进行挖矿 + +### GPU 操作注意事项 + +#### 移除 GPU + +- 如果要对本机GPU进行移除(拔出GPU)操作,云算力平台会同步移除对应的GPU +- **重要**:如果在相关GPU有租约且没有在平台申请故障处理的情况下直接移除GPU,会导致产生罚没 +- **建议**:在有租约的情况下要移除故障GPU,请第一时间前往平台申请故障处理,在平台确认后再进行移除GPU的操作 + +#### 更换 GPU + +- 如果要对本机GPU进行更换(拔出后又新插入GPU)操作,云算力平台会重新读取GPU数据 +- 如果更换型号相同,则会按原有配置上架 +- 如果更换的型号不同,则需在更换后前往卖家中心手动调整上架配置 +- **重要**:如果在相关GPU有租约且没有在平台申请故障处理的情况下直接更换GPU,可能会导致产生罚没 +- **建议**:在有租约的情况下要更换故障GPU,请第一时间前往平台申请故障处理,在平台确认后再进行更换GPU的操作 + +## 项目结构 + +``` +cloud-client/ +├── bin/ # 编译输出目录 +│ ├── auth # 身份认证文件(需手动创建) +│ ├── mining.linux.conf # Linux 挖矿配置 +│ └── mining.windows.conf # Windows 挖矿配置 +├── cmd/ # 主程序目录 +│ ├── main.go # 程序入口 +│ ├── windows.bat # Windows 编译脚本 +│ └── linux.sh # Linux 编译脚本 +├── internal/ # 内部包 +│ ├── client.go # 客户端主逻辑 +│ ├── msg/ # 消息处理 +│ ├── src/ # 系统相关实现 +│ │ ├── linux/ # Linux 系统实现 +│ │ └── windows/ # Windows 系统实现 +│ └── utils/ # 工具函数 +├── go.mod # Go 模块定义 +└── README.md # 本文件 +``` + +## 依赖项 + +- `github.com/google/uuid` v1.6.0 +- `gopkg.in/ini.v1` v1.67.0 + +## 许可证 + +[根据实际情况填写] + diff --git a/bin/mining.linux.conf b/bin/mining.linux.conf new file mode 100644 index 0000000..f2347db --- /dev/null +++ b/bin/mining.linux.conf @@ -0,0 +1,12 @@ +#请确认您的主机上安装了下列挖矿软件,确认后可以打开注释,并修改其路径,如果没有安装,请勿打开注释 +[bzminer] +# path=/path/bzminer +[lolminer] +# path=/home/a11/桌面/lolminer/1.98 +[rigel] +# path=/path/rigel + +#如果您的网络无法直接连通各个矿池,需要使用各大矿池专用网咯,请打开proxy的注释 +#打开此注释后会使用各大矿池的专用网络,每笔订单额外增加1%的网络费用 +[proxy] +# proxy=true \ No newline at end of file diff --git a/bin/mining.windows.conf b/bin/mining.windows.conf new file mode 100644 index 0000000..c2ca0a9 --- /dev/null +++ b/bin/mining.windows.conf @@ -0,0 +1,13 @@ +#请确认您的主机上安装了下列挖矿软件,确认后可以打开注释,并修改其路径,如果没有安装,请勿打开注释 +#请使用双\\,否则可能无法解析出准确的路径 +[bzminer] +# path=C:\\path\\bzminer +[lolminer] +# path=C:\\path\\lolminer +[rigel] +# path=C:\\path\\rigel + +#如果您的网络无法直接连通各个矿池,需要使用各大矿池专用网咯,请打开proxy的注释 +#打开此注释后会使用各大矿池的专用网络,每笔订单额外增加1%的网络费用 +[proxy] +# proxy=true \ No newline at end of file diff --git a/cmd/linux.sh b/cmd/linux.sh new file mode 100644 index 0000000..069a2dd --- /dev/null +++ b/cmd/linux.sh @@ -0,0 +1,10 @@ +#!/bin/bash +cd "$(dirname "$0")" +go build -o ../bin/client main.go +if [ $? -eq 0 ]; then + echo "编译成功!可执行文件位于: ../bin/client" +else + echo "编译失败!" + exit 1 +fi + diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..b136f3b --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,7 @@ +package main + +import client "client/internal" + +func main() { + client.Star() +} diff --git a/cmd/windows.bat b/cmd/windows.bat new file mode 100644 index 0000000..16065c4 --- /dev/null +++ b/cmd/windows.bat @@ -0,0 +1,10 @@ +@echo off +cd /d %~dp0 +go build -o ../bin/client.exe main.go +if %errorlevel% equ 0 ( + echo 编译成功!可执行文件位于: ..\bin\client.exe +) else ( + echo 编译失败! + exit /b %errorlevel% +) + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ab7cd70 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module client + +go 1.25.4 + +require ( + github.com/google/uuid v1.6.0 + gopkg.in/ini.v1 v1.67.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..c563d2f --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= diff --git a/internal/client.go b/internal/client.go new file mode 100644 index 0000000..d881262 --- /dev/null +++ b/internal/client.go @@ -0,0 +1,207 @@ +/* +本程序为云算力平台卖方客户端,主要提供以下功能: +1,将云算力平台上的卖方身份和GPU主机绑定 +2,通过本客户端,可以使云算力平台获取到卖方具体每台机器的详细参数,包括GPU型号、显存容量等 +3,卖方可通过本客户端自动匹配买方的挖矿需求,即卖方无需再买方下单后手动操作挖矿 + +卖家在启动客户端之前需要注意以下事项: +1,确定客户端执行主机已经配置好挖矿环境,包括显卡驱动、挖矿软件(指定挖矿软件)、执行权限等,即执行本客户端的用户可以手动通过挖矿软件进行挖矿 +2,如果要对本机GPU进行移除(拔出GPU)操作,云算力平台会同步移除对应的GPU +3,如果在相关GPU有租约且没有在平台申请故障处理的情况下直接移除(拔出)GPU,会导致产生罚没,因此在有租约的情况下要移除故障GPU,请第一时间前往平台申请故障处理,在平台确认后再进行移除GPU的操作 +4,如果要对本机GPU进行更换(拔出后又新插入GPU)操作,云算力平台会重新读取GPU数据,如果更换型号相同,则会按原有配置上架,如果更换的型号不同,则需在更换后前往卖家中心手动调整上架配置 +5,如果在相关GPU有租约且没有在平台申请故障处理的情况下直接更换GPU,可能会导致产生罚没,因此在有租约的情况下要更换故障GPU,请第一时间前往平台申请故障处理,在平台确认后再进行更换GPU的操作 +*/ +package client + +import ( + message "client/internal/msg" + "client/internal/src" + "client/internal/src/linux" + "client/internal/utils" + "encoding/json" + "fmt" + "log" + "net" + "runtime" + "strings" +) + +type Client struct { + Auth string + MachineCode string + ServerConn net.Conn // 服务连接 + GPUs map[int]message.GPU // {"gpu编号": message.GPU{}, ...} + os *src.SystemServer + osName string +} + +func newClient(url string) *Client { + var client = &Client{} + // 读取身份文件 + auth, err := utils.ReadFile("./auth") + if err != nil { + log.Fatalf("获取客户端身份失败:%v", err) + return nil + } + + client.Auth = auth + + os := src.NewSystemServer() + sys := runtime.GOOS + if sys == "linux" { + linux_ := linux.NewLinuxClient(auth) + os.ResiterSystem("linux", linux_) + } else { + + } + client.os = os + client.osName = sys + + // 读取主机MAC地址信息 + var machine_code string + machine_code, err = os.GetMACAddress(sys) + if err != nil { + log.Fatalln(err) + panic("获取当前主机信息失败,程序已退出,请检查网络后重新启动本客户端。") + } + utils.WirteFile("./machinecode", machine_code) + + client.MachineCode = machine_code + + gpus, err := os.GetGPUInfo(sys) + if err != nil { + log.Fatalln(err) + panic("获取当前主机GPU数据失败,程序已退出,请检查GPU驱动等程序后重新启动本客户端。") + } + client.GPUs = gpus + 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 +} + +func (c *Client) sendMachineCode() { + var msg message.ServerMsg + msg.ID = c.Auth + "." + c.MachineCode + msg.Method = "auth.machineCode" + msg.Params = c.GPUs + msgByte, err := json.Marshal(msg) + if err != nil { + log.Fatalf("消息(%v)序列化失败:%v", msg, err) + return + } + c.send(msgByte) +} + +func (c *Client) receiveMsg() { + buffer := make([]byte, 1024) + + for { + n, err := c.ServerConn.Read(buffer) + if err != nil { + if err.Error() == "EOF" { + // 服务端关闭连接时,退出接收循环 + log.Println("服务端关闭了连接") + return + } + log.Println("接收数据失败:", err) + return + } + + msgByte := buffer[:n] + go c.handleReceiveMsg(msgByte) + } +} + +func (c *Client) send(msg []byte) { + _, err := c.ServerConn.Write(msg) + if err != nil { + log.Fatalf("发送消息失败,消息内容:%s", string(msg)) + return + } +} + +func (c *Client) handleReceiveMsg(msg []byte) { + var data message.ServerMsg + err := json.Unmarshal(msg, &data) + if err != nil { + log.Fatalf("解析接收到的消息失败:%v", err) + return + } + parts := strings.Split(data.ID, ".") + if len(parts) != 2 { + log.Fatalf("解析通信协议(server->client)失败") + return + } + auth, machine_code := parts[0], parts[1] + if c.Auth != auth || c.MachineCode != machine_code { + log.Fatalf("客户端接收到错误的服务端消息") + return + } + switch data.Method { + 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 { + sendMsg_str := message.ServerMsgResp{ + ID: c.Auth + "." + c.MachineCode, + Result: false, + Data: fmt.Errorf("错误的params数据结构:%v", mining_msg), + } + sendMsg_byte, err := json.Marshal(sendMsg_str) + if err != nil { + log.Fatalf("序列化%v失败:%v", sendMsg_str, err) + break + } + c.send(sendMsg_byte) // 返回失败消息 + } + } +} + +func Star() { + url := "xxxx" + client := newClient(url) + client.sendMachineCode() + go client.receiveMsg() // 开始接收服务端消息 +} diff --git a/internal/msg/config.go b/internal/msg/config.go new file mode 100644 index 0000000..997d9f8 --- /dev/null +++ b/internal/msg/config.go @@ -0,0 +1,8 @@ +package message + +type MiningConfig struct { + BzMinerPath string + LolMinerPath string + RigelPath string + ProxyEnabled bool +} diff --git a/internal/msg/msg.go b/internal/msg/msg.go new file mode 100644 index 0000000..c01bd36 --- /dev/null +++ b/internal/msg/msg.go @@ -0,0 +1,43 @@ +package message + +type GPU struct { + Brand string `json:"brand"` // 品牌 + Model string `json:"model"` // 型号 + Mem float64 `json:"mem,omitempty"` // 显存容量(MB) +} + +type ServerMsg struct { + ID string `json:"id"` // 身份码.主机码 + Method string `json:"method"` + Params any `json:"params"` +} + +type ServerMsgResp struct { + ID string `json:"id"` + Result bool `json:"result"` + Data any `json:"data,omitempty"` +} + +// server -> client, method:"mining.req" +type ConfigurationMiningMsg struct { + Coin string `json:"coin"` // 币种 + Algo string `json:"algo"` // 算法 + Pool string `json:"pool"` // 矿池 + WalletMining bool `json:"wallet_mining"` // 是否支持钱包挖矿 + PoolUrl string `json:"pool_url"` // 挖矿地址 + WalletAddress string `json:"wallet_address"` // 收款钱包 + PoolUser string `json:"pool_user,omitempty"` // 矿池挖矿账号,仅针对不支持钱包挖矿的矿池,如f2pool、m2pool等,支持钱包挖矿的矿池不需该字段 + WorkerID string `json:"worker_id"` // 矿工号 + EndTimestamp uint64 `json:"end_timestamp"` // 合约结束时间戳,使用时统一转换成UTC+0时区 +} + +// client -> server, method:"mining.resp" +type ConfigurationMiningResp struct { + Coin string `json:"coin"` // 币种 + Algo string `json:"algo"` // 算法 + Pool string `json:"pool"` // 矿池 + PoolUrl string `json:"pool_url"` // 挖矿地址 + WorkerID string `json:"worker_id"` // 矿工号 + WalletAddress string `json:"wallet_address"` // 收款钱包 + WatchUrl string `json:"watch_url"` // 挖矿信息页面,只有在StartMining字段为true的时候才有,false时该字段为空字符串 +} diff --git a/internal/src/linux/linux.go b/internal/src/linux/linux.go new file mode 100644 index 0000000..9b51222 --- /dev/null +++ b/internal/src/linux/linux.go @@ -0,0 +1,447 @@ +package linux + +// lspci | grep -i vga +import ( + message "client/internal/msg" + "client/internal/utils" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "sync" + "time" + + "gopkg.in/ini.v1" +) + +type LinuxClient struct { + mu sync.Mutex + Auth string + MachineCode string + ID string + MiningConfig message.MiningConfig + Status int // 当前client状态,1正在工作,2空闲 +} + +func NewLinuxClient(auth string) *LinuxClient { + cfg, err := ini.Load("mining.linux.conf") + if err != nil { + log.Fatalf("获取挖矿配置失败: %v", err) + log.Printf("客户端已退出,请重新检查配置文件(%s)是否存在", "mining.linux.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 = &LinuxClient{} + client.Auth = auth + client.MiningConfig = miningConfig + client.Status = 2 + mac, err := client.GetMACAddress() + if err != nil { + log.Fatalln(err) + panic("获取当前主机信息失败,程序已退出,请检查网络后重新启动本客户端。") + } + client.MachineCode = mac + client.ID = auth + "." + mac + return client +} + +// 获取 NVIDIA 显卡信息 +func getNvidiaGPUInfo() (map[int]message.GPU, error) { + // 使用 nvidia-smi 列出所有 GPU + cmd := exec.Command("nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader,nounits") + output, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("无法执行 nvidia-smi 命令: %v", err) + } + + // 解析输出 + info := strings.TrimSpace(string(output)) + lines := strings.Split(info, "\n") + + gpus := make(map[int]message.GPU) + + for i, line := range lines { + parts := strings.Split(strings.TrimSpace(line), ", ") + if len(parts) < 2 { + return nil, fmt.Errorf("nvidia-smi 输出格式错误") + } + + // 解析显存容量 + mem, err := parseMemory(parts[1]) + if err != nil { + return nil, fmt.Errorf("无法解析显存容量: %v", err) + } + + // 将每个 GPU 的信息存储到 map 中 + gpus[i] = message.GPU{ + Brand: "NVIDIA", + Model: parts[0], + Mem: mem, + } + } + + return gpus, nil +} + +// 解析显存容量字符串并返回 float64 类型 +func parseMemory(memStr string) (float64, error) { + var mem float64 + _, err := fmt.Sscanf(memStr, "%f", &mem) + if err != nil { + return 0, fmt.Errorf("显存解析失败: %v", err) + } + return mem, nil +} + +// 获取其他显卡信息(如 Intel 或 AMD) +func getOtherGPUInfo() (map[int]message.GPU, error) { + // 使用 lspci 列出所有显卡设备 + cmd := exec.Command("lspci", "-v") + output, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("无法执行 lspci 命令: %v", err) + } + + // 解析输出,获取显卡信息 + info := strings.TrimSpace(string(output)) + lines := strings.Split(info, "\n") + + gpus := make(map[int]message.GPU) + gpuIndex := 0 + + for _, line := range lines { + // 假设输出中包含 Intel 或 AMD GPU + if strings.Contains(line, "VGA compatible controller") { + if strings.Contains(line, "Intel") || strings.Contains(line, "AMD") { + // 将 Intel/AMD GPU 信息存储到 map 中 + gpus[gpuIndex] = message.GPU{ + Brand: "Intel/AMD", + Model: line, // 这里可以根据实际 lspci 输出提取显卡型号 + Mem: 0, // 无法通过 lspci 获取显存 + } + gpuIndex++ + } + } + } + + if len(gpus) == 0 { + return nil, fmt.Errorf("未找到显卡信息") + } + + return gpus, nil +} + +// 获取所有 GPU 信息 +func (l *LinuxClient) GetGPUInfo() (map[int]message.GPU, error) { + // 尝试获取 NVIDIA GPU 信息 + gpus, err := getNvidiaGPUInfo() + if err == nil { + return gpus, nil + } + + // 如果没有 NVIDIA 显卡,尝试获取其他类型显卡信息 + return getOtherGPUInfo() +} + +// 获取 MAC 地址 +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 + } + } + + return "", fmt.Errorf("MAC address not found") +} + +/* +配置lolminer +#!/bin/bash + +POOL=47.108.221.51:3333 +WALLET=m2test.11x12 +ALGO=NEXA + +./lolMiner --algo $ALGO --pool $POOL --user $WALLET $@ +*/ +func (l *LinuxClient) lolminer(cfg message.ConfigurationMiningMsg) { + l.mu.Lock() + if l.Status != 2 { + log.Fatalf("当前还有挖矿任务正在进行中:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp) + return + } + l.Status = 1 + l.mu.Unlock() + + var address string + if cfg.WalletMining { + address = cfg.WalletAddress + } else { + address = cfg.PoolUser + } + + dir := l.MiningConfig.LolMinerPath + name := filepath.Join(dir, "lolMiner") + 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 + } + // 获取 lolMiner 的进程 ID + fmt.Printf("lolMiner started with PID: %d\n", cmd.Process.Pid) + // 获取当前时间戳(秒级) + currentTimestamp := time.Now().Unix() + endTimestamp := int64(cfg.EndTimestamp) + // 计算目标时间戳和当前时间戳之间的差值 + if endTimestamp <= currentTimestamp { + fmt.Println("目标时间已经到达,直接执行操作") + } 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 process killed.") + } + log.Printf("当前挖矿任务已执行完毕:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp) + l.mu.Lock() + l.Status = 2 + l.mu.Unlock() +} + +/* +配置bzminer +#!/bin/bash + +POOL=47.108.221.51:3333 +WALLET=m2test.11x12 +ALGO=NEXA + +./bzminer -a $ALGO -w $WALLET -p $POOL +*/ +func (l *LinuxClient) bzminer(cfg message.ConfigurationMiningMsg) { + l.mu.Lock() + if l.Status != 2 { + log.Fatalf("当前还有挖矿任务正在进行中:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp) + return + } + l.Status = 1 + l.mu.Unlock() + var address string + if cfg.WalletMining { + address = cfg.WalletAddress + } else { + address = cfg.PoolUser + } + dir := l.MiningConfig.BzMinerPath + name := filepath.Join(dir, "bzminer") + 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 + } + // 获取 bzminer 的进程 ID + fmt.Printf("bzminer started with PID: %d\n", cmd.Process.Pid) + // 获取当前时间戳(秒级) + currentTimestamp := time.Now().Unix() + endTimestamp := int64(cfg.EndTimestamp) + // 计算目标时间戳和当前时间戳之间的差值 + if endTimestamp <= currentTimestamp { + fmt.Println("目标时间已经到达,直接执行操作") + } 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 process killed.") + } + log.Printf("当前挖矿任务已执行完毕:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp) + l.mu.Lock() + l.Status = 2 + l.mu.Unlock() +} + +/* +配置rigel +#!/bin/bash + +POOL=47.108.221.51:3333 +WALLET=m2test +USER=11x12 +ALGO=nexapow + +./rigel -a $ALGO -o $POOL -u $WALLET -w $USER --log-file logs/miner.log +*/ +func (l *LinuxClient) rigel(cfg message.ConfigurationMiningMsg) { + l.mu.Lock() + if l.Status != 2 { + log.Fatalf("当前还有挖矿任务正在进行中:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp) + return + } + l.Status = 1 + l.mu.Unlock() + var address string + if cfg.WalletMining { + address = cfg.WalletAddress + } else { + address = cfg.PoolUser + } + 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"} + 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 + } + // 获取 rigel 的进程 ID + fmt.Printf("rigel started with PID: %d\n", cmd.Process.Pid) + // 获取当前时间戳(秒级) + currentTimestamp := time.Now().Unix() + endTimestamp := int64(cfg.EndTimestamp) + // 计算目标时间戳和当前时间戳之间的差值 + if endTimestamp <= currentTimestamp { + fmt.Println("目标时间已经到达,直接执行操作") + } 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 process killed.") + } + log.Printf("当前挖矿任务已执行完毕:币=%s, 算法=%s, 矿池=%s, 截止时间=%d", cfg.Coin, cfg.Algo, cfg.Pool, cfg.EndTimestamp) + l.mu.Lock() + l.Status = 2 + l.mu.Unlock() +} + +// 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 (l *LinuxClient) Mining(cfg message.ConfigurationMiningMsg) error { + info := cfg.Coin + "-" + cfg.Algo + switch info { + case "XTM-SHA3X": + go l.lolminer(cfg) + case "XNA-KawPow": + go l.bzminer(cfg) + case "CLORE-KawPow": + go l.bzminer(cfg) + case "CFX-Octopus": + go l.rigel(cfg) + case "IRON-IronFish": + go l.lolminer(cfg) + case "NEXA-NexaPow": + go l.lolminer(cfg) + case "KLS-KarlsenHash": + go l.lolminer(cfg) + case "RVN-KawPow": + go l.bzminer(cfg) + case "ERG-Autolykos": + go l.bzminer(cfg) + case "XEL-Xelishashv2": + go l.rigel(cfg) + default: + return fmt.Errorf("不支持%s算法", info) + } + return nil +} diff --git a/internal/src/src.go b/internal/src/src.go new file mode 100644 index 0000000..e453cb7 --- /dev/null +++ b/internal/src/src.go @@ -0,0 +1,63 @@ +package src + +import ( + message "client/internal/msg" + "fmt" + "sync" +) + +type OS interface { + GetGPUInfo() (map[int]message.GPU, error) + Mining(cfg message.ConfigurationMiningMsg) error + GetMACAddress() (string, error) +} + +type SystemServer struct { + mu sync.Mutex + systems map[string]OS +} + +func NewSystemServer() *SystemServer { + return &SystemServer{ + systems: make(map[string]OS), + } +} + +func (s *SystemServer) ResiterSystem(name string, os OS) { + s.mu.Lock() + s.systems[name] = os + s.mu.Unlock() +} + +func (s *SystemServer) GetGPUInfo(osName string) (map[int]message.GPU, error) { + if srv, ok := s.systems[osName]; ok { + gpus, err := srv.GetGPUInfo() + if err != nil { + return map[int]message.GPU{}, fmt.Errorf("获取操作系统(%s)GPU信息失败:%v", osName, err) + } + return gpus, nil + } + return map[int]message.GPU{}, fmt.Errorf("错误的操作系统:%s", osName) +} + +func (s *SystemServer) Mining(osName string, cfg message.ConfigurationMiningMsg) error { + if srv, ok := s.systems[osName]; ok { + err := srv.Mining(cfg) + if err != nil { + return fmt.Errorf("开始挖矿失败:%v", err) + } + return nil + } + return fmt.Errorf("错误的操作系统:%s", osName) +} + +func (s *SystemServer) GetMACAddress(osName string) (string, error) { + if srv, ok := s.systems[osName]; ok { + mac, err := srv.GetMACAddress() + if err != nil { + return "", fmt.Errorf("获取当前机器MAC码失败") + } + return mac, nil + } + return "", fmt.Errorf("错误的操作系统:%s", osName) +} diff --git a/internal/src/windows/windows.go b/internal/src/windows/windows.go new file mode 100644 index 0000000..42f4160 --- /dev/null +++ b/internal/src/windows/windows.go @@ -0,0 +1,149 @@ +package windows + +import ( + message "client/internal/msg" + "fmt" + "os/exec" + "strconv" + "strings" +) + +// 获取 NVIDIA 显卡信息 +func getNvidiaGPUInfo() (map[int]message.GPU, error) { + // 使用 nvidia-smi 列出所有 GPU + cmd := exec.Command("nvidia-smi", "--query-gpu=name,memory.total", "--format=csv,noheader,nounits") + output, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("无法执行 nvidia-smi 命令: %v", err) + } + + // 解析输出 + info := strings.TrimSpace(string(output)) + lines := strings.Split(info, "\n") + + gpus := make(map[int]message.GPU) + + for i, line := range lines { + parts := strings.Split(strings.TrimSpace(line), ", ") + if len(parts) < 2 { + return nil, fmt.Errorf("nvidia-smi 输出格式错误") + } + + // 解析显存容量 + mem, err := parseMemory(parts[1]) + if err != nil { + return nil, fmt.Errorf("无法解析显存容量: %v", err) + } + + // 将每个 GPU 的信息存储到 map 中 + gpus[i] = message.GPU{ + Brand: "NVIDIA", + Model: parts[0], + Mem: mem, + } + } + + return gpus, nil +} + +// 解析显存容量字符串并返回 float64 类型 +func parseMemory(memStr string) (float64, error) { + var mem float64 + _, err := fmt.Sscanf(memStr, "%f", &mem) + if err != nil { + return 0, fmt.Errorf("显存解析失败: %v", err) + } + return mem, nil +} + +// 获取 AMD 显卡信息 +func getAmdGPUInfo() (map[int]message.GPU, error) { + // 使用 wmic 列出所有显卡信息 + cmd := exec.Command("wmic", "path", "Win32_VideoController", "get", "Name,AdapterRAM") + output, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("无法执行 wmic 命令: %v", err) + } + + // 解析输出 + info := strings.TrimSpace(string(output)) + lines := strings.Split(info, "\n") + + gpus := make(map[int]message.GPU) + gpuIndex := 0 + + for _, line := range lines[1:] { // 跳过标题行 + 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 len(gpus) == 0 { + return nil, fmt.Errorf("未找到显卡信息") + } + + 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) { + // 尝试获取 NVIDIA GPU 信息 + gpus, err := getNvidiaGPUInfo() + if err == nil { + return gpus, nil + } + + // 尝试获取 AMD GPU 信息 + gpus, err = getAmdGPUInfo() + if err == nil { + return gpus, nil + } + + return nil, fmt.Errorf("未找到任何显卡信息") +} + +/* +{ + "coin-algo": { + "lolminer": { + "pool_name": "url" + }, + "bzminer": { + "pool_name": "url" + }, + "rigel": { + "pool_name": "url" + } + } +} +*/ diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 0000000..33b2b6a --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,89 @@ +package utils + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" + + "github.com/google/uuid" +) + +func GetYMDMHS() string { + // 获取当前时间 + currentTime := time.Now() + + // 格式化时间为 "年-月-日 时:分:秒" + formattedTime := currentTime.Format("2006-01-02 15:04:05") + + return formattedTime +} + +func ReadFile(filePath string) (string, error) { + data, err := ioutil.ReadFile(filePath) + if err != nil { + return "", fmt.Errorf("读取身份信息失败:%v", err) + } + return string(data), nil +} + +func FileExists(path, filename string) (bool, error) { + // 构造完整的文件路径 + filePath := filepath.Join(path, filename) + + // 使用 os.Stat 检查文件是否存在 + _, err := os.Stat(filePath) + if err != nil { + // 如果错误是 "文件不存在",则返回 false + if os.IsNotExist(err) { + return false, nil + } + // 如果其他错误,返回错误信息 + return false, err + } + + // 如果没有错误,文件存在 + return true, nil +} + +// 检查文件路径是否存在 +func CheckFileExists(path string) bool { + _, err := os.Stat(path) + return !os.IsNotExist(err) +} + +func WirteFile(filePath, content string) { + // // 要写入的文件路径 + // filePath := "example.txt" + // // 要写入的内容 + // content := "Hello, Go! This is a test." + + // 创建/打开文件,覆盖写入 + file, err := os.Create(filePath) // 这里会覆盖原有内容,如果文件不存在会创建新文件 + if err != nil { + fmt.Println("文件创建失败:", err) + return + } + defer file.Close() // 确保文件在写入后关闭 + + // 写入内容 + _, err = file.WriteString(content) + if err != nil { + fmt.Println("写入文件失败:", err) + return + } + + fmt.Println("文件写入成功") +} + +func GenerateUUID() string { + // 生成一个新的 UUID + id := uuid.New() + return id.String() +} + +// func CheckMiningMsg(msg message.ConfigurationMiningMsg) bool{ +// coin, algo, pool := msg.Coin, msg.Algo, msg.Pool + +// }