grew

A lean, mean, package-managing machine. In Go.

Go 1.26+

grew is what happens when you look at your package manager and think: "This could be so much simpler." Deterministic installs. Clean symlinks. A doctor that actually tells you what's wrong. No drama.

πŸ’¬ A word from the author:

"I've been a die-hard Homebrew user for longer than I care to admit. brew and I? We go way back. Late nights, broken PATH, the works β€” and I loved every minute of it. I love brew so much, in fact, that I thought: 'What if I just… made it better?' Audacious? Absolutely. Foolish? Possibly. Fun? You bet. grew is my love letter to brew β€” written in Go, with a cheeky grin."

✨ What it does

πŸ“¦

Formula + cask installs with SHA256 verification (no funny business)

🚰

Tap auto-install β€” automatically clones missing taps when you request user/repo/formula

⚑

Multi-hop binary delta updates β€” selfupdate uses bspatch to seamlessly apply sequences of intermediate patches to reach the latest version, saving bandwidth, with an automated CI patcher tool for releases and -U upgrade path verification

πŸ”

Dual-hash verification β€” self-updates and release assets are verified against both SHA256 and SHA512 to prevent single-algorithm collision attacks

✍️

Ed25519 bottle signing β€” cryptographic signatures on downloads, verified against a local trust store

πŸ”

Self-update health check β€” patched binaries are execution-tested in a sandbox before replacement, with multi-hop binary delta patching via bspatch

🏷️

Signed tap verification β€” refuse or warn on unsigned git commits in tap repos (HOMEGREW_TAP_VERIFY)

πŸ”’

Sandboxed source builds β€” macOS Seatbelt to keep your system safe

πŸ”’

Sandboxed post-install scripts β€” keg is read-only, network denied, minimal env (Homebrew runs these unsandboxed)

πŸ“‹

Install snapshots β€” per-file SHA-256 manifests (.MANIFEST.json) recorded at install time for integrity verification

🧾

Installation receipts β€” stores build options, dependencies, and provenance metadata (INSTALL_RECEIPT.json) in the keg for future reference

πŸ“Œ

Lockfile β€” pin exact versions, hashes, and dependency trees for reproducible environments

πŸ”—

Deterministic linking with opt symlinks and dry-run support (look before you link)

πŸ›‘οΈ

Symlink conflict detection β€” ownership tracking prevents accidental overwrites when multiple formulas claim the same binary; version-family defense blocks incompatible family members (e.g., node@24 when node is already linked)

πŸ“š

Comprehensive documentation β€” architecture guides, security deep-dives, and dependency resolver design in expanded technical documentation; contributor guides for understanding the codebase

πŸ”„

Keg relocation β€” rewrites hardcoded library paths in bottles at install time via install_name_tool, so binaries just work without DYLD_LIBRARY_PATH hacks

🌳

Dependency resolver with an optional tree view (for the visually inclined)

πŸ”—

Missing dependency checker β€” validates runtime dependencies are available on the system; useful for catching broken kegs or library mismatches

πŸ“

Description search β€” query by formula name, one-line description, or regex across both formulae and casks with flexible filtering

🩺

Doctor + audit β€” checks perms, HTTPS, broken links, snapshot integrity, stale kegs, and cask notarization

πŸ›‘οΈ

Hardened command execution β€” -- end-of-options on all external commands, shell-free namespace setup with positional parameters, XML-safe plist generation

🧱

Zip Slip protection β€” archive extraction validates symlink indirection to prevent writes outside the destination

πŸ”

Vulnerability scanning β€” queries OSV.dev for known CVEs via the integrated vuln-scan command

πŸ›‘οΈ

macOS Quarantine β€” automatically applies com.apple.quarantine attributes to downloaded apps and binaries, ensuring Gatekeeper protection is active

πŸͺ΅

Structured logging via log/slog with CLI-friendly output (DEBUG/INFO/WARN/ERROR levels, -v/-d/-q flags). Debug logs include source file and line number context.

🎨

Colorful output β€” ANSI-colored output with automatic TTY detection for a polished, Homebrew-like aesthetic

🐚

Alias + shellenv helpers so your workflows stay snappy (i, rm, ls, up, ug, dr)

πŸš€ Getting Started

1. Get grew

Download the latest release for your platform from the Releases page, extract it, and run setup:

# Download from GitHub Releases
tar -xzf grew_*.tar.gz
./grew setup

Or build from source

Prerequisites: Go 1.26+, git, and a dream.

git clone https://github.com/homegrew/grew.git
cd grew
make build          # or: go generate ./pkg/... && go build -o grew

2. Set up the prefix

grew needs a home β€” a directory tree for the Cellar, symlinks, taps, and config. The setup command creates it and copies the binary into place:

./grew setup   # macOS ARM β†’ /opt/homegrew, Intel β†’ /usr/local/homegrew

