nanobrew logo

justrach%2Fnanobrew | Trendshift

# nanobrew A fast package manager for macOS and Linux. Written in Zig. Native install pipeline for the top 100 Homebrew formulae and top 100 casks (no `brew`, no Ruby), with verified Homebrew fallback for everything else and native `.deb` support for Linux/Docker. ## Why nanobrew? - **Fast warm installs** — already-installed no-ops return in milliseconds (5.8ms on the v0.1.192 sandboxed `yt-dlp` benchmark) - **Parallel downloads** — all dependencies download and extract at the same time - **No Ruby runtime** — single static binary, instant startup - **No auto-update** — `nb install` just installs; self-update is explicit via `nb update` - **No quarantine** — cask installs skip `com.apple.quarantine`, so apps open without Gatekeeper prompts - **Native installs** — top 100 formulae and top 100 casks install without Homebrew, Ruby, or `brew` subprocess (v0.1.192) - **Third-party taps** — `nb install user/tap/formula` just works. The only fast Homebrew client with tap support - **Drop-in Homebrew replacement** — same formulas, same bottles, same casks - **Linux + Docker** — native .deb support, **up to 13x faster** than apt-get on warm installs ## nanobrew vs Homebrew Homebrew is great software and powers millions of dev machines. nanobrew makes different tradeoffs: | | Homebrew | nanobrew | |---|---------|----------| | **Auto-update** | `brew install` runs `brew update` first (can take minutes) | `nb install` just installs. Self-update is explicit via `nb update`. | | **Gatekeeper quarantine** | Casks get `com.apple.quarantine` — triggers "Are you sure?" dialog | No quarantine flag — apps open immediately | | **Parallel downloads** | Sequential by default; set `HOMEBREW_DOWNLOAD_CONCURRENCY` to change | All dependencies download simultaneously out of the box | | **Runtime** | Ruby (~57 MB) | Single 1.2 MB static binary. Instant startup, no bootstrapping. | | **Brewfile no-ops** | `brew bundle` rechecks everything (~10s even when satisfied) | `nb bundle install` returns instantly when nothing to do | If you rely on `post_install` hooks, build-from-source options, or Mac App Store integration, Homebrew is still the right choice. nanobrew covers the fast path: bottles, casks, and bundles. | Package | Homebrew | zerobrew (cold) | zerobrew (warm) | nanobrew (cold) | nanobrew (warm) | |---------|----------|-----------------|-----------------|-----------------|-----------------| | **tree** (0 deps) | 4.070s | 1.254s | 0.242s | **0.507s** | **0.009s** | | **ffmpeg** (11 deps) | 14.252s | 3.986s | 2.147s | **1.624s** | **0.287s** | | **wget** (6 deps) | 3.935s | 5.502s | 0.587s | **3.211s** | **0.027s** | > Benchmarks on Apple Silicon (GitHub Actions macos-14), 2026-03-23. Auto-updated weekly. | | nanobrew | zerobrew | Homebrew | |---|---------|----------|----------| | **Binary size** | **1.2 MB** | 7.9 MB | 57 MB (Ruby runtime) | > nanobrew is **6.8x smaller** than zerobrew and **47x smaller** than Homebrew. See how these are measured in the [benchmark workflow](.github/workflows/benchmark.yml). ### Linux / Docker — nanobrew vs apt-get nanobrew's `--deb` mode is a full apt-get replacement: fetches APT package indices, resolves dependencies, downloads and extracts `.deb` files — all in pure Zig with no subprocess calls. | Package set | Deps | apt-get | nanobrew (warm) | Speedup | |-------------|------|---------|-----------------|---------| | **curl wget** | 35 | 3,426ms | **448ms** | **7.6x** | | **curl wget tree jq htop tmux** | 53 | 3,584ms | **521ms** | **6.9x** | | **git vim build-essential** | 116 | 43,833ms | **3,402ms** | **12.9x** | | **nginx redis-server postgresql-client** | 78 | 5,501ms | **1,402ms** | **3.9x** | > Verified benchmarks on Ubuntu 24.04.4 LTS (aarch64, Docker/Colima), median of 3 runs. Warm = NBIX binary index cache + cached .deb blobs, `--skip-postinst`. See `bench/` for reproduction. **What makes it fast:** - **NBIX binary index cache** — 70K packages deserialized in 32ms (vs 3s HTTP + 72MB gzip decompress + text parse) - **8-thread parallel .deb downloads** with HTTP connection reuse - **8-thread parallel extraction** — concurrent ar/gzip/tar parsing via native Zig tar - **Arena allocator** — single `deinit()` frees all 70K parsed packages ## Install ```bash # One-liner curl -fsSL https://nanobrew.trilok.ai/install | bash # Or via Homebrew brew tap justrach/nanobrew https://github.com/justrach/nanobrew brew install nanobrew # Or build from source (needs Zig 0.16.0+) git clone https://github.com/justrach/nanobrew.git cd nanobrew && ./install.sh ``` ### Upgrading ```bash # v0.1.193 and later: self-update works in one call nb update ``` If you're on **v0.1.192 or older** and `nb update` errors with `could not download SHA256 checksum`, the self-updater on your installed binary can't reach past a redirect-chain bug in its native HTTP client (fixed in v0.1.193). Re-run the installer once to get unstuck, then `nb update` will work for all future releases: ```bash curl -fsSL https://nanobrew.trilok.ai/install | bash ``` The same bug also affected exactly v0.1.190 via a different code path (file-naming bug in the extract step); the same one-liner unsticks it. ## Usage ### Basics ```bash nb install tree # install a package nb install ffmpeg wget curl # install multiple at once nb install --shims yt-dlp # expose yt-dlp, keep dependency tools private nb remove tree # uninstall nb list # see what's installed nb info jq # show package details nb search ripgrep # search formulas and casks ``` ### Installing a specific version Pin any Homebrew formula to an exact version with `name@version`: ```bash nb install hexyl@0.17.0 # install exactly hexyl 0.17.0 nb install wget@1.21.3 # install exactly wget 1.21.3 ``` nanobrew resolves the requested version from the bottles Homebrew keeps in its container registry. Versioned installs are **auto-pinned** so a later `nb upgrade` won't replace your chosen version — run `nb unpin ` to allow upgrades again. Notes and limitations: - Works for **Homebrew formulae** (macOS + Linux bottles). Casks (`--cask`) and deb packages (`--deb`) don't support version pinning yet. - If the exact version isn't available as a bottle for your platform (old bottles can be garbage-collected), nb lists the available versions and points you at the latest instead of guessing. - Dependencies are resolved against the **latest** formula, not the historical one — fine for typical CLI tools; see [`docs/design/versioned-install.md`](docs/design/versioned-install.md) for details. Homebrew's **versioned formulae** (separate packages whose name contains `@`) also work as before, and take precedence over version pinning: ```bash nb install python@3.11 # the python@3.11 formula (distinct from python) nb install node@22 # the node@22 formula nb install openssl@3 # the openssl@3 formula ``` Related version controls once a package is installed: ```bash nb pin tree # hold tree at its current version (skip upgrades) nb unpin tree # allow upgrades again nb rollback tree # revert to the previously installed version ``` ### Shimmed Installs ```bash nb install --shims yt-dlp ``` Shimmed installs are an experimental link mode for packages whose dependencies ship command-line tools you do not want exposed globally. The requested formula gets wrapper shims in `/opt/nanobrew/prefix/bin`; dependency executables are kept out of `prefix/bin` and are only added to that wrapper's private `PATH`. This keeps commands like `deno` or `python` available to the requested tool without making those dependency executables first-class shell commands. You can also enable this mode for formula installs with `NANOBREW_SHIMS=1`. ### Third-Party Taps ```bash nb install steipete/tap/sag # install from a third-party tap nb install indirect/tap/bpb # taps with bottles work too ``` nanobrew fetches the Ruby formula directly from GitHub, parses it, and installs — no `brew tap` step needed. Supports bottles, source builds, and pre-built binaries. ### macOS Apps (Casks) ```bash nb install --cask firefox # install a .dmg/.pkg/.zip app nb remove --cask firefox # uninstall it nb upgrade --cask # upgrade all casks ``` As of v0.1.192, the top 100 casks install through nanobrew's native pipeline — no `brew` subprocess, no Homebrew prefix, no Ruby. Native cask support covers apps, `.pkg`, fonts, binaries, suites, copied artifacts, installer scripts, `.tar.xz`, and extensionless vendor URLs. Casks outside the top 100 still fall back to the verified Homebrew path. ### Linux / Docker (deb packages) ```bash nb install --deb curl wget git # install from Ubuntu/Debian repos nb remove --deb curl # remove a deb package nb upgrade --deb # upgrade all installed deb packages nb list # shows deb packages alongside brew packages nb outdated # checks deb packages for newer versions too ``` ```dockerfile # Replace slow apt-get in Dockerfiles COPY --from=nanobrew/nb /nb /usr/local/bin/nb RUN nb init && nb install --deb curl wget git ``` - Auto-detects distro and architecture (Ubuntu/Debian, amd64/arm64) - Resolves virtual packages via `Provides:` field (e.g. `build-essential` works) - Picks the best alternative when multiple packages satisfy a dependency - Runs `postinst` scripts and `ldconfig` so shared libraries work out of the box - Tracks installed files in `state.json` for clean removal - Content-addressable cache — warm installs are instant ### Keep packages up to date ```bash nb outdated # see what's behind nb upgrade # upgrade everything nb upgrade tree # upgrade one package nb pin tree # prevent a package from upgrading nb unpin tree # allow upgrades again ``` ### Undo and backup ```bash nb rollback tree # revert to the previous version nb bundle dump # export installed packages to a Nanobrew file nb bundle install # reinstall everything from a Nanobrew file ``` ### Diagnostics ```bash nb doctor # check for common problems nb cleanup # remove old caches and orphaned files nb cleanup --dry-run # see what would be removed first ``` ### Download Telemetry ```bash nb telemetry status nb telemetry off nb telemetry on ``` nanobrew sends anonymized, best-effort download timing events to `https://backend.trilok.ai/v1/telemetry/system`. This helps prioritize which packages and casks should get native nanobrew support first, based on real download/install demand and slow paths. The exact event shape is: ```json { "schema": 1, "source": "nanobrew", "event": "download", "os": "macos", "arch": "arm64", "ram_gb": 128, "cpu_count": 10, "operation": "download", "target_kind": "formula", "target_name": "uv", "duration_ms": 120, "download_bytes": 33000000, "success": true } ``` It does not send URLs, paths, hostnames, usernames, IPs, user IDs, full package lists, or command history. `target_name` is only a package-like token such as `uv`, `firefox`, or `owner/tap/pkg`. You can opt out with `nb telemetry off`, `NANOBREW_NO_TELEMETRY=1`, or `NANOBREW_TELEMETRY=0`. ### Dependencies and services ```bash nb deps ffmpeg # list all dependencies nb deps --tree ffmpeg # show dependency tree nb services list # show launchctl services from installed packages nb services start postgresql # start a service nb services stop postgresql # stop a service ``` ### Shell completions ```bash nb completions zsh >> ~/.zshrc nb completions bash >> ~/.bashrc nb completions fish > ~/.config/fish/completions/nb.fish ``` ### Other ```bash nb update # self-update nanobrew nb init # create directory structure (run once) nb help # show all commands ``` ## How it works ``` nb install ffmpeg # macOS: Homebrew bottles │ ├─ 1. Resolve dependencies (BFS, parallel API calls) ├─ 2. Skip anything already installed (warm path: ~3.5ms) ├─ 3. Download bottles in parallel (native HTTP, streaming SHA256) ├─ 4. Extract into content-addressable store (/opt/nanobrew/store/) ├─ 5. Clone into Cellar via APFS clonefile (zero-copy, instant) ├─ 6. Relocate Mach-O headers + batch codesign └─ 7. Symlink binaries into /opt/nanobrew/prefix/bin/ nb install --deb curl # Linux: .deb packages │ ├─ 1. Detect distro from /etc/os-release (Ubuntu/Debian, amd64/arm64) ├─ 2. Fetch + decompress package index (main + universe components) ├─ 3. Build provides map for virtual package resolution ├─ 4. Resolve dependencies (topological sort, index-aware alternatives) ├─ 5. Download .debs with streaming SHA256 verification ├─ 6. Parse ar archive, decompress data.tar natively (zstd/gzip) ├─ 7. Extract to / and track installed files in state.json ├─ 8. Run postinst scripts (ca-certificates, ldconfig, etc.) └─ 9. Run ldconfig for shared library registration nb install steipete/tap/sag # Third-party taps │ ├─ 1. Detect tap syntax (user/tap/formula) ├─ 2. Fetch Ruby formula from GitHub (raw.githubusercontent.com) ├─ 3. Parse .rb file (version, url, sha256, deps, bottle blocks) ├─ 4. Resolve dependencies normally (they're homebrew-core names) └─ 5. Install via bottle or source path (same pipeline as above) ``` Dependency ordering walks the explicit formula graph and topologically sorts it in `O(V+E)`. The `O(1)` resolver improvement in v0.1.190 refers to queue dequeue during that sort, not solving arbitrary version constraints. Key design choices: - **Content-addressable store** — deduplicates bottles by SHA256. Reinstalls are instant because the data is already there. - **APFS clonefile** — copy-on-write on macOS means no extra disk space when materializing from the store. - **Streaming SHA256** — hash is verified during download, no second pass over the file. - **Native binary parsing** — reads Mach-O (macOS) and ELF (Linux) headers directly instead of spawning `otool`/`patchelf`. - **Native ar + decompression** — .deb extraction without `dpkg`, `ar`, or `zstd` binaries. Only needs `tar`. - **Single static binary** — no runtime dependencies. 1.2 MB. ## Testing ```bash # Run all tests (macOS — native) zig build test # Run individual module tests with verbose output zig test src/deb/index.zig # 7 tests: package parsing, provides map zig test src/deb/resolver.zig # 17 tests: dependency resolution, virtual packages # Cross-compile and run on Linux via Colima/Docker zig build test -Dtarget=aarch64-linux # cross-compile to static ELF docker run --rm -v .zig-cache/o//test:/test alpine /test # Or as a one-liner (find the binary automatically) docker run --rm -v "$(find .zig-cache -name test -newer build.zig | head -1):/t:ro" alpine /t ``` Zig's cross-compilation produces a statically-linked binary that runs directly in any Linux container — no need to install Zig or any toolchain inside Docker. ## Contributing Follow [CONTRIBUTING.md](./CONTRIBUTING.md) for all future issues and PRs. The short version: - every PR must be tied to an issue - every fix must show red-to-green proof - every non-trivial branch must be rebased onto current `main` - PRs over 500 changed lines will usually be rejected unless they are clearly justified, tightly scoped, and good enough to survive strict review ## Directory layout ``` /opt/nanobrew/ cache/ blobs/ # downloaded bottles (by SHA256) api/ # cached formula metadata (5-min TTL) tokens/ # GHCR auth tokens (4-min TTL) tmp/ # partial downloads store/ # extracted bottles (by SHA256) prefix/ Cellar/ # installed packages Caskroom/ # installed casks bin/ # symlinks to binaries opt/ # symlinks to keg dirs db/ state.json # installed package state ``` ## Homebrew Compatibility nanobrew uses Homebrew's formulas, bottles, and cask definitions. It's a faster client for the same ecosystem — not a fork. ### What works - **Bottle installs** — all pre-built Homebrew bottles install correctly - **Cask installs** — `.dmg`, `.zip`, `.pkg`, and `.tar.gz` casks - **Dependency resolution** — same transitive deps as Homebrew - **Third-party taps** — `nb install user/tap/formula` fetches from GitHub - **Shared Cellar** — packages install to `/opt/nanobrew/prefix/Cellar/` (same layout as Homebrew) - **Bundle/Brewfile** — `nb bundle dump` and `nb bundle install` for common `brew "pkg"` and `cask "pkg"` lines ### What doesn't work (yet) - **Ruby `post_install` hooks** — Homebrew formulae with Ruby `post_install` blocks won't run those hooks. Most bottles don't need them. - **Build from source with custom options** — `args: ["with-feature"]` in Brewfiles is ignored - **`tap` command** — nanobrew auto-fetches taps inline; standalone `brew tap` is not needed - **Mac App Store (`mas`)** — not supported - **Complex Ruby DSL in Brewfiles** — conditional blocks, custom Ruby code ### Migration from Homebrew ```bash nb migrate # scan /opt/homebrew/Cellar and Caskroom, import into nanobrew's DB ``` After migration, `nb list`, `nb outdated`, and `nb upgrade` will see your existing packages. ### Switching back to Homebrew Packages installed by nanobrew live in `/opt/nanobrew/prefix/Cellar/` — they don't interfere with Homebrew's `/opt/homebrew/Cellar/`. You can safely remove nanobrew with `nb nuke` without affecting Homebrew. ## Project status **Experimental** — works well for common packages. If something breaks, [open an issue](https://github.com/justrach/nanobrew/issues). License: [Apache 2.0](./LICENSE) ## All commands | Command | Short | What it does | |---------|-------|-------------| | `nb install ` | `nb i` | Install packages | | `nb install --cask ` | | Install macOS apps | | `nb install --deb ` | | Install .deb packages (Linux/Docker) | | `nb install user/tap/formula` | | Install from a third-party tap | | `nb remove ` | `nb ui` | Uninstall packages | | `nb remove --deb ` | | Remove a .deb package (Linux/Docker) | | `nb list [--versions\|--names]` | `nb ls` | List installed packages, version history, or names-only output | | `nb leaves [--tree]` | | List installed formulae with no dependents | | `nb where ` | `nb wh` | Show installed kegs, prefix files, and index hits matching pattern | | `nb info ` | | Show package details | | `nb info --cask ` | | Show cask details | | `nb search ` | `nb s` | Search formulas and casks | | `nb upgrade [pkg]` | | Upgrade packages | | `nb upgrade --deb` | | Upgrade all installed .deb packages | | `nb outdated` | | List outdated packages (brew + deb) | | `nb pin ` | | Prevent upgrades | | `nb unpin ` | | Allow upgrades | | `nb rollback ` | `nb rb` | Revert to previous version | | `nb bundle dump` | | Export installed packages | | `nb bundle install` | | Import from bundle file | | `nb doctor` | `nb dr` | Health check | | `nb cleanup` | `nb clean` | Remove old caches | | `nb deps [--tree] ` | | Show dependencies | | `nb services` | | Manage services (launchctl/systemd) | | `nb completions ` | | Print shell completions | | `nb telemetry [status\|on\|off]` | | View or change telemetry opt-in | | `nb nuke` | | Remove all of nanobrew's state | | `nb migrate` | | Import packages from Homebrew | | `nb update` | | Self-update nanobrew | | `nb init` | | Create directory structure | | `nb help` | | Show help | See [CHANGELOG.md](./CHANGELOG.md) for version history.