Files
snack/README.md
Tai Groot 85e06ffc44 docs: rewrite README with current API, capabilities table, CLI docs
- Fix usage examples to use snack.Targets() and snack.Target
- Replace construction emoji status with actual extras per provider
- Document all interfaces including PackageUpgrader and DryRunner
- Add Options section documenting all functional options
- Add full CLI command reference with examples
- Remove implementation priority (everything is implemented)
2026-03-06 01:07:35 +00:00

6.2 KiB

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 Extras
apt APT (apt-get/apt-cache) Debian/Ubuntu DryRun, FileOwner, Holder, KeyManager, NameNormalizer, RepoManager
dpkg dpkg Debian/Ubuntu DryRun, FileOwner, NameNormalizer
dnf DNF 4/5 Fedora/RHEL DryRun, FileOwner, Grouper, Holder, KeyManager, NameNormalizer, RepoManager
rpm RPM Fedora/RHEL FileOwner, NameNormalizer
pacman pacman Arch Linux DryRun, FileOwner, Grouper
aur AUR (makepkg) Arch Linux
apk apk-tools Alpine Linux DryRun, FileOwner
flatpak Flatpak Cross-distro RepoManager
snap snapd Cross-distro
pkg pkg(8) FreeBSD FileOwner
ports ports/packages OpenBSD FileOwner
detect Auto-detection All

All providers implement Manager, VersionQuerier, Cleaner, and PackageUpgrader. The Extras column lists additional capabilities beyond that baseline.

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 packages
    result, err := mgr.Install(ctx, snack.Targets("nginx", "curl"), snack.WithSudo(), snack.WithAssumeYes())
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Installed: %d, Unchanged: %d\n", len(result.Installed), len(result.Unchanged))

    // Check if installed
    installed, err := mgr.IsInstalled(ctx, "nginx")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("nginx installed:", installed)

    // Upgrade specific packages
    if up, ok := mgr.(snack.PackageUpgrader); ok {
        _, err := up.UpgradePackages(ctx, snack.Targets("nginx"), snack.WithSudo(), snack.WithAssumeYes())
        if err != nil {
            log.Fatal(err)
        }
    }
}

Auto-detection

import "github.com/gogrlx/snack/detect"

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

// All available managers
for _, m := range detect.All() {
    fmt.Println(m.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

// Core optional — implemented by all providers
snack.VersionQuerier     // LatestVersion, ListUpgrades, UpgradeAvailable, VersionCmp
snack.Cleaner            // Autoremove, Clean (orphan/cache cleanup)
snack.PackageUpgrader    // UpgradePackages (upgrade specific packages)

// Provider-specific — type-assert to check
snack.Holder             // Hold, Unhold, ListHeld, IsHeld (version pinning)
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, GroupIsInstalled
snack.NameNormalizer     // NormalizeName, ParseArch
snack.DryRunner          // SupportsDryRun (honors WithDryRun option)

Check capabilities at runtime:

caps := snack.GetCapabilities(mgr)
if caps.Hold {
    mgr.(snack.Holder).Hold(ctx, []string{"nginx"})
}
if caps.FileOwnership {
    owner, _ := mgr.(snack.FileOwner).Owner(ctx, "/usr/bin/curl")
    fmt.Println("Owned by:", owner)
}

Options

All mutating operations accept functional options:

snack.WithSudo()           // prepend sudo
snack.WithAssumeYes()      // auto-confirm prompts
snack.WithDryRun()         // simulate (if DryRunner)
snack.WithVerbose()        // verbose output
snack.WithRefresh()        // refresh index before operation
snack.WithReinstall()      // reinstall even if current
snack.WithRoot("/mnt")     // alternate root filesystem
snack.WithFromRepo("sid")  // install from specific repository

CLI

A companion CLI is included at cmd/snack:

snack install nginx curl     # install packages
snack remove nginx           # remove packages
snack upgrade                # upgrade all packages
snack update                 # refresh package index
snack search redis           # search for packages
snack info nginx             # show package details
snack list                   # list installed packages
snack which /usr/bin/curl    # find owning package
snack hold nginx             # pin package version
snack unhold nginx           # unpin package version
snack clean                  # autoremove + clean cache
snack detect                 # show detected managers + capabilities
snack version                # show version

Global flags: --manager <name>, --sudo, --yes, --dry-run

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.

License

0BSD — see LICENSE.