go-package-manager/README.md
2026-07-01 17:47:49 -05:00

4.8 KiB

go-package-manager

Go Reference

pkgmgr is a Go library that abstracts system package managers behind a single Manager interface. One code path can install, upgrade, search, and manage repositories on Debian, Fedora, openSUSE, Arch, Alpine, macOS, and more, without each caller hard-coding apt/dnf/pacman invocations.

API documentation is available on pkg.go.dev.

Supported managers

Manager Type Format Notes
zypper root rpm openSUSE
dnf root rpm Fedora / RHEL
yum root rpm Legacy RHEL
apt root deb Debian / Ubuntu
apt-get root deb Debian / Ubuntu
pacman root pacman Arch
apk root apk Alpine
brew unpriv. bottle Homebrew; refuses to run as root
yay unpriv. pacman AUR helper; pacman drop-in
paru unpriv. pacman AUR helper; pacman drop-in

Root-type managers escalate privileged commands through a configurable wrapper (sudo by default). Homebrew and the AUR helpers must not run as root, so they bypass the wrapper and instead drop to an unprivileged user via sudo -u when the process is running as root.

Installation

go get github.com/grmrgecko/go-package-manager

Usage

Resolve the active system manager, then drive it through the interface. The example below installs a package, escalating with sudo when not already root.

package main

import (
	"context"
	"log"

	pkgmgr "github.com/grmrgecko/go-package-manager"
)

func main() {
	m := pkgmgr.GetSystemManager()
	if m == nil {
		log.Fatal("no supported package manager found")
	}

	// Wrap privileged commands with sudo when not running as root.
	m.UseSudoWhenNeeded()

	ctx := context.Background()
	if err := m.Sync(ctx, nil); err != nil {
		log.Fatal(err)
	}
	if err := m.Install(ctx, nil, "htop"); err != nil {
		log.Fatal(err)
	}

	results, err := m.Search(ctx, nil, "editor")
	if err != nil {
		log.Fatal(err)
	}
	for _, r := range results {
		log.Printf("%s %s - %s", r.Name, r.Version, r.Summary)
	}
}

Selecting a manager explicitly

GetSystemManager auto-detects in priority order. To target a specific manager, construct it directly:

m := &pkgmgr.Apt{}
m.UseSudoWhenNeeded()

AUR helpers must be constructed through their constructors so the helper binary name is set:

yay := pkgmgr.NewYay()
paru := pkgmgr.NewParu()

Interface

The Manager interface covers the full package lifecycle. Methods that perform I/O take a context.Context for cancellation and timeouts.

  • Identity: Name, Format, Path
  • Privilege: SetCmdWrapper, UseSudoWhenNeeded
  • I/O: SetIO (override stdin/stdout/stderr of spawned commands)
  • Repos: AddRepo, AddRepoURL, RemoveRepo, GetRepo, ListRepos
  • Keys: AddRepoKey, AddRepoKeyFile, AddRepoKeyURL
  • Packages: Sync, Install, Remove, Upgrade, InstallFile, UpgradeAll, Clean
  • Queries: Search, Info, ListInstalled, ListUpgradable

Search returns structured SearchResult values; Info returns the underlying tool's native text output; ListInstalled and ListUpgradable return name -> version maps. ListRepos returns every configured repo as a name -> configuration map, where each value matches what GetRepo returns for that name.

Privilege handling

Two mechanisms cover opposite requirements:

  • Root managers (apt, dnf, pacman, ...) need root for mutating operations. UseSudoWhenNeeded sets the wrapper to sudo when the process is not already root.
  • Non-root managers (brew, yay, paru) refuse to run as root. They use an embedded dropPrivilege to run their command as an unprivileged user, dropping via sudo -u only when the process is root. The drop user defaults to SUDO_USER and can be set explicitly with SetDropUser.

A custom command wrapper (for example []string{"sudo", "-n"}) can be supplied through SetCmdWrapper.

Testing

go test ./...

The suite verifies that every manager satisfies the Manager interface and exercises the command wrapper, I/O overrides, privilege drop logic, and the output parsers.