A lean, mean, package-managing machine. In Go.
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.
- π¦ Formula + cask installs with SHA256 verification (no funny business)
- β‘ Binary delta updates β
selfupdateusesbspatchto download only the differences between versions, saving bandwidth - π Dual-hash verification β self-updates and release assets are verified against both SHA256 and SHA512 to prevent single-algorithm collision attacks
- π Sandboxed source builds using macOS Seatbelt or Linux namespaces to keep your system safe
- π Sandboxed post-install scripts β keg is read-only, network denied, minimal env (Homebrew runs these unsandboxed)
- βοΈ Ed25519 bottle signing β cryptographic signatures on downloads, verified against a local trust store
- π·οΈ Signed tap verification β refuse or warn on unsigned git commits in tap repos (
HOMEGREW_TAP_VERIFY) - π Install snapshots β per-file SHA256 manifests recorded at install time for integrity verification
- π Lockfile β pin exact versions, hashes, and dependency trees for reproducible environments
- π Deterministic linking with opt symlinks and dry-run support (look before you link)
- π Keg relocation β rewrites hardcoded library paths in bottles at install time via
install_name_tool(macOS) andpatchelf(Linux), so binaries just work withoutDYLD_LIBRARY_PATHhacks - π³ Dependency resolver with an optional tree view (for the visually inclined)
- π©Ί Doctor that checks perms, HTTPS, broken links, snapshot integrity, stale kegs, and cask notarization
- π‘οΈ Hardened command execution β
--end-of-options on all external commands, POSIX shell quoting via shellescape, XML-safe plist generation, systemd specifier escaping - π§± Zip Slip protection β archive extraction validates symlink indirection to prevent writes outside the destination
- π Vulnerability scanning β queries OSV.dev for known CVEs, checks signatures, permissions, and file integrity
- πͺ΅ Structured logging via
log/slogwith CLI-friendly output (DEBUG/INFO/WARN/ERROR levels,-v/-dflags) - π Alias + shellenv helpers so your workflows stay snappy
go install github.com/homegrew/grew/tools/getgrew
getgrew && sudo grew setupPrerequisites: 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 grewgrew needs a home β a directory tree for the Cellar, symlinks, taps, and config. The setup command creates it and copies the binary into place:
sudo ./grew setup # macOS ARM β /opt/homegrew, Intel/Linux β /usr/local/homegrewThe 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.
Add this to your shell profile so grew-installed binaries and libraries are available:
# bash (~/.bashrc) or zsh (~/.zshrc)
eval "$(grew shellenv)"
# fish (~/.config/fish/config.fish)
grew shellenv fish | sourcegrew install jq
grew install --cask firefoxThat's it. No dark rituals. No 47-step setup guide.
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):
Uninstall casks and stop services first so grew can clean up /Applications and your service managers (launchd/systemd):
# 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; done2. 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.
For an in-depth look at how grew installs itself, its self-update mechanism, and the developer mode, check out the Architecture & Technical Details.
grew install jq nmap # install multiple formulas
grew install -s ldns # build from source, like a purist
grew install --cask firefox # going big
grew link jq # stitch it in
grew deps --tree jq # what hath jq wrought
grew upgrade # stay fresh
grew cleanup -n # peek before you sweep
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 leaves -r | xargs grew uninstall # uninstall all top-level packages installed on request| Command | What it does |
|---|---|
install |
Install formulas or casks (-s to build from source) |
uninstall |
Send formulas or casks to the void |
list |
See what you've collected |
leaves [-r] [-p] |
List installed formulas that are not dependencies of another installed formula |
info |
Stalk packages |
search |
Find the thing |
link |
Weave formulas into your PATH |
unlink |
Cut the thread |
update |
Refresh tap definitions |
upgrade |
Get the new hotness |
outdated |
The hall of shame |
reinstall |
Uninstall + install from scratch |
cleanup |
Marie Kondo your Cellar |
deps |
Dependency spelunking |
alias |
Name things your way |
verify |
Check installed packages against their snapshot manifests |
audit |
Lint formula/cask definitions for quality and security |
lock |
Generate, check, or show a reproducible lockfile |
sign |
Sign formula SHA256 hashes with an Ed25519 key |
services |
Manage background services (start, stop, restart, list) |
setup |
One-time prefix setup (requires sudo) |
doctor |
It's not a bug, it's a misconfiguration |
vuln-scan |
Scan installed packages for security vulnerabilities |
config |
What grew thinks it knows |
shellenv |
Wire up your shell |
pin / unpin |
Freeze formulas to prevent upgrades |
completion |
Generate shell completion (bash, zsh, fish) |
help |
You got this |
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 |
/Applications |
Where casks live |
HOMEGREW_TAP_VERIFY |
off |
Tap commit signature policy (off, warn, strict) |
HOMEGREW_ALLOWED_HOSTS |
(built-in allowlist) | Additional hosts for SSRF-protected downloads |
Everything else 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`)
make check # go test -v -race ./...
make build # go generate + go build (release β requires root at runtime)
make dev # go generate + go build -tags devmode (developer build)
make lint # golangci-lint (if installed)Release builds require root (sudo grew setup). For local development you can build with the devmode tag and pass --unsafe to setup to install to ~/.homegrew without root:
make dev
./grew setup --unsafe # installs to ~/.homegrew as your user
./grew install jq # works without rootBoth gates are required β the build tag compiles in the code path, and --unsafe activates it at setup time. Release binaries ignore --unsafe entirely.
grew/
βββ internal/
β βββ cmd/ β CLI commands (the face)
β βββ cellar/ β installed package management
β βββ config/ β prefix + path resolution
β βββ depgraph/ β dependency resolution (Kahn's toposort)
β βββ downloader/ β HTTP download + SHA256 + archive extraction (Zip Slip protected)
β βββ flags/ β global CLI flags (-v, -d) shared across all subcommands
β βββ formula/ β formula parsing and validation
β βββ cask/ β cask parsing and Caskroom
β βββ linker/ β deterministic symlink management
β βββ lockfile/ β reproducible environment pinning
β βββ relocation/ β keg relocation (rewrite dylib/ELF paths via install_name_tool/patchelf)
β βββ runtime/ β runtime environment (root detection, prefix, devmode gate)
β βββ sandbox/ β build + post-install sandboxing (macOS/Linux, shell-safe quoting)
β βββ service/ β background service management (launchd/systemd, properly escaped)
β βββ signing/ β Ed25519 bottle signing + trust store
β βββ snapshot/ β per-file manifest capture + integrity verification
β βββ tap/ β tap repo management + commit verification
β βββ version/ β embedded version from git tags
βββ pkg/
β βββ logger/ β CLI-friendly log/slog handler (DEBUG/INFO/WARN/ERROR)
β βββ safepath/ β safe path manipulation to prevent directory traversal
β βββ validation/ β name/version/SHA256/path validation (shared across packages)
βββ tools/ β grew-genrepo (Homebrew formula/cask conversion), getgrew (installer)
grew is designed to be more secure than Homebrew out of the box:
| Feature | grew | Homebrew |
|---|---|---|
| Bottle signing | Ed25519 signatures verified against local trust store | None β relies on HTTPS + SHA256 only |
| Tap verification | Optional GPG/SSH commit signature enforcement | None |
| Post-install sandbox | Read-only keg, no network, minimal env | Unsandboxed |
| Source build sandbox | macOS Seatbelt / Linux bwrap+unshare, no network | macOS Seatbelt only, no Linux |
| Install manifests | Per-file SHA256 snapshot at install time | None |
| Lockfile | Full dependency tree with hashes | None |
| Integrity check | grew verify + grew doctor snapshot check |
None |
| Dual-hash verification | Self-updates and release assets use both SHA256 and SHA512 | None |
| Self-update health check | Patched binaries are execution-tested in a sandbox before replacement | None |
| HTTPS enforcement | At parse time β HTTP URLs rejected before download | At download time |
| Path traversal protection | Validated at cellar, linker, loader, and archive extraction layers | Partial |
| Shell injection prevention | POSIX shell quoting via shellescape for sandbox scripts; systemd ExecStart and launchd plist values properly escaped |
N/A |
| Zip Slip protection | Symlink indirection attacks blocked during tar/zip extraction | Partial |
| Command argument hardening | -- end-of-options separator 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.
Got ideas? Bugs? Grievances? β Open an issue
Hot takes on the list:
- SLSA provenance attestations for bottles
- Content-addressable bottle storage
- Windows support (one day, probably, maybe)
- Fork it
- Branch it (
git checkout -b feature/your-cool-thing) - Commit it (
git commit -m "Add the cool thing") - Push it (
git push origin feature/your-cool-thing) - PR it
PRs welcome. Drama not so much.
No license file yet β add a LICENSE to clarify what others can and can't do with your code. (It's the responsible thing to do. We believe in you.)
- π Open an issue
- π Project on GitHub
- Best-README-Template β the scaffold beneath the scaffold
- Everyone who ever squinted at a wall of package manager output and thought "there has to be a better way"