The system prefix isolates sandboxed builds from $HOME, preventing them from reaching ~/.ssh, ~/.gnupg, or other sensitive dotfiles. After setup, ownership is transferred to your user β€” no root needed at runtime.

Developer builds (make dev) can install to ~/.homegrew without root using ./grew setup --unsafe.

3. Wire up your shell

# bash (~/.bashrc) or zsh (~/.zshrc)
eval "$(grew shellenv)"

# fish (~/.config/fish/config.fish)
grew shellenv fish | source

4. Install something

grew i jq                      # 'i' is an alias for 'install'
grew install --cask firefox    # going big

That's it. No dark rituals. No 47-step setup guide.

πŸ“– Day-to-day usage

grew i jq nmap                 # install multiple formulas (alias 'i')
grew install --force jq        # force reinstall even if already installed
grew install -s ldns           # build from source, like a purist
grew install --force-bottle jq # pour a bottle (current or newest macOS), never build
grew install --cask firefox    # going big
grew link jq                   # stitch it in
grew deps --tree jq            # what hath jq wrought
grew up                        # stay fresh (alias for update)
grew ug                        # upgrade all (alias for upgrade)
grew version                   # what are we running
grew autoremove --dry-run      # see which orphaned dependencies would be removed
grew autoremove                # clean up unused dependencies
grew rm --force jq             # uninstall even if not installed (alias 'rm')
grew cleanup -n                # peek before you sweep
grew cleanup --scrub           # aggressive cache cleaning
grew cleanup --prune=7         # remove cache older than a week
grew verify jq                 # check installed files against manifest
grew vuln-scan -q              # scan for CVEs, only show critical/high severity findings
grew lock                      # pin your environment
grew audit --strict            # lint your formulas
grew cache                     # show download cache
grew cache jq                  # show cache path for jq
grew cache --os=darwin jq      # show cache path for a different OS
grew leaves -r | xargs grew uninstall  # uninstall all top-level packages installed on request
grew dr                        # check for common problems (alias for doctor)

πŸ—‘οΈ Uninstallation

grew stores most of its data in its prefix directory, but some items (like Cask applications and background services) are linked to system directories. To completely remove grew and all of its traces:

1. Clean up installed packages (Casks and Services)
# Stop and remove all background services
for s in $(grew services ls | awk 'NR>1 {print $1}'); do grew services stop $s; done

# Uninstall all macOS casks
for c in $(grew list --cask | awk '{print $1}'); do grew uninstall --cask $c; done
2. Delete the prefix directory
# macOS (Apple Silicon):
sudo rm -rf /opt/homegrew

# macOS (Intel):
sudo rm -rf /usr/local/homegrew

# Devmode (User-local install via --unsafe):
rm -rf ~/.homegrew
3. Clean up your shell profile

Open your shell configuration file (e.g., ~/.zshrc, ~/.bashrc, or ~/.config/fish/config.fish) and remove the line that initializes grew:

# Remove this line:
eval "$(/opt/homegrew/bin/grew shellenv)"

Restart your terminal, and grew is completely gone.

πŸ—ΊοΈ Commands

Command What it does
install, iInstall formulas or casks (-f to force, -s to build from source, --force-bottle to force a bottle)
uninstall, rmSend formulas or casks to the void (-f to ignore missing or errors, delete all versions)
autoremoveTransitively uninstall formulae that were only installed as a dependency and are no longer needed β€” removes the full orphan chain in one run (--dry-run supported)
reinstallUninstall + install from scratch (--cask, -f without checking for previously installed keg-only or non-migrated versions)
list, lsSee what you've collected
leavesList installed formulas that are not dependencies of another installed formula (-r for explicitly requested, -p for orphaned dependencies)
infoStalk packages
searchFind the thing
linkWeave formulas into your PATH
unlinkCut the thread
update, upRefresh tap definitions
upgrade, ugGet the new hotness
outdatedThe hall of shame
cleanupRemove old versions and prune download cache (-s to scrub all, --prune=DAYS)
depsDependency spelunking (--tree for the visual view)
verifyCheck installed packages against their snapshot manifests
vuln-scanScan installed packages for security vulnerabilities (OSV.dev)
auditLint formula/cask definitions for quality and security
create <url>Scaffold a new formula from a URL (infers name, version, and SHA256)
homepage <formula>Open a formula or cask's homepage in the default browser
uses <formula>Show installed formulae that depend on the specified formula
tap <user/repo>Add a formula repository
untap <user/repo>Remove a tapped formula repository
linkage <formula>Inspect dynamic library dependencies for an installed formula
lockGenerate, check, or show a reproducible lockfile
signSign formula checksums with an Ed25519 key
servicesManage background services (start, stop, restart, list)
setupOne-time prefix setup (requires sudo unless --unsafe)
aliasName things your way
doctor, drIt's not a bug, it's a misconfiguration
pin / unpinFreeze formulas to prevent upgrades
completionGenerate shell completion (bash, zsh, fish)
cacheDisplay download cache root or specific package cache paths
configWhat grew thinks it knows
shellenvWire up your shell
versionPrint version and exit
helpYou got this

