snack

package module
v0.4.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 25, 2026 License: 0BSD Imports: 3 Imported by: 0

README

snack 🍿

Idiomatic Go wrappers for system package managers.

License 0BSD GoDoc

snack provides thin, context-aware Go bindings for system package managers. Think taigrr/systemctl but for package management.

Part of the grlx ecosystem.

Supported Package Managers

Package Manager Platform Status
pacman pacman Arch Linux
aur AUR (RPC + makepkg) Arch Linux
apk apk-tools Alpine Linux
apt APT (apt-get/apt-cache) Debian/Ubuntu
dpkg dpkg Debian/Ubuntu
dnf DNF Fedora/RHEL
rpm RPM Fedora/RHEL
flatpak Flatpak Linux
snap snapd Linux
brew Homebrew macOS/Linux
pkg pkg(8) FreeBSD
ports ports/packages OpenBSD
winget Windows Package Manager Windows
detect Auto-detection All
Capability Matrix
Provider VersionQuery Hold Clean FileOwner RepoMgmt KeyMgmt Groups NameNorm DryRun PkgUpgrade
apt -
pacman - - -
aur - - - - - - -
apk - - - -
dnf
flatpak - - - - -
snap - - - - - -
brew - - - - -
pkg - - - - -
ports - - - - -
winget - - - - - -

Install

go get github.com/gogrlx/snack

Usage

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/gogrlx/snack"
    "github.com/gogrlx/snack/apt"
)

func main() {
    ctx := context.Background()
    mgr := apt.New()

    // Install a package
    _, err := mgr.Install(ctx, snack.Targets("nginx"), snack.WithSudo(), snack.WithAssumeYes())
    if err != nil {
        log.Fatal(err)
    }

    // Check if installed
    installed, err := mgr.IsInstalled(ctx, "nginx")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("nginx installed:", installed)
}
Auto-detection
import "github.com/gogrlx/snack/detect"

mgr, err := detect.Default()
if err != nil {
    log.Fatal(err)
}
fmt.Println("Detected:", mgr.Name())

Interfaces

snack uses a layered interface design. Every provider implements Manager (the base). Extended capabilities are optional — use type assertions to check support:

// Base — every provider
snack.Manager           // Install, Remove, Purge, Upgrade, Update, List, Search, Info, IsInstalled, Version

// Optional capabilities — type-assert to check
snack.VersionQuerier    // LatestVersion, ListUpgrades, UpgradeAvailable, VersionCmp
snack.Holder            // Hold, Unhold, ListHeld (version pinning)
snack.Cleaner           // Autoremove, Clean (orphan/cache cleanup)
snack.FileOwner         // FileList, Owner (file-to-package queries)
snack.RepoManager       // ListRepos, AddRepo, RemoveRepo
snack.KeyManager        // AddKey, RemoveKey, ListKeys (GPG keys)
snack.Grouper           // GroupList, GroupInfo, GroupInstall
snack.NameNormalizer    // NormalizeName, ParseArch

Check capabilities at runtime:

caps := snack.GetCapabilities(mgr)
if caps.Hold {
    mgr.(snack.Holder).Hold(ctx, []string{"nginx"})
}

Design

  • Thin CLI wrappers — each sub-package wraps a package manager's CLI tools. No FFI, no library bindings.
  • Common interface — all managers implement snack.Manager, making them interchangeable.
  • Capability interfaces — extended features via type assertion, so providers aren't forced to stub unsupported operations.
  • Per-provider mutex — each provider serializes mutating operations independently; apt + snap can run in parallel.
  • Context-aware — all operations accept context.Context for cancellation and timeouts.
  • Platform-safe — build tags ensure packages compile everywhere but only run where appropriate.
  • No root assumption — use snack.WithSudo() when elevated privileges are needed.

Implementation Priority

  1. pacman + AUR (Arch Linux)
  2. apk (Alpine Linux)
  3. apt + dpkg (Debian/Ubuntu)
  4. dnf + rpm (Fedora/RHEL)
  5. flatpak + snap (cross-distro)
  6. pkg + ports (BSD)
  7. winget (Windows)

