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

141 lines
4.8 KiB
Markdown

# go-package-manager
[![Go Reference](https://pkg.go.dev/badge/github.com/grmrgecko/go-package-manager.svg)](https://pkg.go.dev/github.com/grmrgecko/go-package-manager)
`pkgmgr` is a Go library that abstracts system package managers behind a single
[`Manager`](managers.go) 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](https://pkg.go.dev/github.com/grmrgecko/go-package-manager).
## 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
```bash
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.
```go
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:
```go
m := &pkgmgr.Apt{}
m.UseSudoWhenNeeded()
```
AUR helpers must be constructed through their constructors so the helper binary
name is set:
```go
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
```bash
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.