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
| Layer | File | Install script | Platform |
|---|---|---|---|
| System packages | packages/Brewfile | install/homebrew.sh / install/linux-packages.sh | macOS (bottles) / Linux (native, no container) |
| Rust tools | packages/cargo.txt | install/rust.sh | All |
| Python packages | packages/pip.txt | install/python.sh | All |
| Global npm | packages/npm.txt | install/node.sh | All |
| Claude plugins | packages/claude-plugins.txt | install/claude.sh | All |
| Claude MCP servers | packages/claude-mcp.txt | install/claude.sh | All |
| VS Code extensions | packages/vscode-extensions.txt | install/vscode.sh | All |
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.provenanceon 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-lmrequires Apple Metal/MLX framework)# python=X.Y— pins to a specific Python version for that tool (e.g.aider-chatneeds 3.12 becausescipyhas 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.jsonis 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:
| Tool | Layer | Notes |
|---|---|---|
ollama | packages/Brewfile (macOS only) | Inference server; installed as Homebrew formula, managed as a LaunchAgent |
opencode | packages/Brewfile | TUI coding agent by the SST team |
mlx-lm | packages/pip.txt | Apple Silicon Metal inference; on-demand only |
aider-chat | packages/pip.txt (# python=3.12) | CLI coding agent; pinned to Python 3.12 because scipy has no wheels for 3.14+ |
just | packages/cargo.txt | Command 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 freecargo-binstalldownloads 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 clang → llvm@21/bin/clang.
See Compiler toolchains below for CMake integration.
[email protected] patches: On Linux, install/patch-homebrew-python.sh automatically patches
the [email protected] 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 variable | Value |
|---|---|
CMAKE_C_COMPILER | $LOCAL_PLAT/brew/opt/llvm/bin/clang |
CMAKE_CXX_COMPILER | $LOCAL_PLAT/brew/opt/llvm/bin/clang++ |
CMAKE_AR / RANLIB / NM | llvm-ar, llvm-ranlib, llvm-nm |
CMAKE_LINKER_TYPE | LLD (Linux only; macOS requires Apple ld) |
CMAKE_CUDA_HOST_COMPILER | clang++ |
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_PATHandCUDAToolkit_ROOT— picked up by CMake’sfind_package(CUDAToolkit)and most other build systems- Prepends
$CUDA_PATH/bintoPATHsonvccis 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:
| Setting | Value | Why |
|---|---|---|
CCACHE_BASEDIR | scratch root or $HOME | Rewrites absolute paths to relative before hashing — builds in different directories share cache hits |
CCACHE_COMPILERCHECK | content | Hash compiler by content, not mtime — survives brew reinstalls and module swaps |
CCACHE_SLOPPINESS | file_stat_matches,time_macros | Use mtime+size for include checks; cache TUs with __DATE__/__TIME__ |
CCACHE_HARDLINK | 1 | Hardlink cached objects instead of copying — halves I/O on cache hits |
CCACHE_MAXSIZE | 2% of partition, clamped [10G, 100G] | Auto-sized to scratch partition |
RUSTC_WRAPPER | sccache | Rust compiler caching |
SCCACHE_CACHE_SIZE | 2% 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.