CLI

A companion CLI tool is planned for direct terminal usage:

snack install nginx
snack remove nginx
snack search redis
snack list
snack upgrade

License

0BSD — see LICENSE.

Documentation

Overview

Package snack provides idiomatic Go wrappers for system package managers.

Each sub-package wraps a specific package manager's CLI, while the root package defines the common Manager interface that all providers implement. Use [detect.Default] to auto-detect the system's package manager.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotInstalled is returned when a queried package is not installed.
	ErrNotInstalled = errors.New("package is not installed")

	// ErrNotFound is returned when a package cannot be found in any repository.
	ErrNotFound = errors.New("package not found")

	// ErrUnsupportedPlatform is returned when a package manager is not
	// available on the current platform.
	ErrUnsupportedPlatform = errors.New("package manager not available on this platform")

	// ErrPermissionDenied is returned when an operation requires elevated
	// privileges that were not provided.
	ErrPermissionDenied = errors.New("permission denied; try WithSudo()")

	// ErrAlreadyInstalled is returned when attempting to install a package
	// that is already present.
	ErrAlreadyInstalled = errors.New("package is already installed")

	// ErrDependencyConflict is returned when a package has unresolvable
	// dependency conflicts.
	ErrDependencyConflict = errors.New("dependency conflict")

	// ErrManagerNotFound is returned by detect when no supported package
	// manager can be found on the system.
	ErrManagerNotFound = errors.New("no supported package manager found")

	// ErrDaemonNotRunning is returned when a package manager's required
	// daemon (e.g. snapd) is not running.
	ErrDaemonNotRunning = errors.New("package manager daemon is not running")
)

Functions

func TargetNames

func TargetNames(targets []Target) []string

TargetNames extracts just the package names from a slice of targets.

Types

type Capabilities

type Capabilities struct {
	VersionQuery   bool
	Hold           bool
	Clean          bool
	FileOwnership  bool
	RepoManagement bool
	KeyManagement  bool
	Groups         bool
	NameNormalize  bool
	DryRun         bool
	PackageUpgrade bool
}

Capabilities reports which optional interfaces a Manager implements. Useful for grlx to determine what operations are available before attempting them.

func GetCapabilities

func GetCapabilities(m Manager) Capabilities

GetCapabilities probes a Manager for all optional interface support.

type Cleaner

type Cleaner interface {
	// Autoremove removes packages that were installed as dependencies
	// but are no longer required by any installed package.
	Autoremove(ctx context.Context, opts ...Option) error

	// Clean removes cached package files from the local cache.
	Clean(ctx context.Context) error
}

Cleaner provides orphan/cache cleanup operations. Supported by: apt, pacman, apk, dnf.

type DryRunner

type DryRunner interface {
	// SupportsDryRun reports whether this backend honors [WithDryRun].
	SupportsDryRun() bool
}

DryRunner reports whether a Manager backend natively supports dry-run mode. Backends that implement this interface honor WithDryRun in Install, Remove, Purge, and Upgrade and pass the appropriate flag to the underlying CLI (e.g. apt-get --dry-run, apk --simulate, dnf --setopt=tsflags=test, pacman --print).

Backends that do NOT implement this interface (snap, flatpak, rpm, ports) silently ignore WithDryRun.

Supported by: apt, apk, dnf, pacman.

type FileOwner

type FileOwner interface {
	// FileList returns all files installed by a package.
	FileList(ctx context.Context, pkg string) ([]string, error)

	// Owner returns the package that owns a given file path.
	Owner(ctx context.Context, path string) (string, error)
}

FileOwner provides file-to-package ownership queries. Supported by: apt/dpkg, pacman, rpm/dnf, apk.

type Grouper

type Grouper interface {
	// GroupList returns all available package groups.
	GroupList(ctx context.Context) ([]string, error)

	// GroupInfo returns the packages in a group.
	GroupInfo(ctx context.Context, group string) ([]Package, error)

	// GroupInstall installs all packages in a group.
	GroupInstall(ctx context.Context, group string, opts ...Option) error

	// GroupIsInstalled reports whether all packages in the group are installed.
	GroupIsInstalled(ctx context.Context, group string) (bool, error)
}

