Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Package management

Every package layer has a declarative text file and an idempotent install script. All scripts skip already-installed items — safe to re-run at any time.

The layers

LayerFileInstall scriptPlatform
System packagespackages/Brewfileinstall/homebrew.sh / install/linux-packages.shmacOS (bottles) / Linux (native, no container)
Rust toolspackages/cargo.txtinstall/rust.shAll
Python packagespackages/pip.txtinstall/python.shAll
Global npmpackages/npm.txtinstall/node.shAll
Claude pluginspackages/claude-plugins.txtinstall/claude.shAll
Claude MCP serverspackages/claude-mcp.txtinstall/claude.shAll
VS Code extensionspackages/vscode-extensions.txtinstall/vscode.shAll

Adding a package — priority order

Choose the first layer that applies. Native installers first, Homebrew as fallback:

1. cargo — Rust crates

# Add to packages/cargo.txt
fd-find
ripgrep
bat
my-new-tool

Re-run: bash ~/dotfiles/install/rust.sh

install/rust.sh uses cargo-binstall: it tries to download a pre-built binary from GitHub releases first (fast, no compilation), and falls back to cargo install (source compilation) if no binary is available.

On Linux, cargo-binstall avoids the manylinux container round-trip entirely. On macOS, it downloads the same pre-built binary that Homebrew bottles provide — same quality, faster install.

macOS note: Source compilation requires running from a normal terminal. The macOS Sequoia linker enforces com.apple.provenance on object files and will block compilation in sandboxed contexts (e.g., certain CI environments). This isn’t an issue for day-to-day use.

2. npm — npm-specific tools

# packages/npm.txt
@cometix/ccline

Re-run: bash ~/dotfiles/install/node.sh

Currently ships @cometix/ccline — a Rust-based status line for Claude Code with themes and TUI config (ccline --config).

3. pip — Python packages

# packages/pip.txt
requests
black
numpy
some-macos-tool  # macos-only (requires Metal / only available on macOS)
aider-chat       # python=3.12 (scipy has no wheels for python 3.14+)

Re-run: bash ~/dotfiles/install/python.sh

Each tool gets its own isolated venv via uv tool install, with entrypoints in $LOCAL_PLAT/bin/.

Comment conventions parsed by install/python.sh:

  • # macos-only — skipped on Linux (e.g. mlx-lm requires Apple Metal/MLX framework)
  • # python=X.Y — pins to a specific Python version for that tool (e.g. aider-chat needs 3.12 because scipy has no wheels for Python 3.14+)

4. Homebrew — non-language-specific tools and C libraries

# packages/Brewfile
brew "tool-name"

# macOS-only (casks, GUI apps, macOS-specific services)
if OS.mac?
  cask "some-app"
  brew "macos-only-tool"
end

Re-run: brew bundle --file=~/dotfiles/packages/Brewfile

if OS.mac? blocks are silently skipped on Linux. Everything outside those blocks runs on both platforms.

Prefer Homebrew for tools that aren’t available via cargo/npm/pip, have complex C dependencies, or are macOS-specific (casks, GUI apps).

5. VS Code extensions

# Add to packages/vscode-extensions.txt
ms-python.python
charliermarsh.ruff

Re-run: bash ~/dotfiles/install/vscode.sh

To capture newly installed extensions back into the file (union — never removes):

bash ~/dotfiles/install/vscode.sh sync-extensions

Note: settings.json is not tracked — it contains an embedded GitLab token (cmake.configureEnvironment). Extensions only.

6. Custom install script

Look at an existing install/ script for patterns and follow them. Add a DF_DO_* flag to bootstrap.sh.


Local AI tools

Local LLM inference and coding agents are split across three layers:

