package pkgmanager import ( "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" ) type PackageInfo struct { Name string `json:"name"` Version string `json:"version"` Manager string `json:"manager"` } type ServiceInfo struct { Name string `json:"name"` Status string `json:"status"` Enabled bool `json:"enabled"` } func DetectManager() string { if runtime.GOOS == "windows" { if _, err := exec.LookPath(resolveManagerPath("winget", interactiveProfile())); err == nil { return "winget" } if _, err := exec.LookPath(resolveManagerPath("choco", interactiveProfile())); err == nil { return "choco" } if _, err := exec.LookPath("msiexec"); err == nil { return "msiexec" } return "unknown" } if _, err := exec.LookPath("apt"); err == nil { return "apt" } if _, err := exec.LookPath("dnf"); err == nil { return "dnf" } if _, err := exec.LookPath("yum"); err == nil { return "yum" } return "unknown" } func ListPackages() ([]PackageInfo, error) { if runtime.GOOS == "windows" { return listWindowsPackages() } manager := DetectManager() switch manager { case "apt": return listAptPackages() case "yum", "dnf": return listYumPackages() case "winget": return listWingetPackages() } return nil, fmt.Errorf("unsupported package manager: %s", manager) } func listAptPackages() ([]PackageInfo, error) { cmd := exec.Command("dpkg-query", "-W", "-f", "${Package}\t${Version}\n") out, err := cmd.Output() if err != nil { return nil, fmt.Errorf("dpkg-query failed: %w", err) } var packages []PackageInfo for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { if line == "" { continue } parts := strings.SplitN(line, "\t", 2) if len(parts) == 2 { packages = append(packages, PackageInfo{ Name: parts[0], Version: parts[1], Manager: "apt", }) } } return packages, nil } func listYumPackages() ([]PackageInfo, error) { cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\t%{VERSION}-%{RELEASE}\n") out, err := cmd.Output() if err != nil { return nil, fmt.Errorf("rpm query failed: %w", err) } var packages []PackageInfo for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") { if line == "" { continue } parts := strings.SplitN(line, "\t", 2) if len(parts) == 2 { packages = append(packages, PackageInfo{ Name: parts[0], Version: parts[1], Manager: "yum", }) } } return packages, nil } func listWingetPackages() ([]PackageInfo, error) { cmd := exec.Command(resolveManagerPath("winget", interactiveProfile()), "list", "--accept-source-agreements", "--disable-interactivity") out, err := cmd.Output() if err != nil { return nil, fmt.Errorf("winget list failed: %w", err) } var packages []PackageInfo lines := strings.Split(string(out), "\n") inTable := false for _, line := range lines { trimmed := strings.TrimSpace(line) if strings.HasPrefix(trimmed, "Name") && strings.Contains(trimmed, "Id") { inTable = true continue } if inTable && strings.Contains(trimmed, "---") { continue } if inTable && trimmed != "" { fields := strings.Fields(trimmed) if len(fields) >= 3 { pkg := PackageInfo{ Name: fields[0], Version: fields[len(fields)-1], Manager: "winget", } packages = append(packages, pkg) } } } return packages, nil } func InstallPackage(name string) (string, error) { return installPackageWithManager(name, DetectManager(), "") } func InstallPackageWithManager(name, manager, version string) (string, error) { return installPackageWithManager(name, manager, version) } func installPackageWithManager(name, manager, version string) (string, error) { var executable string var args []string switch manager { case "apt": executable = "sudo" args = []string{"apt-get", "install", "-y", name} case "yum": executable = "sudo" args = []string{"yum", "install", "-y", name} case "dnf": executable = "sudo" args = []string{"dnf", "install", "-y", name} case "winget": executable = "winget" args = []string{"install", "--id", name, "--accept-source-agreements", "--accept-package-agreements", "--disable-interactivity"} if version != "" { args = append(args, "--version", version) } case "choco": executable = "choco" args = []string{"install", "-y", name} if version != "" { args = append(args, "--version", version) } case "msiexec": executable = "msiexec" args = []string{"/i", name, "/quiet", "/norestart"} default: return "", fmt.Errorf("unsupported package manager: %s", manager) } out, err := runManagerCommand(executable, args) if err != nil { return out, err } return out, nil } func InstallPackageFromFile(filePath string) (string, error) { ext := strings.ToLower(filepath.Ext(filePath)) switch runtime.GOOS { case "windows": switch ext { case ".msi": cmd := exec.Command("msiexec", "/i", filePath, "/quiet", "/norestart") out, err := cmd.CombinedOutput() if err != nil { return string(out), fmt.Errorf("msi install failed: %w\nOutput: %s", err, string(out)) } return string(out), nil case ".exe": cmd := exec.Command(filePath, "/S") out, err := cmd.CombinedOutput() if err != nil { return string(out), fmt.Errorf("exe install failed: %w\nOutput: %s", err, string(out)) } return string(out), nil case ".msix", ".appx": cmd := exec.Command("powershell", "-Command", fmt.Sprintf("Add-AppxPackage -Path '%s'", filePath)) out, err := cmd.CombinedOutput() if err != nil { return string(out), fmt.Errorf("msix install failed: %w\nOutput: %s", err, string(out)) } return string(out), nil } case "linux": switch ext { case ".deb": cmd := exec.Command("sudo", "dpkg", "-i", filePath) out, err := cmd.CombinedOutput() if err != nil { cmd2 := exec.Command("sudo", "apt-get", "install", "-f", "-y") out2, _ := cmd2.CombinedOutput() return string(out) + "\n" + string(out2), fmt.Errorf("dpkg install failed: %w", err) } return string(out), nil case ".rpm": cmd := exec.Command("sudo", "rpm", "-i", filePath) out, err := cmd.CombinedOutput() if err != nil { return string(out), fmt.Errorf("rpm install failed: %w\nOutput: %s", err, string(out)) } return string(out), nil } case "darwin": switch ext { case ".dmg": cmd := exec.Command("hdiutil", "attach", filePath, "-nobrowse", "-quiet") out, err := cmd.CombinedOutput() if err != nil { return string(out), fmt.Errorf("dmg mount failed: %w", err) } return string(out), nil case ".pkg": cmd := exec.Command("sudo", "installer", "-pkg", filePath, "-target", "/") out, err := cmd.CombinedOutput() if err != nil { return string(out), fmt.Errorf("pkg install failed: %w\nOutput: %s", err, string(out)) } return string(out), nil } } return "", fmt.Errorf("unsupported file type %s for %s", ext, runtime.GOOS) } func UpdatePackage(name string) (string, error) { manager := DetectManager() var executable string var args []string switch manager { case "apt": executable = "sudo" args = []string{"apt-get", "install", "-y", "--only-upgrade", name} case "yum": executable = "sudo" args = []string{"yum", "update", "-y", name} case "dnf": executable = "sudo" args = []string{"dnf", "update", "-y", name} case "winget": executable = "winget" args = []string{"upgrade", "--id", name, "--accept-source-agreements", "--accept-package-agreements", "--disable-interactivity"} case "choco": executable = "choco" args = []string{"upgrade", "-y", name} default: return "", fmt.Errorf("unsupported package manager: %s", manager) } out, err := runManagerCommand(executable, args) if err != nil { return out, err } return out, nil } func RemovePackage(name string) (string, error) { return removePackageWithManager(name, DetectManager()) } func removePackageWithManager(name, manager string) (string, error) { var executable string var args []string switch manager { case "apt": executable = "sudo" args = []string{"apt-get", "remove", "-y", name} case "yum": executable = "sudo" args = []string{"yum", "remove", "-y", name} case "dnf": executable = "sudo" args = []string{"dnf", "remove", "-y", name} case "winget": executable = "winget" args = []string{"uninstall", "--id", name, "--accept-source-agreements", "--disable-interactivity"} case "choco": executable = "choco" args = []string{"uninstall", "-y", name} default: return "", fmt.Errorf("unsupported package manager: %s", manager) } out, err := runManagerCommand(executable, args) if err != nil { return out, err } return out, nil } func RemovePackageWithManager(name, manager string) (string, error) { return removePackageWithManager(name, manager) } func DownloadFile(url string, destPath string) error { dir := filepath.Dir(destPath) if err := os.MkdirAll(dir, 0755); err != nil { return fmt.Errorf("failed to create directory: %w", err) } var cmd *exec.Cmd if _, err := exec.LookPath("curl"); err == nil { cmd = exec.Command("curl", "-L", "-o", destPath, url) } else if _, err := exec.LookPath("wget"); err == nil { cmd = exec.Command("wget", "-O", destPath, url) } else { return fmt.Errorf("neither curl nor wget found") } out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("download failed: %w\nOutput: %s", err, string(out)) } return nil }