414 lines
10 KiB
Go
414 lines
10 KiB
Go
package installer
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"yizhisec.com/hsv2/forge/pkg/logger"
|
|
"yizhisec.com/hsv2/forge/pkg/syscheck"
|
|
"yizhisec.com/hsv2/forge/pkg/tool/human"
|
|
)
|
|
|
|
type CheckOption func(*checkOpt)
|
|
type checkOpt struct {
|
|
ignoreDisk bool
|
|
ignoreMemory bool
|
|
ignoreCPU bool
|
|
noWriteDown bool
|
|
}
|
|
|
|
func WithIgnoreDiskCheck(ignore bool) CheckOption {
|
|
return func(o *checkOpt) {
|
|
o.ignoreDisk = ignore
|
|
}
|
|
}
|
|
|
|
func WithIgnoreMemoryCheck(ignore bool) CheckOption {
|
|
return func(o *checkOpt) {
|
|
o.ignoreMemory = ignore
|
|
}
|
|
}
|
|
|
|
func WithIgnoreCPUCheck(ignore bool) CheckOption {
|
|
return func(o *checkOpt) {
|
|
o.ignoreCPU = ignore
|
|
}
|
|
}
|
|
|
|
func WithNoWriteDown(noWriteDown bool) CheckOption {
|
|
return func(o *checkOpt) {
|
|
o.noWriteDown = noWriteDown
|
|
}
|
|
}
|
|
|
|
type HardwareCheckResult struct {
|
|
Timestamp int64 // ms
|
|
DiskSize int64 // bytes, if err, -1; if ignore, 0
|
|
DiskReadSpeed int64 // bytes/s, if err, -1; if ignore, 0
|
|
DiskWriteSpeed int64 // bytes/s, if err, -1; if ignore, 0
|
|
MemorySize int64 // bytes, if err, -1; if ignore, 0
|
|
MemoryReadSpeed int64 // bytes/s, if err, -1; if ignore, 0
|
|
MemoryWriteSpeed int64 // bytes/s, if err, -1; if ignore, 0
|
|
CPUCores int64 // if err, -1; if ignore, 0
|
|
CPUFrequency int64 // MHz, if err, -1; if ignore, 0
|
|
CPUSupportAES bool
|
|
CPUIsX86V2 bool
|
|
}
|
|
|
|
func (h *HardwareCheckResult) Write(filename string) error {
|
|
content := []byte(fmt.Sprintf(`timestamp=%d
|
|
disk_size=%d
|
|
disk_read_speed=%d
|
|
disk_write_speed=%d
|
|
memory_size=%d
|
|
memory_read_speed=%d
|
|
memory_write_speed=%d
|
|
cpu_cores=%d
|
|
cpu_frequency=%d
|
|
cpu_support_aes=%t
|
|
cpu_is_x86_v2=%t`,
|
|
h.Timestamp,
|
|
h.DiskSize,
|
|
h.DiskReadSpeed,
|
|
h.DiskWriteSpeed,
|
|
h.MemorySize,
|
|
h.MemoryReadSpeed,
|
|
h.MemoryWriteSpeed,
|
|
h.CPUCores,
|
|
h.CPUFrequency,
|
|
h.CPUSupportAES,
|
|
h.CPUIsX86V2,
|
|
))
|
|
return os.WriteFile(filename, content, 0644)
|
|
}
|
|
|
|
func (h *HardwareCheckResult) Load(filename string) error {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open file: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
|
|
parts := strings.SplitN(line, "=", 2)
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
key := strings.TrimSpace(parts[0])
|
|
value := strings.TrimSpace(parts[1])
|
|
|
|
switch key {
|
|
case "timestamp":
|
|
v, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
h.Timestamp = -1
|
|
} else {
|
|
h.Timestamp = v
|
|
}
|
|
case "disk_size":
|
|
v, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
h.DiskSize = -1
|
|
} else {
|
|
h.DiskSize = v
|
|
}
|
|
case "disk_read_speed":
|
|
v, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
h.DiskReadSpeed = -1
|
|
} else {
|
|
h.DiskReadSpeed = v
|
|
}
|
|
case "disk_write_speed":
|
|
v, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
h.DiskWriteSpeed = -1
|
|
} else {
|
|
h.DiskWriteSpeed = v
|
|
}
|
|
case "memory_size":
|
|
v, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
h.MemorySize = -1
|
|
} else {
|
|
h.MemorySize = v
|
|
}
|
|
case "memory_read_speed":
|
|
v, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
h.MemoryReadSpeed = -1
|
|
} else {
|
|
h.MemoryReadSpeed = v
|
|
}
|
|
case "memory_write_speed":
|
|
v, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
h.MemoryWriteSpeed = -1
|
|
} else {
|
|
h.MemoryWriteSpeed = v
|
|
}
|
|
case "cpu_cores":
|
|
v, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
h.CPUCores = -1
|
|
} else {
|
|
h.CPUCores = v
|
|
}
|
|
case "cpu_frequency":
|
|
v, err := strconv.ParseInt(value, 10, 64)
|
|
if err != nil {
|
|
h.CPUFrequency = -1
|
|
} else {
|
|
h.CPUFrequency = v
|
|
}
|
|
case "cpu_support_aes":
|
|
v, err := strconv.ParseBool(value)
|
|
if err == nil {
|
|
h.CPUSupportAES = v
|
|
}
|
|
// bool fields don't use -1, keep default false on error
|
|
case "cpu_is_x86_v2":
|
|
v, err := strconv.ParseBool(value)
|
|
if err == nil {
|
|
h.CPUIsX86V2 = v
|
|
}
|
|
// bool fields don't use -1, keep default false on error
|
|
}
|
|
}
|
|
|
|
if err := scanner.Err(); err != nil {
|
|
return fmt.Errorf("failed to read file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *HardwareCheckResult) Pass(opts ...CheckOption) error {
|
|
const (
|
|
DISK_SIZE int64 = 480 * 1024 * 1024 * 1024 // 480GB
|
|
DISK_READ_SPEED int64 = 1024 * 1024 * 1024 // 1024MB/s
|
|
DISK_WRITE_SPEED int64 = 500 * 1024 * 1024 // 500MB/s
|
|
MEMORY_SIZE int64 = 15.5 * 1024 * 1024 * 1024 // 15.5GB
|
|
MEMORY_READ_SPEED int64 = 1024 * 1024 * 1024 // 1024MB/s
|
|
MEMORY_WRITE_SPEED int64 = 500 * 1024 * 1024 // 500MB/s
|
|
CPUCORES int64 = 8
|
|
CPU_FREQUENCY int64 = 2.0 * 1024 // 2.0GHz
|
|
CPU_NEED_V2 = true
|
|
CPU_NEED_AES = true
|
|
)
|
|
|
|
var (
|
|
now = time.Now()
|
|
o = &checkOpt{}
|
|
)
|
|
|
|
for _, fn := range opts {
|
|
fn(o)
|
|
}
|
|
|
|
if h.Timestamp < now.AddDate(0, 0, -1).UnixMilli() {
|
|
return fmt.Errorf("上次检测结果已失效, 请重新检测")
|
|
}
|
|
|
|
if o.ignoreDisk || h.DiskSize+h.DiskReadSpeed+h.DiskWriteSpeed == -3 {
|
|
goto PASS_MEMORY
|
|
}
|
|
|
|
if h.DiskSize < DISK_SIZE {
|
|
return fmt.Errorf("磁盘大小不满足, 需要: %s, 实际: %s", human.Size(DISK_SIZE), human.Size(h.DiskSize))
|
|
}
|
|
|
|
if h.DiskReadSpeed < DISK_READ_SPEED {
|
|
return fmt.Errorf("磁盘读速度不满足, 需要: %s, 实际: %s", human.Size(DISK_READ_SPEED), human.Size(h.DiskReadSpeed))
|
|
}
|
|
|
|
if h.DiskWriteSpeed < DISK_WRITE_SPEED {
|
|
return fmt.Errorf("磁盘写速度不满足, 需要: %s, 实际: %s", human.Size(DISK_WRITE_SPEED), human.Size(h.DiskWriteSpeed))
|
|
}
|
|
|
|
PASS_MEMORY:
|
|
|
|
if o.ignoreMemory || h.MemorySize+h.MemoryReadSpeed+h.MemoryWriteSpeed == -3 {
|
|
goto PASS_CPU
|
|
}
|
|
|
|
if h.MemorySize < MEMORY_SIZE {
|
|
return fmt.Errorf("内存大小不满足, 需要: %s, 实际: %s", human.Size(MEMORY_SIZE), human.Size(h.MemorySize))
|
|
}
|
|
|
|
if h.MemoryReadSpeed < MEMORY_READ_SPEED {
|
|
return fmt.Errorf("内存读速度不满足, 需要: %s, 实际: %s", human.Size(MEMORY_READ_SPEED), human.Size(h.MemoryReadSpeed))
|
|
}
|
|
|
|
if h.MemoryWriteSpeed < MEMORY_WRITE_SPEED {
|
|
return fmt.Errorf("内存写速度不满足, 需要: %s, 实际: %s", human.Size(MEMORY_WRITE_SPEED), human.Size(h.MemoryWriteSpeed))
|
|
}
|
|
|
|
PASS_CPU:
|
|
|
|
if o.ignoreCPU || h.CPUCores+h.CPUFrequency == -2 {
|
|
goto END
|
|
}
|
|
if h.CPUCores < CPUCORES {
|
|
return fmt.Errorf("cpu 核心数不满足, 需要: %d, 实际: %d", CPUCORES, h.CPUCores)
|
|
}
|
|
|
|
if h.CPUFrequency < CPU_FREQUENCY {
|
|
return fmt.Errorf("cpu 频率不满足, 需要: %s, 实际: %s", human.Size(CPU_FREQUENCY), human.Size(h.CPUFrequency))
|
|
}
|
|
|
|
if CPU_NEED_V2 && !h.CPUIsX86V2 {
|
|
return fmt.Errorf("cpu 不支持aes")
|
|
}
|
|
|
|
if CPU_NEED_AES && !h.CPUSupportAES {
|
|
return fmt.Errorf("cpu 不支持 x86_v2s")
|
|
}
|
|
|
|
END:
|
|
|
|
return nil
|
|
}
|
|
|
|
func (i *installer) HardwareCheck(ctx context.Context, opts ...CheckOption) error {
|
|
var (
|
|
err error
|
|
o = &checkOpt{}
|
|
now = time.Now()
|
|
result = &HardwareCheckResult{
|
|
Timestamp: now.UnixMilli(),
|
|
}
|
|
cpuInfo syscheck.CPUInfo
|
|
dir string
|
|
)
|
|
|
|
for _, fn := range opts {
|
|
fn(o)
|
|
}
|
|
|
|
logger.Info("✅ 开始目标机器系统检测...")
|
|
|
|
if o.ignoreDisk {
|
|
logger.Warn("⚠️ 跳过磁盘检测")
|
|
result.DiskSize = -1
|
|
result.DiskReadSpeed = -1
|
|
result.DiskWriteSpeed = -1
|
|
goto CHECK_MEMORY
|
|
}
|
|
|
|
if result.DiskSize, err = syscheck.GetDiskSpace(ctx); err != nil {
|
|
logger.Debug("❌ installer.HardwareCheck: Failed to get disk space: %v", err)
|
|
return err
|
|
}
|
|
|
|
logger.Info("💾 磁盘容量: %s", human.Size(result.DiskSize))
|
|
|
|
if result.DiskReadSpeed, result.DiskWriteSpeed, err = syscheck.GetDiskSpeed(ctx); err != nil {
|
|
logger.Debug("❌ installer.HardwareCheck: Failed to get disk speed: %v", err)
|
|
return err
|
|
}
|
|
|
|
logger.Info("💾 磁盘速率: 读取(%s/每秒), 写入(%s/每秒)", human.Size(result.DiskReadSpeed), human.Size(result.DiskWriteSpeed))
|
|
|
|
CHECK_MEMORY:
|
|
|
|
if o.ignoreMemory {
|
|
logger.Warn("⚠️ 跳过内存检测")
|
|
result.MemorySize = -1
|
|
result.MemoryReadSpeed = -1
|
|
result.MemoryWriteSpeed = -1
|
|
goto CHECK_CPU
|
|
}
|
|
|
|
if result.MemorySize, err = syscheck.GetMemorySpace(ctx); err != nil {
|
|
logger.Debug("❌ installer.HardwareCheck: Failed to get memory size: %v", err)
|
|
return err
|
|
}
|
|
|
|
logger.Info("💿 内存容量: %s", human.Size(result.MemorySize))
|
|
|
|
if result.MemoryReadSpeed, result.MemoryWriteSpeed, err = syscheck.GetMemorySpeed(ctx); err != nil {
|
|
logger.Debug("❌ installer.HardwareCheck: Failed to get memory speed: %v", err)
|
|
return err
|
|
}
|
|
|
|
logger.Info("💿 内存速率: 读取(%s/每秒), 写入(%s/每秒)", human.Size(result.MemoryReadSpeed), human.Size(result.MemoryWriteSpeed))
|
|
|
|
CHECK_CPU:
|
|
if o.ignoreCPU {
|
|
logger.Warn("⚠️ 跳过 CPU 检测")
|
|
result.CPUCores = -1
|
|
result.CPUFrequency = -1
|
|
result.CPUSupportAES = false
|
|
result.CPUIsX86V2 = false
|
|
goto END
|
|
}
|
|
|
|
if cpuInfo, err = syscheck.GetCPUInfo(ctx); err != nil {
|
|
logger.Debug("❌ installer.HardwareCheck: Failed to get CPU info: %v", err)
|
|
return err
|
|
}
|
|
|
|
result.CPUCores = cpuInfo.Cores
|
|
result.CPUFrequency = cpuInfo.FrequencyMHz
|
|
result.CPUSupportAES = cpuInfo.SupportAES
|
|
result.CPUIsX86V2 = cpuInfo.IsX86V2
|
|
|
|
logger.Info("🧮 CPU 核心数: %d", result.CPUCores)
|
|
logger.Info("🧮 CPU 频率: %d Mhz", result.CPUFrequency)
|
|
logger.Info("🧮 CPU 支持 AES: %t", result.CPUSupportAES)
|
|
END:
|
|
|
|
if dir, err = os.UserHomeDir(); err != nil {
|
|
logger.Debug("❌ installer.HardwareCheck: Failed to get user home directory: %v", err)
|
|
return err
|
|
}
|
|
|
|
if err = result.Write(filepath.Join(dir, ".hsv2-installation")); err != nil {
|
|
logger.Debug("❌ installer.HardwareCheck: Failed to write installation file: %v", err)
|
|
return err
|
|
}
|
|
|
|
if err = result.Pass(opts...); err != nil {
|
|
logger.Error("❌ %s", err.Error())
|
|
return nil
|
|
}
|
|
|
|
logger.Info("✅ 检测完成")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (*installer) CheckOK(ctx context.Context) error {
|
|
var (
|
|
err error
|
|
h = &HardwareCheckResult{}
|
|
dir string
|
|
)
|
|
|
|
if dir, err = os.UserHomeDir(); err != nil {
|
|
logger.Debug("❌ installer.HardwareCheck: Failed to get user home directory: %v", err)
|
|
return err
|
|
}
|
|
|
|
if err = h.Load(filepath.Join(dir, ".hsv2-installation")); err != nil {
|
|
logger.Debug("❌ installer.HardwareCheck: Failed to load installation file: %v", err)
|
|
return err
|
|
}
|
|
|
|
return h.Pass()
|
|
}
|