ToolLayerNotes
ollamapackages/Brewfile (macOS only)Inference server; installed as Homebrew formula, managed as a LaunchAgent
opencodepackages/BrewfileTUI coding agent by the SST team
mlx-lmpackages/pip.txtApple Silicon Metal inference; on-demand only
aider-chatpackages/pip.txt (# python=3.12)CLI coding agent; pinned to Python 3.12 because scipy has no wheels for 3.14+
justpackages/cargo.txtCommand runner / Makefile alternative

install/local-llm.sh creates the PLAT-isolated HuggingFace cache directory ($LOCAL_PLAT/.cache/huggingface) and verifies that the expected binaries are present. install/opencode.sh creates context-boosted Ollama model aliases so the 4096-token default doesn’t starve the agentic tool-use loop.

See Local AI coding for usage details.


Don’t duplicate across layers

Do not install the same tool in both cargo.txt and Brewfile. PLAT paths (~/.local/$PLAT/) come first on PATH — the Homebrew copy would install but never be used. If a tool is in cargo.txt, it must not be in Brewfile, and vice versa.


Why cargo over Homebrew for Rust tools

Tools like fd, sd, bat, ripgrep, git-delta, difftastic, procs, bottom, ast-grep, zoxide, and hyperfine live in cargo.txt because:

  • $CARGO_HOME/bin/ is already under $LOCAL_PLAT/ — PLAT isolation is free
  • cargo-binstall downloads pre-built GitHub release binaries — fast, no compilation

Tools that have no pre-built binary and are painful to compile (or only make sense on macOS) go in Brewfile under if OS.mac?.


Why Homebrew for Linux

Homebrew on Linux installs natively on the host (no container, no sudo). It bundles its own glibc 2.35, making binaries fully self-contained regardless of the host’s glibc version.

Custom prefix tradeoff: Installing to ~/.local/$PLAT/brew/ instead of the standard /home/linuxbrew/.linuxbrew enables per-CPU isolation on shared NFS homes, but bottles built for the standard prefix can’t always be relocated:

  • Relocatable packages (jq, CLI tools with simple dependencies) pour as bottles — patchelf rewrites RPATH and they work fine
  • Deep path embedding (Python, Perl, git, vim, ffmpeg, imagemagick) build from source on first install. Homebrew uses all available CPU cores (auto-detects nproc), so builds are fast on modern hardware.

Once built, packages are cached. Subsequent runs and upgrades are bottle-only.

Compilers: gcc and llvm are keg-only (Homebrew doesn’t create unversioned gcc/clang symlinks to avoid shadowing system compilers). linux-packages.sh creates symlinks in $LOCAL_PLAT/bin/ so gcc → the highest installed GCC and clangllvm@21/bin/clang.

See Compiler toolchains below for CMake integration.

Python@3.14 patches: On Linux, install/patch-homebrew-python.sh automatically patches the python@3.14 formula to fix build issues (uuid module detection, test_datetime PGO hangs). Patches are applied during bootstrap and protected by HOMEBREW_NO_AUTO_UPDATE=1.

The same Brewfile works on macOS and Linux. if OS.mac? blocks are silently skipped on Linux.



Compiler toolchains

CMake compiler selection is handled by toolchain files deployed per-PLAT, not by raw CC/CXX env vars. install/cmake.sh copies them from install/cmake/toolchains/ to $LOCAL_PLAT/cmake/toolchains/ on every bootstrap run (always overwrites, so they stay in sync with the repo).

Default: LLVM (Homebrew clang)

When Homebrew LLVM is present, ~/.profile automatically sets:

export CMAKE_TOOLCHAIN_FILE="$_LOCAL_PLAT/cmake/toolchains/llvm.cmake"

The toolchain configures:

CMake variableValue
CMAKE_C_COMPILER$LOCAL_PLAT/brew/opt/llvm/bin/clang
CMAKE_CXX_COMPILER$LOCAL_PLAT/brew/opt/llvm/bin/clang++
CMAKE_AR / RANLIB / NMllvm-ar, llvm-ranlib, llvm-nm
CMAKE_LINKER_TYPELLD (Linux only; macOS requires Apple ld)
CMAKE_CUDA_HOST_COMPILERclang++

Switching to GCC 15

Per-invocation:

CMAKE_TOOLCHAIN_FILE="$_LOCAL_PLAT/cmake/toolchains/gcc.cmake" cmake -B build

Per-session:

export CMAKE_TOOLCHAIN_FILE="$_LOCAL_PLAT/cmake/toolchains/gcc.cmake"

Per-project (CMakePresets.json):

{ "cacheVariables": { "CMAKE_TOOLCHAIN_FILE": "/absolute/path/to/gcc.cmake" } }

The GCC toolchain uses versioned binaries (gcc-15, g++-15, etc.) because Homebrew does not create unversioned gcc symlinks on macOS. Linker priority: mold → lld → gold → system ld.

Disabling the toolchain

unset CMAKE_TOOLCHAIN_FILE   # let CMake auto-detect compilers

CUDA

CUDA is not managed by bootstrap — install the toolkit separately (system package, NVIDIA runfile, or a module system on HPC). Then point the per-PLAT symlink at it:

ln -sfn /usr/local/cuda "$_LOCAL_PLAT/.cuda"        # system default
ln -sfn /opt/nvidia/cuda/12.6 "$_LOCAL_PLAT/.cuda"  # versioned install

~/.profile resolves the symlink at login and exports:

  • CUDA_PATH and CUDAToolkit_ROOT — picked up by CMake’s find_package(CUDAToolkit) and most other build systems
  • Prepends $CUDA_PATH/bin to PATH so nvcc is on the path

Both toolchain files also set CMAKE_CUDA_COMPILER to $LOCAL_PLAT/.cuda/bin/nvcc when the symlink exists, so enable_language(CUDA) works without any project-level configuration.

Different machines on a shared NFS home can point their $LOCAL_PLAT/.cuda symlinks at different toolkit versions — no conflicts.

Switching toolchains at runtime

The tc shell function (defined in .zshrc) switches the active toolchain for the current session:

tc              # show active toolchain
tc list         # list available toolchain files
tc gcc-15       # switch to GCC 15 (sets CC/CXX/AR/RANLIB/NM + CMAKE_TOOLCHAIN_FILE)
tc gcc-13       # switch to GCC 13
tc llvm-22      # switch to LLVM 22 (clears CC/CXX; CMake file owns compiler selection)
tc llvm-21      # switch to LLVM 21

Compiler caching (ccache / sccache)

~/.profile configures ccache and sccache automatically when they’re installed:

SettingValueWhy
CCACHE_BASEDIRscratch root or $HOMERewrites absolute paths to relative before hashing — builds in different directories share cache hits
CCACHE_COMPILERCHECKcontentHash compiler by content, not mtime — survives brew reinstalls and module swaps
CCACHE_SLOPPINESSfile_stat_matches,time_macrosUse mtime+size for include checks; cache TUs with __DATE__/__TIME__
CCACHE_HARDLINK1Hardlink cached objects instead of copying — halves I/O on cache hits
CCACHE_MAXSIZE2% of partition, clamped [10G, 100G]Auto-sized to scratch partition
RUSTC_WRAPPERsccacheRust compiler caching
SCCACHE_CACHE_SIZE2% of partition, clamped [10G, 100G]Same auto-sizing as ccache

CMake integration: CMAKE_C_COMPILER_LAUNCHER=ccache and CMAKE_CXX_COMPILER_LAUNCHER=ccache are exported automatically.

openssh from Homebrew

The Brewfile installs openssh cross-platform (not just macOS) to avoid OpenSSL version mismatches between the system ssh and Homebrew-linked libraries. On Linux, the system ssh may link against a different OpenSSL than Homebrew’s, causing git push failures when Homebrew’s git shells out to ssh. Brew’s openssh uses Homebrew’s OpenSSL consistently.

Source files

Toolchain source files live in install/cmake/toolchains/ — edit them there, not in the deployed copies under $LOCAL_PLAT/. Re-deploy with:

bash ~/dotfiles/install/cmake.sh

Then wipe the CMake cache (rm -rf build/CMakeCache.txt build/CMakeFiles) for the changes to take effect in an existing build directory.


Updating all packages

~/dotfiles/bootstrap.sh update    # pull + refresh (install missing, skip current)
~/dotfiles/bootstrap.sh upgrade   # update + brew upgrade + cargo upgrade

update refreshes tools without upgrading existing versions. upgrade additionally enables Homebrew upgrades and forces cargo-binstall to re-check for newer binaries. Both are idempotent — safe to run at any time.