upm-universalpackagemanager/pkg_manager/pkg_manager.go
2025-01-21 16:59:43 -05:00

388 lines
10 KiB
Go

package pkg_manager
import (
"fmt"
"os"
"os/exec"
"strings"
)
// PackageManager interface defines common operations
type PackageManager interface {
Install(packages ...string) error
Remove(packages ...string) error
Search(term string) error
Update() error
Upgrade() error
ListInstalled() error
ShowInfo(pkg string) error
AddRepository(repo string) error
RemoveRepository(repo string) error
ListRepositories() error
CleanCache() error
AutoRemove() error
CheckDependencies(pkg string) error
ShowDependencies(pkg string) error
}
// Manager represents a concrete package manager
type Manager struct {
command string
installCmd []string
removeCmd []string
searchCmd []string
updateCmd []string
upgradeCmd []string
listCmd []string
infoCmd []string
addRepoCmd []string
removeRepoCmd []string
listRepoCmd []string
cleanCacheCmd []string
autoRemoveCmd []string
checkDependsCmd []string
showDependsCmd []string
needsSudo bool
versionSep string
cacheDir string
}
// UniversalManager represents universal package managers like snap/flatpak
type UniversalManager struct {
Manager
isAvailable bool
}
// PackageVersion represents a package with optional version
type PackageVersion struct {
Name string
Version string
}
var universalManagers = map[string]*UniversalManager{
"snap": {
Manager: Manager{
command: "snap",
installCmd: []string{"install"},
removeCmd: []string{"remove"},
searchCmd: []string{"find"},
updateCmd: []string{"refresh"},
upgradeCmd: []string{"refresh"},
needsSudo: true,
},
},
"flatpak": {
Manager: Manager{
command: "flatpak",
installCmd: []string{"install", "-y"},
removeCmd: []string{"uninstall", "-y"},
searchCmd: []string{"search"},
updateCmd: []string{"update", "-y"},
upgradeCmd: []string{"update", "-y"},
needsSudo: false,
},
},
}
var managers = map[string]Manager{
"apt": {
command: "apt",
installCmd: []string{"install", "-y"},
removeCmd: []string{"remove", "-y"},
searchCmd: []string{"search"},
updateCmd: []string{"update"},
upgradeCmd: []string{"upgrade", "-y"},
listCmd: []string{"list", "--installed"},
infoCmd: []string{"show"},
addRepoCmd: []string{"add-apt-repository", "-y"},
removeRepoCmd: []string{"add-apt-repository", "-r", "-y"},
listRepoCmd: []string{"sources"},
cleanCacheCmd: []string{"clean"},
autoRemoveCmd: []string{"autoremove", "-y"},
checkDependsCmd: []string{"check"},
showDependsCmd: []string{"depends"},
needsSudo: true,
versionSep: "=",
cacheDir: "/var/cache/apt",
},
"dnf": {
command: "dnf",
installCmd: []string{"install", "-y"},
removeCmd: []string{"remove", "-y"},
searchCmd: []string{"search"},
updateCmd: []string{"check-update"},
upgradeCmd: []string{"upgrade", "-y"},
listCmd: []string{"list", "installed"},
infoCmd: []string{"info"},
addRepoCmd: []string{"config-manager", "--add-repo"},
removeRepoCmd: []string{"config-manager", "--disable"},
listRepoCmd: []string{"repolist"},
cleanCacheCmd: []string{"clean", "all"},
autoRemoveCmd: []string{"autoremove", "-y"},
checkDependsCmd: []string{"check-dependencies"},
showDependsCmd: []string{"repoquery", "--requires"},
needsSudo: true,
versionSep: "-",
cacheDir: "/var/cache/dnf",
},
"yum": {
command: "yum",
installCmd: []string{"install", "-y"},
removeCmd: []string{"remove", "-y"},
searchCmd: []string{"search"},
updateCmd: []string{"check-update"},
upgradeCmd: []string{"upgrade", "-y"},
needsSudo: true,
versionSep: "-",
cacheDir: "/var/cache/yum",
},
"pacman": {
command: "pacman",
installCmd: []string{"-S", "--noconfirm"},
removeCmd: []string{"-R", "--noconfirm"},
searchCmd: []string{"-Ss"},
updateCmd: []string{"-Sy"},
upgradeCmd: []string{"-Syu", "--noconfirm"},
needsSudo: true,
versionSep: "=",
cacheDir: "/var/cache/pacman",
},
"zypper": {
command: "zypper",
installCmd: []string{"install", "-y"},
removeCmd: []string{"remove", "-y"},
searchCmd: []string{"search"},
updateCmd: []string{"refresh"},
upgradeCmd: []string{"update", "-y"},
needsSudo: true,
versionSep: "=",
cacheDir: "/var/cache/zypp",
},
"apk": {
command: "apk",
installCmd: []string{"add"},
removeCmd: []string{"del"},
searchCmd: []string{"search"},
updateCmd: []string{"update"},
upgradeCmd: []string{"upgrade"},
needsSudo: true,
versionSep: "=",
cacheDir: "/var/cache/apk",
},
"xbps": {
command: "xbps-install",
installCmd: []string{"-y"},
removeCmd: []string{"-R"},
searchCmd: []string{"-Rs"},
updateCmd: []string{"-S"},
upgradeCmd: []string{"-Su"},
listCmd: []string{"-l"},
infoCmd: []string{"-S"},
addRepoCmd: []string{"-R", "add"},
removeRepoCmd: []string{"-R", "remove"},
listRepoCmd: []string{"-R", "list"},
cleanCacheCmd: []string{"-Op"},
autoRemoveCmd: []string{"-Oo"},
checkDependsCmd: []string{"-C"},
showDependsCmd: []string{"-x"},
needsSudo: true,
versionSep: "-",
cacheDir: "/var/cache/xbps",
},
"emerge": {
command: "emerge",
installCmd: []string{"--ask", "n"},
removeCmd: []string{"--unmerge", "--ask", "n"},
searchCmd: []string{"--search"},
updateCmd: []string{"--sync"},
upgradeCmd: []string{"--update", "--deep", "--ask", "n", "@world"},
listCmd: []string{"--list-sets"},
infoCmd: []string{"--info"},
addRepoCmd: []string{"--repos-conf", "add"},
removeRepoCmd: []string{"--repos-conf", "remove"},
listRepoCmd: []string{"--repos-conf", "list"},
cleanCacheCmd: []string{"--depclean"},
autoRemoveCmd: []string{"--depclean"},
checkDependsCmd: []string{"--depclean", "-p"},
showDependsCmd: []string{"--deps"},
needsSudo: true,
versionSep: "-",
cacheDir: "/var/cache/portage",
},
}
// ParsePackageVersion parses package name and version
func ParsePackageVersion(pkg string) PackageVersion {
parts := strings.Split(pkg, "@")
if len(parts) == 2 {
return PackageVersion{
Name: parts[0],
Version: parts[1],
}
}
return PackageVersion{Name: pkg}
}
// formatPackageWithVersion formats package with version using manager's separator
func (m *Manager) formatPackageWithVersion(pv PackageVersion) string {
if pv.Version == "" {
return pv.Name
}
return fmt.Sprintf("%s%s%s", pv.Name, m.versionSep, pv.Version)
}
// Install packages with version support
func (m *Manager) Install(packages ...string) error {
if len(packages) == 0 {
return fmt.Errorf("no packages specified")
}
formattedPkgs := make([]string, len(packages))
for i, pkg := range packages {
pv := ParsePackageVersion(pkg)
formattedPkgs[i] = m.formatPackageWithVersion(pv)
}
return m.execCommand(m.installCmd, formattedPkgs...)
}
// InitializeUniversalManagers checks which universal package managers are available
func InitializeUniversalManagers() {
for _, mgr := range universalManagers {
_, err := exec.LookPath(mgr.command)
mgr.isAvailable = err == nil
}
}
// GetUniversalManager returns a universal package manager if available
func GetUniversalManager(name string) (*UniversalManager, bool) {
if mgr, exists := universalManagers[name]; exists && mgr.isAvailable {
return mgr, true
}
return nil, false
}
type OutputMode int
const (
OutputQuiet OutputMode = iota
OutputVerbose
)
var outputMode = OutputQuiet
// SetOutputMode sets the output verbosity
func SetOutputMode(mode OutputMode) {
outputMode = mode
}
func (m *Manager) execCommand(baseCmd []string, args ...string) error {
cmdArgs := append(baseCmd, args...)
var cmd *exec.Cmd
if m.needsSudo && os.Geteuid() != 0 {
// Clear the line to prevent spinner interference with sudo prompt
fmt.Print("\r\033[K")
cmdArgs = append([]string{m.command}, cmdArgs...)
cmd = exec.Command("sudo", cmdArgs...)
} else {
cmd = exec.Command(m.command, cmdArgs...)
}
// Always capture output
if outputMode == OutputVerbose {
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
} else {
// Capture but don't display output in quiet mode
cmd.Stdout = nil
cmd.Stderr = nil
}
cmd.Stdin = os.Stdin
return cmd.Run()
}
// Remove packages
func (m *Manager) Remove(packages ...string) error {
if len(packages) == 0 {
return fmt.Errorf("no packages specified")
}
return m.execCommand(m.removeCmd, packages...)
}
// Search for packages
func (m *Manager) Search(term string) error {
if term == "" {
return fmt.Errorf("no search term specified")
}
return m.execCommand(m.searchCmd, term)
}
// Update package lists
func (m *Manager) Update() error {
return m.execCommand(m.updateCmd)
}
// Upgrade installed packages
func (m *Manager) Upgrade() error {
return m.execCommand(m.upgradeCmd)
}
// ListInstalled lists all installed packages
func (m *Manager) ListInstalled() error {
return m.execCommand(m.listCmd)
}
// ShowInfo shows detailed information about a package
func (m *Manager) ShowInfo(pkg string) error {
return m.execCommand(m.infoCmd, pkg)
}
// AddRepository adds a new repository
func (m *Manager) AddRepository(repo string) error {
return m.execCommand(m.addRepoCmd, repo)
}
// RemoveRepository removes a repository
func (m *Manager) RemoveRepository(repo string) error {
return m.execCommand(m.removeRepoCmd, repo)
}
// ListRepositories lists all configured repositories
func (m *Manager) ListRepositories() error {
return m.execCommand(m.listRepoCmd)
}
// CleanCache cleans the package manager cache
func (m *Manager) CleanCache() error {
return m.execCommand(m.cleanCacheCmd)
}
// AutoRemove removes unused dependencies
func (m *Manager) AutoRemove() error {
return m.execCommand(m.autoRemoveCmd)
}
// CheckDependencies checks for dependency issues
func (m *Manager) CheckDependencies(pkg string) error {
if pkg == "" {
return m.execCommand(m.checkDependsCmd)
}
return m.execCommand(m.checkDependsCmd, pkg)
}
// ShowDependencies shows dependencies for a package
func (m *Manager) ShowDependencies(pkg string) error {
return m.execCommand(m.showDependsCmd, pkg)
}
// DetectPackageManager identifies the system's package manager
func DetectPackageManager() (*Manager, error) {
for _, mgr := range managers {
if _, err := exec.LookPath(mgr.command); err == nil {
return &mgr, nil
}
}
return nil, fmt.Errorf("no supported package manager found")
}