Ever tried to document how to install your own program?
You end up writing 47 pages just to say "install the dev headers for libfoo", because on Debian it's libfoo-dev, on Fedora it's libfoo-devel, on Arch it's libfoo, on Alpine it's libfoo-dev (different one), on FreeBSD it's foo, and on macOS it's "unidentified developer", because the dev didn't pay Apple's yearly tax to bless the binary.
upkg lets you speak civilized. It speaks the dialect of 87 other package managers for you.
upkg install git curl ripgrepThat's it. Same command on Debian, Fedora, Arch, openSUSE, OpenWrt, Termux, FreeBSD, DragonFly BSD, and macOS.
- One command surface:
upkg install,upkg uninstall,upkg upgrade. - Routes to the platform package manager where that is the right boundary, with a built-in Homebrew-compatible engine on macOS.
- Keeps the names people already know: no new catalog, no parallel universe.
- Stays out of your way for anything advanced. Use
apt,dnf,pacman,pkgdirectly when you need to.
- Not a new global package ecosystem.
- Not a replacement for native tooling when you need flags, repos, or version pinning.
- Not a daemon, not a sync service, not a wrapper that "knows better."
| Platform | Backend |
|---|---|
| Debian/Ubuntu | apt |
| Fedora/RHEL | dnf / yum |
| Arch | pacman |
| openSUSE | zypper |
| OpenWrt | opkg |
| Android (Termux) | pkg (fallback apt) |
| FreeBSD (+ GhostBSD, HardenedBSD) | pkg |
| DragonFly BSD | pkg (DPorts) |
| Any of the above with Ravenports | rvn (takes precedence when installed) |
| macOS | built-in engine (Homebrew-compatible names + prefixes) |
Most package names are identical everywhere, so upkg passes them straight
through to the native manager. The exceptions are packages that genuinely ship
under different names across managers, the classic case being development
headers: libpq-dev on Debian, libpq-devel on Fedora, postgresql-libs on
Arch.
For those, upkg keeps a small compiled-in alias catalog
(src/backend/catalog/aliases.toml). It is divergent names only and is
deliberately not a registry: if a name is the same on every backend it is left
out. Resolution is exact-match with verbatim fallthrough, so an unknown name is
never rewritten or guessed at.
[libpq-dev]
upstream = "https://www.postgresql.org"
apt = "libpq-dev"
dnf = "libpq-devel"
pacman = "postgresql-libs"Contributions are welcome. Every entry names one upstream so a reviewer can
confirm all the per-manager names are the same software, and entries must be
human-verified, not bulk-generated by an LLM. See the header of
aliases.toml for the full policy.
No runtime switch, no shelling out to brew. The engine is built in and keeps the prefixes people expect:
- Apple Silicon:
/opt/homebrew - Intel:
/usr/local
Same formula and cask names. Different engine.
Apps are detected automatically when a name exists as a cask but not as a
formula. Use --app when you want to force cask resolution:
upkg install ghostty
upkg install --app ghostty
upkg uninstall --app ghosttyApp installs are written in the Homebrew-compatible cask layout, so brew info --cask <name> can detect them. Supported cask artifacts currently include
apps, manpages, and bash/fish/zsh completions.
curl -fsSL https://raw.githubusercontent.com/seuros/upkg/master/install.sh | shOr specify a custom install directory:
curl -fsSL https://raw.githubusercontent.com/seuros/upkg/master/install.sh | UPKG_INSTALL_DIR="$HOME/.local/bin" shIf the install directory is not already in PATH, the installer adds it to
your shell profile (.zshrc, .bashrc, fish config, or .profile). Restart
your shell after install. Set UPKG_NO_MODIFY_PATH=1 to skip this.
Use seuros/setup-upkg to install upkg
in a workflow:
steps:
- uses: seuros/setup-upkg@master
- run: upkg install imagemagick@master tracks the latest action and installs the latest upkg release. Pin
both if you need reproducibility: seuros/setup-upkg@v0.9.1 with
with: { version: '0.10.0' }. Works on GitHub Actions, Gitea Actions, and
Forgejo Actions.
upkg install git curl
upkg install ghostty
upkg install --app ghostty
upkg install --dry-run ripgrep
upkg uninstall htop
upkg uninstall --app ghostty
upkg upgrade
upkg list
upkg search ripgrep
upkg search --app ghostty
upkg search --exact git
upkg --self-upgrade
upkg helpupkg search <query> routes to the platform's native search:
| Platform | Runs |
|---|---|
| Debian/Ubuntu | apt search |
| Fedora/RHEL | dnf search |
| Arch | pacman -Ss |
| openSUSE | zypper search |
| OpenWrt | opkg find |
| FreeBSD / DragonFly BSD | pkg search |
| Ravenports | rvn search |
| Android (Termux) | pkg search |
| Windows | winget search / choco search |
| macOS | built-in search against the Homebrew JSON index |
Output passes through verbatim on native backends so you get the
formatting you're used to. On macOS the engine prints a uniform
tab-separated line: kind<TAB>name<TAB>version<TAB>description.
Flags:
--app: on macOS, restrict to casks. No-op (and rejected asUnsupported) on other platforms.--exact,-e: exact name match. Maps topkg/winget/choco/rvn -eand to an anchored^…$regex forpacman/opkg. Returns anUnsupportederror on managers without an exact mode (apt/dnf/yum/zypper).--refresh: macOS only. Forces revalidation of the cached Homebrew index instead of using the on-disk copy (default TTL 12 hours).
The macOS index lives at <root>/cache/homebrew-search/ and is
revalidated with If-None-Match / If-Modified-Since. If the network
or server fails, a stale cached copy is used and a warning is printed
to stderr.
cargo build -p upkg
just qq