Grouper provides package group operations. Supported by: pacman, dnf/yum.

type Holder

type Holder interface {
	// Hold pins packages at their current version, preventing upgrades.
	Hold(ctx context.Context, pkgs []string) error

	// Unhold removes version pins, allowing packages to be upgraded.
	Unhold(ctx context.Context, pkgs []string) error

	// ListHeld returns all currently held/pinned packages.
	ListHeld(ctx context.Context) ([]Package, error)

	// IsHeld reports whether a specific package is currently held/pinned.
	IsHeld(ctx context.Context, pkg string) (bool, error)
}

Holder provides package version pinning (hold/unhold). Supported by: apt, pacman (with pacman-contrib), dnf.

type InstallResult

type InstallResult struct {
	// Installed contains packages that were newly installed by this operation.
	Installed []Package
	// Updated contains packages that were upgraded by this operation.
	Updated []Package
	// Unchanged contains the names of packages that were already at the
	// desired state and required no action.
	Unchanged []string
}

InstallResult holds the outcome of an Install operation.

type KeyManager

type KeyManager interface {
	// AddKey imports a GPG key for package verification.
	// The key can be a URL, file path, or key ID.
	AddKey(ctx context.Context, key string) error

	// RemoveKey removes a GPG key.
	RemoveKey(ctx context.Context, keyID string) error

	// ListKeys returns all trusted package signing keys.
	ListKeys(ctx context.Context) ([]string, error)
}

KeyManager provides GPG/signing key management for repositories. Supported by: apt, rpm/dnf.

type Locker

type Locker struct {
	// contains filtered or unexported fields
}

Locker is an embeddable type that provides per-provider mutex locking. Each package manager backend should embed this in its struct to serialize mutating operations (Install, Remove, Purge, Upgrade, Update).

Read-only operations (List, Search, Info, IsInstalled, Version) generally don't need the lock, but backends may choose to lock them if the underlying tool doesn't support concurrent reads.

Different providers use independent locks, so an apt Install and a snap Install can run concurrently, but two apt Installs will serialize.

func (*Locker) Lock

func (l *Locker) Lock()

Lock acquires the provider lock. Call before mutating operations.

func (*Locker) Unlock

func (l *Locker) Unlock()

Unlock releases the provider lock. Defer after Lock.

type Manager

type Manager interface {
	// Install one or more packages. Returns a result describing what changed.
	Install(ctx context.Context, pkgs []Target, opts ...Option) (InstallResult, error)

	// Remove one or more packages. Returns a result describing what changed.
	Remove(ctx context.Context, pkgs []Target, opts ...Option) (RemoveResult, error)

	// Purge one or more packages (remove including config files).
	Purge(ctx context.Context, pkgs []Target, opts ...Option) error

	// Upgrade all installed packages to their latest versions.
	Upgrade(ctx context.Context, opts ...Option) error

	// Update refreshes the package index/database.
	Update(ctx context.Context) error

	// List returns all installed packages.
	List(ctx context.Context) ([]Package, error)

	// Search queries the package index for packages matching the query.
	Search(ctx context.Context, query string) ([]Package, error)

	// Info returns details about a specific package.
	Info(ctx context.Context, pkg string) (*Package, error)

	// IsInstalled reports whether a package is currently installed.
	IsInstalled(ctx context.Context, pkg string) (bool, error)

	// Version returns the installed version of a package.
	Version(ctx context.Context, pkg string) (string, error)

	// Available reports whether this package manager is present on the system.
	Available() bool

	// Name returns the package manager's identifier (e.g. "apt", "pacman").
	Name() string
}

Manager is the base interface implemented by all package manager wrappers. It covers the core operations every package manager supports.

For extended capabilities, use type assertions against the optional interfaces below. Example:

