426 lines
12 KiB
Go
426 lines
12 KiB
Go
package pkg_manager
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"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: true,
|
|
},
|
|
},
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// CheckPackageExists verifies if a package exists in the repositories
|
|
func (m *Manager) CheckPackageExists(pkg string) bool {
|
|
cmd := exec.Command(m.command, append(m.searchCmd, pkg)...)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
// Different package managers have different output formats
|
|
// This is a simple check that should work for most
|
|
return len(output) > 0
|
|
}
|
|
|
|
// Install packages with version support
|
|
func (m *Manager) Install(packages ...string) error {
|
|
if len(packages) == 0 {
|
|
return fmt.Errorf("no packages specified")
|
|
}
|
|
|
|
// Validate packages first
|
|
for _, pkg := range packages {
|
|
pv := ParsePackageVersion(pkg)
|
|
if !m.CheckPackageExists(pv.Name) {
|
|
return fmt.Errorf("package not found: %s", pv.Name)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// execCommandWithSudo executes a command with sudo
|
|
func (m *Manager) execCommandWithSudo(baseCmd []string, args ...string) error {
|
|
cmdArgs := append(baseCmd, args...)
|
|
cmdArgs = append([]string{m.command}, cmdArgs...)
|
|
cmd := exec.Command("sudo", cmdArgs...)
|
|
|
|
// Always show sudo password prompt
|
|
cmd.Stdin = os.Stdin
|
|
if outputMode == OutputVerbose {
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
} else {
|
|
cmd.Stdout = io.Discard
|
|
cmd.Stderr = io.Discard
|
|
}
|
|
|
|
return cmd.Run()
|
|
}
|
|
|
|
func (m *Manager) execCommand(baseCmd []string, args ...string) error {
|
|
cmdArgs := append(baseCmd, args...)
|
|
|
|
var cmd *exec.Cmd
|
|
if m.needsSudo && os.Geteuid() != 0 {
|
|
return m.execCommandWithSudo(baseCmd, args...)
|
|
} else {
|
|
cmd = exec.Command(m.command, cmdArgs...)
|
|
if outputMode == OutputVerbose {
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
} else {
|
|
cmd.Stdout = io.Discard
|
|
cmd.Stderr = io.Discard
|
|
}
|
|
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")
|
|
}
|
|
|
|
// NeedsSudo returns whether this package manager needs sudo
|
|
func (m *Manager) NeedsSudo() bool {
|
|
return m.needsSudo && os.Geteuid() != 0
|
|
}
|