package system import ( "context" "fmt" "runtime" "strings" "git.gostacks.org/iwasforcedtobehere/WiseTLP/autotlp/pkg/types" "git.gostacks.org/iwasforcedtobehere/WiseTLP/autotlp/pkg/utils" ) type Info struct { Distribution string Version string Architecture string PackageManager string } func DetectSystem(ctx context.Context) (*Info, error) { if runtime.GOOS != "linux" { return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) } info := &Info{ Architecture: runtime.GOARCH, } if err := detectFromOSRelease(info); err != nil { if err := detectFromLSBRelease(info); err != nil { return nil, fmt.Errorf("failed to detect Linux distribution: %w", err) } } info.PackageManager = detectPackageManager(info.Distribution) return info, nil } func detectFromOSRelease(info *Info) error { const osReleasePath = "/etc/os-release" if !utils.FileExists(osReleasePath) { return fmt.Errorf("os-release file not found") } lines, err := utils.ReadFileLines(osReleasePath) if err != nil { return fmt.Errorf("failed to read os-release: %w", err) } osInfo := make(map[string]string) for _, line := range lines { if key, value, ok := utils.ParseKeyValue(line); ok { osInfo[key] = value } } if id, exists := osInfo["ID"]; exists { info.Distribution = id } if version, exists := osInfo["VERSION_ID"]; exists { info.Version = version } else if version, exists := osInfo["VERSION"]; exists { info.Version = version } if info.Distribution == "" { return fmt.Errorf("could not determine distribution from os-release") } return nil } func detectFromLSBRelease(info *Info) error { if !utils.CommandExists("lsb_release") { return fmt.Errorf("lsb_release command not available") } distro, err := utils.RunCommand("lsb_release", "-si") if err != nil { return fmt.Errorf("failed to get distribution ID: %w", err) } info.Distribution = strings.ToLower(distro) version, err := utils.RunCommand("lsb_release", "-sr") if err != nil { return fmt.Errorf("failed to get distribution version: %w", err) } info.Version = version return nil } func detectPackageManager(distro string) string { switch strings.ToLower(distro) { case "ubuntu", "debian", "linuxmint", "elementary", "pop": return "apt" case "fedora", "rhel", "centos", "rocky", "almalinux": return "dnf" case "opensuse", "suse", "opensuse-leap", "opensuse-tumbleweed": return "zypper" case "arch", "manjaro", "endeavouros", "garuda": return "pacman" case "gentoo": return "portage" case "alpine": return "apk" default: if utils.CommandExists("apt") { return "apt" } else if utils.CommandExists("dnf") { return "dnf" } else if utils.CommandExists("yum") { return "yum" } else if utils.CommandExists("zypper") { return "zypper" } else if utils.CommandExists("pacman") { return "pacman" } else if utils.CommandExists("apk") { return "apk" } return "unknown" } } func GatherSystemInfo(ctx context.Context) (*types.SystemInfo, error) { sysInfo := &types.SystemInfo{} basicInfo, err := DetectSystem(ctx) if err != nil { return nil, fmt.Errorf("failed to detect system: %w", err) } sysInfo.Distribution = types.DistributionInfo{ ID: basicInfo.Distribution, Version: basicInfo.Version, PackageManager: basicInfo.PackageManager, } if cpuInfo, err := gatherCPUInfo(); err == nil { sysInfo.CPU = *cpuInfo } if memInfo, err := gatherMemoryInfo(); err == nil { sysInfo.Memory = *memInfo } if batteryInfo, err := gatherBatteryInfo(); err == nil { sysInfo.Battery = batteryInfo } if powerInfo, err := gatherPowerSupplyInfo(); err == nil { sysInfo.PowerSupply = *powerInfo } if kernelInfo, err := gatherKernelInfo(); err == nil { sysInfo.Kernel = *kernelInfo } if hwInfo, err := gatherHardwareInfo(); err == nil { sysInfo.Hardware = *hwInfo } return sysInfo, nil } func gatherCPUInfo() (*types.CPUInfo, error) { cpuInfo := &types.CPUInfo{} if utils.FileExists("/proc/cpuinfo") { lines, err := utils.ReadFileLines("/proc/cpuinfo") if err != nil { return nil, err } for _, line := range lines { if key, value, ok := utils.ParseKeyValue(line); ok { switch strings.ToLower(key) { case "model name": if cpuInfo.Model == "" { cpuInfo.Model = value } case "vendor_id": if cpuInfo.Vendor == "" { cpuInfo.Vendor = value } case "processor": cpuInfo.Cores++ } } } } if utils.FileExists("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor") { governor, err := utils.ReadFirstLine("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor") if err == nil { cpuInfo.Governor = governor } } if utils.FileExists("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq") { maxFreq, err := utils.ReadFirstLine("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq") if err == nil { cpuInfo.MaxFrequency = utils.ParseInt64(maxFreq) / 1000 } } if utils.FileExists("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq") { minFreq, err := utils.ReadFirstLine("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq") if err == nil { cpuInfo.MinFrequency = utils.ParseInt64(minFreq) / 1000 } } cpuInfo.Architecture = runtime.GOARCH return cpuInfo, nil } func gatherMemoryInfo() (*types.MemoryInfo, error) { memInfo := &types.MemoryInfo{} if !utils.FileExists("/proc/meminfo") { return nil, fmt.Errorf("/proc/meminfo not found") } lines, err := utils.ReadFileLines("/proc/meminfo") if err != nil { return nil, err } for _, line := range lines { parts := strings.Fields(line) if len(parts) < 2 { continue } key := strings.TrimSuffix(parts[0], ":") value := utils.ParseInt64(parts[1]) switch key { case "MemTotal": memInfo.Total = value / 1024 case "MemAvailable": memInfo.Available = value / 1024 case "SwapTotal": memInfo.SwapTotal = value / 1024 case "SwapFree": swapFree := value / 1024 memInfo.SwapUsed = memInfo.SwapTotal - swapFree } } memInfo.Used = memInfo.Total - memInfo.Available return memInfo, nil } func gatherBatteryInfo() (*types.BatteryInfo, error) { batteryPath := "/sys/class/power_supply/BAT0" if !utils.FileExists(batteryPath) { batteryPath = "/sys/class/power_supply/BAT1" if !utils.FileExists(batteryPath) { return nil, fmt.Errorf("no battery found") } } batteryInfo := &types.BatteryInfo{Present: true} properties := map[string]*string{ "status": &batteryInfo.Status, "manufacturer": &batteryInfo.Manufacturer, "model_name": &batteryInfo.Model, "technology": &batteryInfo.Technology, } for prop, field := range properties { if value, err := utils.ReadFirstLine(batteryPath + "/" + prop); err == nil { *field = value } } if value, err := utils.ReadFirstLine(batteryPath + "/capacity"); err == nil { batteryInfo.Capacity = utils.ParseInt(value) } if value, err := utils.ReadFirstLine(batteryPath + "/energy_full"); err == nil { batteryInfo.EnergyFull = utils.ParseInt64(value) / 1000000 } if value, err := utils.ReadFirstLine(batteryPath + "/energy_now"); err == nil { batteryInfo.EnergyNow = utils.ParseInt64(value) / 1000000 } if value, err := utils.ReadFirstLine(batteryPath + "/power_now"); err == nil { batteryInfo.PowerNow = utils.ParseInt64(value) / 1000000 } if value, err := utils.ReadFirstLine(batteryPath + "/cycle_count"); err == nil { batteryInfo.CycleCount = utils.ParseInt(value) } if value, err := utils.ReadFirstLine(batteryPath + "/energy_full_design"); err == nil { batteryInfo.DesignCapacity = utils.ParseInt64(value) / 1000000 } return batteryInfo, nil } func gatherPowerSupplyInfo() (*types.PowerSupplyInfo, error) { powerInfo := &types.PowerSupplyInfo{} acPaths := []string{ "/sys/class/power_supply/ADP0", "/sys/class/power_supply/ADP1", "/sys/class/power_supply/AC", "/sys/class/power_supply/ACAD", } for _, path := range acPaths { if utils.FileExists(path + "/online") { if online, err := utils.ReadFirstLine(path + "/online"); err == nil { powerInfo.ACConnected = online == "1" powerInfo.Online = powerInfo.ACConnected break } } } powerInfo.Type = "AC" return powerInfo, nil } func gatherKernelInfo() (*types.KernelInfo, error) { kernelInfo := &types.KernelInfo{ Parameters: make(map[string]string), } if version, err := utils.ReadFirstLine("/proc/version"); err == nil { parts := strings.Fields(version) if len(parts) >= 3 { kernelInfo.Version = parts[2] } } if release, err := utils.ReadFirstLine("/proc/sys/kernel/osrelease"); err == nil { kernelInfo.Release = release } if cmdline, err := utils.ReadFirstLine("/proc/cmdline"); err == nil { params := strings.Fields(cmdline) for _, param := range params { if key, value, ok := utils.ParseKeyValue(param); ok { kernelInfo.Parameters[key] = value } else { kernelInfo.Parameters[param] = "" } } } return kernelInfo, nil } func gatherHardwareInfo() (*types.HardwareInfo, error) { hwInfo := &types.HardwareInfo{} if utils.FileExists("/sys/class/dmi/id/chassis_type") { if chassis, err := utils.ReadFirstLine("/sys/class/dmi/id/chassis_type"); err == nil { hwInfo.Chassis = getChassisType(utils.ParseInt(chassis)) } } if utils.FileExists("/sys/class/dmi/id/sys_vendor") { if vendor, err := utils.ReadFirstLine("/sys/class/dmi/id/sys_vendor"); err == nil { hwInfo.Manufacturer = vendor } } if utils.FileExists("/sys/class/dmi/id/product_name") { if product, err := utils.ReadFirstLine("/sys/class/dmi/id/product_name"); err == nil { hwInfo.ProductName = product } } hwInfo.StorageDevices = gatherStorageInfo() return hwInfo, nil } func getChassisType(chassisType int) string { types := map[int]string{ 1: "Other", 2: "Unknown", 3: "Desktop", 4: "Low Profile Desktop", 5: "Pizza Box", 6: "Mini Tower", 7: "Tower", 8: "Portable", 9: "Laptop", 10: "Notebook", 11: "Hand Held", 12: "Docking Station", 13: "All In One", 14: "Sub Notebook", 15: "Space-saving", 16: "Lunch Box", 17: "Main Server Chassis", 18: "Expansion Chassis", 19: "Sub Chassis", 20: "Bus Expansion Chassis", 21: "Peripheral Chassis", 22: "RAID Chassis", 23: "Rack Mount Chassis", 24: "Sealed-case PC", 25: "Multi-system", 26: "CompactPCI", 27: "AdvancedTCA", 28: "Blade", 29: "Blade Enclosing", } if desc, exists := types[chassisType]; exists { return desc } return "Unknown" } func gatherStorageInfo() []types.StorageInfo { var devices []types.StorageInfo if !utils.FileExists("/proc/partitions") { return devices } lines, err := utils.ReadFileLines("/proc/partitions") if err != nil { return devices } for _, line := range lines { fields := strings.Fields(line) if len(fields) < 4 { continue } deviceName := fields[3] if strings.HasPrefix(line, "major") || strings.Contains(deviceName, "loop") || len(deviceName) > 3 && (deviceName[len(deviceName)-1] >= '0' && deviceName[len(deviceName)-1] <= '9') { continue } device := types.StorageInfo{ Device: "/dev/" + deviceName, Size: utils.ParseInt64(fields[2]) / 1024 / 1024, } rotationalPath := fmt.Sprintf("/sys/block/%s/queue/rotational", deviceName) if utils.FileExists(rotationalPath) { if rotational, err := utils.ReadFirstLine(rotationalPath); err == nil { device.Rotational = rotational == "1" if device.Rotational { device.Type = "HDD" } else if strings.HasPrefix(deviceName, "nvme") { device.Type = "NVMe" } else { device.Type = "SSD" } } } modelPath := fmt.Sprintf("/sys/block/%s/device/model", deviceName) if utils.FileExists(modelPath) { if model, err := utils.ReadFirstLine(modelPath); err == nil { device.Model = strings.TrimSpace(model) } } devices = append(devices, device) } return devices }