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() }