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 dual SHA-256/512 verification (no funny business)

🚰

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

⚑

Multi-hop binary delta updates β€” selfupdate applies sequences of intermediate patches to reach the latest version seamlessly

✍️

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

πŸ”’

Sandboxed builds & post-install β€” macOS Seatbelt enforcement, network denied, keg read-only for post-install

πŸ“‹

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

πŸ“Œ

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

πŸ”„

Keg relocation β€” rewrites hardcoded library paths in bottles at install time via install_name_tool or patchelf

🩺

Doctor + audit β€” checks perms, HTTPS, broken links, snapshot integrity, and formula quality

πŸ”

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

🎨

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

🧱

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

🧾

Installation receipts β€” stores build options, dependencies, and provenance metadata in the keg

πŸͺ΅

Structured logging via log/slog with CLI-friendly output β€” DEBUG, INFO, WARN, ERROR levels

🐚

Alias + shellenv helpers β€” manage daemons, name things your way, and wire up your shell with snappy aliases

πŸš€ Getting Started

1. Get grew

Download the latest release for your platform, 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 ./internal/... && go build -o grew

2. Set up the prefix

grew needs a home β€” a directory tree for the Cellar, symlinks, taps, and config:

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

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 -s ldns           # build from source, like a purist
grew install --cask firefox    # going big

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

πŸ“– Day-to-day usage

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 autoremove                # clean up unused dependencies
grew verify jq                 # check installed files against manifest
grew vuln-scan                 # scan for CVEs and integrity issues
grew lock                      # pin your environment
grew audit --strict            # lint your formulas
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) & Linux:
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 a formula or cask (-s to build from source, -f to force)
uninstall, rmSend it to the void (-f to ignore missing)
autoremoveUninstall formulae that are no longer needed (no longer a dependency)
reinstallUninstall + install from scratch
list, lsSee what you've collected
leavesList installed formulas that are not dependencies of others
info, abvStalk a package
searchFind the thing
linkWeave a formula into your PATH
unlinkCut the thread
update, upRefresh tap definitions
upgrade, ugGet the new hotness
outdatedThe hall of shame
cleanupMarie Kondo your Cellar (--scrub for aggressive cleaning)
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
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 a formula to prevent upgrades
completionGenerate shell completion (bash, zsh, fish)
configWhat grew thinks it knows
shellenvWire up your shell
versionPrint version and exit
--cacheDisplay download cache root or specific package paths
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/Linux)
β”œβ”€β”€ 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 / Linux namespaces, no networkmacOS Seatbelt only, no Linux sandbox
Dual-hash verificationSelf-updates and assets use both SHA256 and SHA512SHA256 only
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 stored alongside the manifestMetadata stored in INSTALL_RECEIPT.json
LockfileFull dependency tree with exact hashesNone
Integrity checkgrew verify + grew doctor snapshot checkNone
Vulnerability scanningIntegrated vuln-scan via OSV.devNone (requires external tools)
macOS QuarantineAutomatically applies com.apple.quarantine to all downloadsNone
HTTPS enforcementAt parse time β€” HTTP URLs rejected before downloadAt download time
Path traversal protectionValidated at cellar, linker, and archive extraction layersPartial
Shell injection preventionNamespace setup uses positional parameters; systemd/launchd values escapedN/A
Zip Slip protectionSymlink indirection attacks blocked during extractionPartial
Command hardening-- end-of-options on all external commands (git, tar, etc.)Not consistently applied

Gradual rollout: signature verification doesn't block installs until you add keys to etc/trusted-keys. Adopt security features at your own pace.

πŸ› οΈ Development

make check  # go test -v -race ./...
make build  # go generate + go build
make dev    # devmode build (user-local)
make lint   # golangci-lint

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/cool-thing)
  3. Commit it
  4. Push it
  5. PR it

PRs welcome. Drama not so much.

πŸ“¬ Links & Info

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".

License: (Add a LICENSE to your repo!)