if holder, ok := mgr.(snack.Holder); ok {
    holder.Hold(ctx, "nginx")
} else {
    log.Warn("hold not supported by", mgr.Name())
}

type NameNormalizer

type NameNormalizer interface {
	// NormalizeName returns the canonical form of a package name,
	// stripping architecture suffixes, epoch prefixes, etc.
	NormalizeName(name string) string

	// ParseArch extracts the architecture from a package name if present.
	// Returns the name without arch and the arch string.
	ParseArch(name string) (string, string)
}

NormalizeName provides package name normalization. Supported by: apt (strips :arch suffixes), rpm.

type Option

type Option func(*Options)

Option is a functional option for package manager operations.

func WithAssumeYes

func WithAssumeYes() Option

WithAssumeYes automatically confirms prompts.

func WithDryRun

func WithDryRun() Option

WithDryRun simulates the operation without making changes.

func WithFromRepo

func WithFromRepo(repo string) Option

WithFromRepo constrains the operation to a specific repository. e.g. "unstable" for apt, "community" for pacman.

func WithRefresh

func WithRefresh() Option

WithRefresh refreshes the package database before the operation. Equivalent to pacman -Sy, apt-get update, apk update, etc.

func WithReinstall

func WithReinstall() Option

WithReinstall forces reinstallation of already-installed packages.

func WithRoot

func WithRoot(root string) Option

WithRoot sets an alternate root filesystem path.

func WithSudo

func WithSudo() Option

WithSudo runs the command with sudo.

func WithVerbose

func WithVerbose() Option

WithVerbose enables verbose output.

type Options

type Options struct {
	Sudo      bool
	AssumeYes bool
	DryRun    bool
	Root      string // alternate root filesystem
	Verbose   bool
	Refresh   bool   // refresh package index before operation
	FromRepo  string // constrain operation to a specific repository
	Reinstall bool   // reinstall already-installed packages
}

Options holds configuration for package manager operations.

func ApplyOptions

func ApplyOptions(opts ...Option) Options

ApplyOptions processes functional options into an Options struct.

type Package

type Package struct {
	Name        string `json:"name"`
	Version     string `json:"version"`
	Description string `json:"description,omitempty"`
	Arch        string `json:"arch,omitempty"`
	Repository  string `json:"repository,omitempty"`
	Installed   bool   `json:"installed"`
}

Package represents a system package.

type PackageUpgrader

type PackageUpgrader interface {
	// UpgradePackages upgrades specific installed packages to their latest
	// versions. Packages that are not installed are skipped (not installed).
	// Returns an InstallResult describing what changed.
	UpgradePackages(ctx context.Context, pkgs []Target, opts ...Option) (InstallResult, error)
}

PackageUpgrader provides targeted package upgrades (as opposed to Upgrade which upgrades everything). Backends that implement this use native upgrade commands that only act on already-installed packages.

Supported by: apt, dnf, pacman, apk, pkg (FreeBSD), flatpak, snap.

type RemoveResult

type RemoveResult struct {
	// Removed contains packages that were removed by this operation.
	Removed []Package
	// Unchanged contains the names of packages that were not installed
	// and required no action.
	Unchanged []string
}

RemoveResult holds the outcome of a Remove operation.

type RepoManager

type RepoManager interface {
	// ListRepos returns all configured package repositories.
	ListRepos(ctx context.Context) ([]Repository, error)

	// AddRepo adds a new package repository.
	AddRepo(ctx context.Context, repo Repository) error

	// RemoveRepo removes a configured repository.
	RemoveRepo(ctx context.Context, id string) error
}

RepoManager provides repository configuration operations. Supported by: apt, dnf, pacman (partially).

type Repository

type Repository struct {
	ID       string `json:"id"`                  // unique identifier
	Name     string `json:"name,omitempty"`      // human-readable name
	URL      string `json:"url"`                 // repository URL
	Enabled  bool   `json:"enabled"`             // whether the repo is active
	GPGCheck bool   `json:"gpg_check,omitempty"` // whether GPG verification is enabled
	GPGKey   string `json:"gpg_key,omitempty"`   // GPG key URL or ID
	Type     string `json:"type,omitempty"`      // e.g. "deb", "rpm-md", "pkg"
	Arch     string `json:"arch,omitempty"`      // architecture filter
}

