feat: initial project scaffold

- Common Manager interface, Package type, functional options
- Sentinel errors for common package manager failures
- Sub-package stubs for: pacman, aur, apk, apt, dpkg, dnf, rpm,
  flatpak, snap, pkg (FreeBSD), ports (OpenBSD)
- detect/ package for auto-detection of system package manager
- 0BSD license
This commit is contained in:
2026-02-25 20:01:51 +00:00
commit 08514a27e1
18 changed files with 326 additions and 0 deletions

12
LICENSE Normal file
View File

@@ -0,0 +1,12 @@
Copyright (C) 2026 by the gogrlx contributors
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

111
README.md Normal file
View File

@@ -0,0 +1,111 @@
# snack 🍿
Idiomatic Go wrappers for system package managers.
[![License 0BSD](https://img.shields.io/badge/License-0BSD-pink.svg)](https://opensource.org/licenses/0BSD)
[![GoDoc](https://img.shields.io/badge/GoDoc-reference-007d9c)](https://pkg.go.dev/github.com/gogrlx/snack)
**snack** provides thin, context-aware Go bindings for system package managers. Think [`taigrr/systemctl`](https://github.com/taigrr/systemctl) but for package management.
Part of the [grlx](https://github.com/gogrlx/grlx) ecosystem.
## Supported Package Managers
| Package | Manager | Platform | Status |
|---------|---------|----------|--------|
| `pacman` | pacman | Arch Linux | 🚧 |
| `aur` | AUR (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 | Cross-distro | 🚧 |
| `snap` | snapd | Cross-distro | 🚧 |
| `pkg` | pkg(8) | FreeBSD | 🚧 |
| `ports` | ports/packages | OpenBSD | 🚧 |
| `detect` | Auto-detection | All | 🚧 |
## Install
```bash
go get github.com/gogrlx/snack
```
## Usage
```go
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, []string{"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
```go
import "github.com/gogrlx/snack/detect"
mgr, err := detect.Default()
if err != nil {
log.Fatal(err)
}
fmt.Println("Detected:", mgr.Name())
```
## 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.
- **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)
## CLI
A companion CLI tool is planned for direct terminal usage:
```bash
snack install nginx
snack remove nginx
snack search redis
snack list
snack upgrade
```
## License
0BSD — see [LICENSE](LICENSE).

2
apk/apk.go Normal file
View File

@@ -0,0 +1,2 @@
// Package apk provides Go bindings for apk-tools (Alpine Linux package manager).
package apk

2
apt/apt.go Normal file
View File

@@ -0,0 +1,2 @@
// Package apt provides Go bindings for APT (Advanced Packaging Tool) on Debian/Ubuntu.
package apt

2
aur/aur.go Normal file
View File

@@ -0,0 +1,2 @@
// Package aur provides Go bindings for AUR (Arch User Repository) package building.
package aur

43
detect/detect.go Normal file
View File

@@ -0,0 +1,43 @@
// Package detect provides auto-detection of the system's available package manager.
package detect
import (
"os/exec"
"github.com/gogrlx/snack"
)
// probeOrder defines the order in which package managers are probed.
// The first available manager wins.
var probeOrder = []struct {
name string
bin string
}{
{"pacman", "pacman"},
{"apk", "apk"},
{"apt", "apt-get"},
{"dnf", "dnf"},
{"rpm", "rpm"},
{"flatpak", "flatpak"},
{"snap", "snap"},
{"pkg", "pkg"},
}
// Default returns the first available package manager on the system.
// Returns ErrManagerNotFound if no supported manager is detected.
func Default() (snack.Manager, error) {
// TODO: implement — probe for each manager in order, return first match
return nil, snack.ErrManagerNotFound
}
// All returns all available package managers on the system.
func All() []snack.Manager {
// TODO: implement
return nil
}
// HasBinary reports whether a binary is available in PATH.
func HasBinary(name string) bool {
_, err := exec.LookPath(name)
return err == nil
}

2
dnf/dnf.go Normal file
View File

@@ -0,0 +1,2 @@
// Package dnf provides Go bindings for DNF (Fedora/RHEL package manager).
package dnf

2
dpkg/dpkg.go Normal file
View File

@@ -0,0 +1,2 @@
// Package dpkg provides Go bindings for dpkg (low-level Debian package tool).
package dpkg

31
errors.go Normal file
View File

@@ -0,0 +1,31 @@
package snack
import "errors"
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")
)

2
flatpak/flatpak.go Normal file
View File

@@ -0,0 +1,2 @@
// Package flatpak provides Go bindings for Flatpak (cross-distribution application packaging).
package flatpak

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/gogrlx/snack
go 1.26.0

2
pacman/pacman.go Normal file
View File

@@ -0,0 +1,2 @@
// Package pacman provides Go bindings for the pacman package manager (Arch Linux).
package pacman

2
pkg/pkg.go Normal file
View File

@@ -0,0 +1,2 @@
// Package pkg provides Go bindings for pkg(8) (FreeBSD package manager).
package pkg

2
ports/ports.go Normal file
View File

@@ -0,0 +1,2 @@
// Package ports provides Go bindings for OpenBSD ports/packages.
package ports

2
rpm/rpm.go Normal file
View File

@@ -0,0 +1,2 @@
// Package rpm provides Go bindings for RPM (low-level Red Hat package tool).
package rpm

47
snack.go Normal file
View File

@@ -0,0 +1,47 @@
// 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.
package snack
import "context"
// Manager is the common interface implemented by all package manager wrappers.
type Manager interface {
// Install one or more packages.
Install(ctx context.Context, pkgs []string, opts ...Option) error
// Remove one or more packages.
Remove(ctx context.Context, pkgs []string, opts ...Option) error
// Purge one or more packages (remove including config files).
Purge(ctx context.Context, pkgs []string, 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
}

2
snap/snap.go Normal file
View File

@@ -0,0 +1,2 @@
// Package snap provides Go bindings for snapd (Canonical's cross-distribution package manager).
package snap

57
types.go Normal file
View File

@@ -0,0 +1,57 @@
package snack
// Package represents a system 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"`
}
// Options holds configuration for package manager operations.
type Options struct {
Sudo bool
AssumeYes bool
DryRun bool
Root string // alternate root filesystem
Verbose bool
}
// Option is a functional option for package manager operations.
type Option func(*Options)
// WithSudo runs the command with sudo.
func WithSudo() Option {
return func(o *Options) { o.Sudo = true }
}
// WithAssumeYes automatically confirms prompts.
func WithAssumeYes() Option {
return func(o *Options) { o.AssumeYes = true }
}
// WithDryRun simulates the operation without making changes.
func WithDryRun() Option {
return func(o *Options) { o.DryRun = true }
}
// WithRoot sets an alternate root filesystem path.
func WithRoot(root string) Option {
return func(o *Options) { o.Root = root }
}
// WithVerbose enables verbose output.
func WithVerbose() Option {
return func(o *Options) { o.Verbose = true }
}
// ApplyOptions processes functional options into an Options struct.
func ApplyOptions(opts ...Option) Options {
var o Options
for _, opt := range opts {
opt(&o)
}
return o
}