//go:build !windows package client import ( "os" "os/exec" "os/user" "runtime" "strconv" "strings" ) func collectSysCheckPlatform(r *FullSysCheckReport) { hw := &SysCheckHardware{} if out, err := exec.Command("uname", "-a").CombinedOutput(); err == nil { parts := strings.Fields(string(out)) if len(parts) >= 3 { hw.Manufacturer = parts[2] } } if runtime.GOOS == "darwin" { if out, err := exec.Command("sysctl", "-n", "hw.model").CombinedOutput(); err == nil { hw.Model = strings.TrimSpace(string(out)) } if out, err := exec.Command("sysctl", "-n", "hw.memsize").CombinedOutput(); err == nil { if n, err := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64); err == nil { hw.MemoryGB = float64(n) / (1024 * 1024 * 1024) } } } else { if out, err := exec.Command("sh", "-c", "grep -m1 'model name' /proc/cpuinfo | cut -d: -f2").CombinedOutput(); err == nil { hw.CPUs = []SysCheckCPU{{Name: strings.TrimSpace(string(out))}} } if out, err := exec.Command("sh", "-c", "grep -c ^processor /proc/cpuinfo").CombinedOutput(); err == nil { if n, err := strconv.Atoi(strings.TrimSpace(string(out))); err == nil && len(hw.CPUs) > 0 { hw.CPUs[0].Logical = n } } if out, err := exec.Command("free", "-b").CombinedOutput(); err == nil { lines := strings.Split(string(out), "\n") if len(lines) > 1 { fields := strings.Fields(lines[1]) if len(fields) >= 2 { if total, err := strconv.ParseInt(fields[1], 10, 64); err == nil { hw.MemoryGB = float64(total) / (1024 * 1024 * 1024) } } } } } if out, err := exec.Command("df", "-h").CombinedOutput(); err == nil { hw.Disks = parseDfOutput(string(out)) } if out, err := exec.Command("sh", "-c", "cat /proc/uptime 2>/dev/null || sysctl -n kern.boottime 2>/dev/null").CombinedOutput(); err == nil { fields := strings.Fields(string(out)) if len(fields) > 0 { if sec, err := strconv.ParseFloat(fields[0], 64); err == nil { hw.UptimeHours = sec / 3600 } } } r.Hardware = hw if r.Identity == nil { r.Identity = &SysCheckIdentity{} } if u, err := user.Current(); err == nil { r.Identity.Username = u.Username } r.Identity.MACAddress = primaryMACAddress() if r.Environment == nil { r.Environment = &SysCheckEnvironment{} } r.Environment.HomeDir = os.Getenv("HOME") r.Environment.TempDir = os.Getenv("TMPDIR") if r.Environment.TempDir == "" { r.Environment.TempDir = "/tmp" } if r.Network == nil { r.Network = &SysCheckNetwork{} } if out, err := exec.Command("sh", "-c", "ip route show default 2>/dev/null || route -n get default 2>/dev/null").CombinedOutput(); err == nil { r.Network.RoutesSummary = strings.TrimSpace(string(out)) for _, line := range strings.Split(string(out), "\n") { if strings.Contains(line, "via") || strings.Contains(line, "gateway:") { fields := strings.Fields(line) for i, f := range fields { if f == "via" && i+1 < len(fields) { r.Network.DefaultGateway = fields[i+1] break } if strings.HasPrefix(f, "gateway:") { r.Network.DefaultGateway = strings.TrimPrefix(f, "gateway:") break } } break } } } } func parseDfOutput(raw string) []SysCheckDisk { var disks []SysCheckDisk for _, line := range strings.Split(raw, "\n")[1:] { fields := strings.Fields(line) if len(fields) < 6 { continue } mount := fields[len(fields)-1] disks = append(disks, SysCheckDisk{ Mount: mount, FSType: fields[0], TotalGB: parseSizeGB(fields[1]), FreeGB: parseSizeGB(fields[3]), }) } return disks } func parseSizeGB(s string) float64 { s = strings.TrimSuffix(s, "G") s = strings.TrimSuffix(s, "T") s = strings.TrimSuffix(s, "M") f, _ := strconv.ParseFloat(s, 64) if strings.HasSuffix(s, "T") { return f * 1024 } return f }