package inventory import ( "os" "os/exec" "runtime" "strings" "github.com/fleetforge/forge-agent/internal/pkgmanager" "github.com/fleetforge/forge-agent/internal/svcmgr" "github.com/shirou/gopsutil/v3/cpu" "github.com/shirou/gopsutil/v3/disk" "github.com/shirou/gopsutil/v3/mem" "github.com/shirou/gopsutil/v3/net" ) func execCmd(name string, args ...string) (string, error) { cmd := exec.Command(name, args...) out, err := cmd.Output() return string(out), err } type NetworkInterface struct { Name string `json:"name"` Addresses []string `json:"addresses"` MAC string `json:"mac_address,omitempty"` } type SystemInfo struct { Hostname string Platform string OSName string OSVersion string Architecture string KernelVersion string CPUModel string CPUCores int TotalMemory uint64 TotalDisk uint64 IPAddress string NetworkInterfaces []NetworkInterface Packages []pkgmanager.PackageInfo Services []svcmgr.ServiceInfo } func CollectSystemInfo() (*SystemInfo, error) { hostname, _ := os.Hostname() info := &SystemInfo{ Hostname: hostname, Platform: runtime.GOOS, Architecture: runtime.GOARCH, } switch runtime.GOOS { case "linux": info.OSName = "Linux" if data, err := os.ReadFile("/etc/os-release"); err == nil { lines := strings.Split(string(data), "\n") for _, line := range lines { if strings.HasPrefix(line, "PRETTY_NAME=") { info.OSName = strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"") } if strings.HasPrefix(line, "VERSION_ID=") { info.OSVersion = strings.Trim(strings.TrimPrefix(line, "VERSION_ID="), "\"") } } } if data, err := os.ReadFile("/proc/version"); err == nil { parts := strings.Fields(string(data)) if len(parts) >= 3 { info.KernelVersion = parts[2] } } case "darwin": info.OSName = "macOS" if out, err := execCmd("sw_vers", "-productVersion"); err == nil { info.OSVersion = strings.TrimSpace(out) } if out, err := execCmd("uname", "-r"); err == nil { info.KernelVersion = strings.TrimSpace(out) } case "windows": info.OSName = "Windows" if out, err := execCmd("cmd", "/c", "ver"); err == nil { out = strings.TrimSpace(out) if start := strings.Index(out, "[Version "); start != -1 { ver := out[start+len("[Version "):] if end := strings.Index(ver, "]"); end != -1 { info.OSVersion = ver[:end] } } if info.OSVersion == "" { info.OSVersion = out } } if out, err := execCmd("cmd", "/c", "ver"); err == nil { _ = out } if out, err := execCmd("powershell", "-NoProfile", "-NonInteractive", "-Command", "(Get-CimInstance Win32_OperatingSystem).Caption"); err == nil { caption := strings.TrimSpace(out) if caption != "" { info.OSName = caption } } if out, err := execCmd("powershell", "-NoProfile", "-NonInteractive", "-Command", "$k = (Get-CimInstance Win32_OperatingSystem).Version; $b = (Get-CimInstance Win32_ComputerSystem).BuildNumber; $k"); err == nil { if kb := strings.TrimSpace(out); kb != "" { info.KernelVersion = kb } } } if cpuInfo, err := cpu.Info(); err == nil && len(cpuInfo) > 0 { info.CPUModel = cpuInfo[0].ModelName info.CPUCores = len(cpuInfo) } if v, err := mem.VirtualMemory(); err == nil { info.TotalMemory = v.Total } if d, err := disk.Usage("/"); err == nil { info.TotalDisk = d.Total } populateNetwork(info) if info.OSVersion == "" { info.OSVersion = "unknown" } if pkgs, err := pkgmanager.ListPackages(); err == nil { info.Packages = pkgs } if svcs, err := svcmgr.ListServices(); err == nil { info.Services = svcs } return info, nil } func populateNetwork(info *SystemInfo) { ifs, err := net.Interfaces() if err != nil { return } var primaryIP string for _, ifc := range ifs { if !containsFlag(ifc.Flags, "up") || containsFlag(ifc.Flags, "loopback") { continue } ni := NetworkInterface{ Name: ifc.Name, Addresses: []string{}, MAC: ifc.HardwareAddr, } for _, addr := range ifc.Addrs { if addr.Addr == "" { continue } ip := strings.TrimSpace(addr.Addr) ni.Addresses = append(ni.Addresses, ip) if primaryIP == "" && !strings.Contains(ip, ":") { primaryIP = strings.Split(ip, "/")[0] } } if len(ni.Addresses) > 0 { info.NetworkInterfaces = append(info.NetworkInterfaces, ni) } } if primaryIP != "" { info.IPAddress = primaryIP } } func containsFlag(flags []string, flag string) bool { for _, f := range flags { if strings.EqualFold(f, flag) { return true } } return false }