feat: Fleet Forge UEM - device management, agent, remote desktop (VNC), TLS reverse proxy
This commit is contained in:
@@ -0,0 +1,354 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user