βš™οΈ Configuration

grew keeps its stuff tidy under one roof. Tweak it with env vars:

Variable Default What it is
HOMEGREW_PREFIX(inferred from binary location)Root of the grew tree
HOMEGREW_APPDIR/ApplicationsWhere casks live
HOMEGREW_TAP_VERIFYoffTap commit signature policy (off, warn, strict)
HOMEGREW_ALLOWED_HOSTS(built-in allowlist)Additional hosts for SSRF-protected downloads
HOMEGREW_CLEANUP_MAX_AGE_DAYS120Max age in days for cached downloads

Directory Structure

Everything flows from the prefix:

/opt/homegrew/              (or /usr/local/homegrew on Intel)
β”œβ”€β”€ Cellar/        ← installed packages (each keg has a .MANIFEST.json)
β”œβ”€β”€ Taps/          ← formula definitions (git-cloned or API-fetched)
β”œβ”€β”€ bin/           ← symlinked binaries
β”œβ”€β”€ lib/           ← symlinked libraries
β”œβ”€β”€ include/       ← symlinked headers
β”œβ”€β”€ opt/           ← per-formula keg symlinks
β”œβ”€β”€ etc/           ← trusted-keys (Ed25519 public keys, one per line)
β”œβ”€β”€ tmp/           ← ephemeral stuff
β”œβ”€β”€ var/log/       ← audit log
└── grew.lock      ← lockfile (opt-in, created by `grew lock`)

πŸ” Security: grew vs Homebrew

grew is designed to be more secure than Homebrew out of the box:

Feature grew Homebrew
Bottle signingEd25519 signatures verified against local trust storeNone β€” relies on HTTPS + SHA256 only
Tap verificationOptional GPG/SSH commit signature enforcement (HOMEGREW_TAP_VERIFY)None
Post-install sandboxRead-only keg, no network, minimal envUnsandboxed
Source build sandboxmacOS Seatbelt, no networkmacOS Seatbelt only
Dual-hash verificationSelf-updates and release assets use both SHA256 and SHA512None
Self-update health checkPatched binaries are execution-tested in a sandbox before replacementNone
Install manifestsPer-file SHA256 snapshot (.MANIFEST.json) at install timeNone
Installation receiptsProvenance and dependency metadata (INSTALL_RECEIPT.json) stored alongside the manifestMetadata stored in INSTALL_RECEIPT.json
LockfileFull dependency tree with hashesNone
Integrity checkgrew verify + grew doctor snapshot checkNone
Vulnerability scanningIntegrated vuln-scan powered by OSV.devRequires external gems/tools
macOS QuarantineAutomatically applies com.apple.quarantine to all downloads via LaunchServicesNone
HTTPS enforcementAt parse time β€” HTTP URLs rejected before downloadAt download time
Path traversal protectionValidated at cellar, linker, loader, and archive extraction layersPartial
Shell injection preventionNamespace setup uses positional parameters to eliminate injection risks; systemd ExecStart and launchd plist values properly escapedN/A
Zip Slip protectionSymlink indirection attacks blocked during tar/zip extractionPartial
Command hardening-- end-of-options on all external commands (git, systemctl, launchctl, hdiutil, tar, etc.)Not consistently applied

Gradual rollout: signature verification doesn't block installs until you add keys to etc/trusted-keys. Tap verification is opt-in via HOMEGREW_TAP_VERIFY. This lets you adopt security features incrementally.

πŸ—ΊοΈ Roadmap

Got ideas? Bugs? Grievances? β†’ Open an issue

βœ… Recently Completed

πŸš€ Coming Soon

πŸ› οΈ Development

make test-unit        # run unit tests
make test-smoke       # run quick health checks
make test-integration # run command-level integration tests
make test-e2e         # run full lifecycle E2E tests
make check-all        # run all of the above
make build            # go generate + go build
make dev              # devmode build (user-local)

Developer builds (make dev) can install to ~/.homegrew without root using grew setup --unsafe. See the README for details.

πŸ› οΈ Release Tools

grew ships with a standalone patcher tool to generate and verify multi-hop binary updates:

patcher -v -D dist/ v1.0 v1.1 # Generate patches
patcher -U v1.0 v1.2          # Verify upgrade path

🀝 Contributing

  1. Fork it
  2. Branch it (git checkout -b feature/your-cool-thing)
  3. Commit it (git commit -m "Add the cool thing")
  4. Push it (git push origin feature/your-cool-thing)
  5. PR it

PRs welcome. Drama not so much.

πŸ“¬ Links & Info

License: MIT License β€” see the LICENSE file for details.

Acknowledgments: Best-README-Template, and everyone who ever squinted at a wall of package manager output and thought "there has to be a better way".