Repository represents a configured package repository.

type Target

type Target struct {
	// Name is the package name (required).
	Name string

	// Version pins a specific version. If empty, the latest is used.
	// Comparison operators are supported where the backend allows them
	// (e.g. ">=1.2.3", "<2.0", "1.2.3-4").
	Version string

	// FromRepo constrains the install to a specific repository
	// (e.g. "unstable", "community", "epel").
	FromRepo string

	// Source is a local file path or URL for package files
	// (e.g. .deb, .rpm, .pkg.tar.zst). When set, Name is used only
	// for display/logging.
	Source string
}

Target represents a package to install, remove, or otherwise act on. At minimum, Name must be set. Version and other fields constrain the action.

Modeled after SaltStack's pkgs list, which accepts both plain names and name:version mappings:

pkgs:
  - nginx
  - redis: ">=7.0"
  - curl: "8.5.0-1"

func Targets

func Targets(names ...string) []Target

Targets is a convenience constructor for a slice of Target from plain package names (no version constraint).

type VersionQuerier

type VersionQuerier interface {
	// LatestVersion returns the latest available version of a package
	// from configured repositories.
	LatestVersion(ctx context.Context, pkg string) (string, error)

	// ListUpgrades returns packages that have newer versions available.
	ListUpgrades(ctx context.Context) ([]Package, error)

	// UpgradeAvailable reports whether a newer version of a package is
	// available in the repositories.
	UpgradeAvailable(ctx context.Context, pkg string) (bool, error)

	// VersionCmp compares two version strings using the package manager's
	// native version comparison logic. Returns -1, 0, or 1.
	VersionCmp(ctx context.Context, ver1, ver2 string) (int, error)
}

VersionQuerier provides version comparison and upgrade availability checks. Supported by: apt, pacman, apk, dnf.

Directories

Path Synopsis
Package apk provides Go bindings for apk-tools (Alpine Linux package manager).
Package apk provides Go bindings for apk-tools (Alpine Linux package manager).
Package apt provides Go bindings for APT (Advanced Packaging Tool) on Debian/Ubuntu.
Package apt provides Go bindings for APT (Advanced Packaging Tool) on Debian/Ubuntu.
Package aur provides a native Go client for the Arch User Repository.
Package aur provides a native Go client for the Arch User Repository.
Package brew provides Go bindings for Homebrew package manager.
Package brew provides Go bindings for Homebrew package manager.
cmd
snack command
snack is a unified CLI for system package managers.
snack is a unified CLI for system package managers.
Package detect provides auto-detection of the system's available package manager.
Package detect provides auto-detection of the system's available package manager.
Package dnf provides Go bindings for the dnf package manager (Fedora/RHEL).
Package dnf provides Go bindings for the dnf package manager (Fedora/RHEL).
Package dpkg provides Go bindings for dpkg (low-level Debian package tool).
Package dpkg provides Go bindings for dpkg (low-level Debian package tool).
Package flatpak provides Go bindings for the Flatpak package manager.
Package flatpak provides Go bindings for the Flatpak package manager.
Package pacman provides Go bindings for the pacman package manager (Arch Linux).
Package pacman provides Go bindings for the pacman package manager (Arch Linux).
Package pkg provides Go bindings for pkg(8) (FreeBSD package manager).
Package pkg provides Go bindings for pkg(8) (FreeBSD package manager).
Package ports provides Go bindings for OpenBSD ports/packages.
Package ports provides Go bindings for OpenBSD ports/packages.
Package rpm provides Go bindings for the rpm low-level package manager.
Package rpm provides Go bindings for the rpm low-level package manager.
Package snap provides Go bindings for the snap package manager.
Package snap provides Go bindings for the snap package manager.
Package winget provides Go bindings for the Windows Package Manager (winget).
Package winget provides Go bindings for the Windows Package Manager (winget).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL