Package Management Architecture¶
Purpose: Unified strategy for installing and managing CLI tools across all platforms
Philosophy¶
Priority: Latest versions and cross-platform consistency over system package manager convenience
Rationale: Ubuntu LTS (and other system package managers) ship conservative versions that are often 6-12 months (or more) behind upstream. This causes:
- Missing features and bug fixes
- Plugin compatibility issues (especially Neovim)
- Naming conflicts (bat/batcat, fd/fdfind)
- Platform-specific workarounds
By using universal installation methods (cargo-binstall, GitHub releases), we get:
- ✅ Same versions on macOS and Linux
- ✅ Latest features and fixes
- ✅ Consistent binary names
- ✅ User-space installation (no sudo needed)
Three-Tier Strategy¶
Tier 1: GitHub Releases (Latest Stable)¶
When to use: Core tools requiring specific versions, not available in cargo/language ecosystems
Installation target: ~/.local/bin or ~/.local/{tool-name}/
Method: Download pre-built binaries from GitHub releases
Tools:
yq- YAML processor (single binary)go- Build toolchain (extract to/usr/local/goper official docs)fzf- Fuzzy finder (pre-built binary)neovim- Editor (extract to~/.local/nvim-linux-x86_64/, symlink binary)lazygit- Git TUI (single binary)yazi- File manager (single binary + plugins)glow- Markdown renderer (single binary)duf- Disk usage utility (single binary)awscli- AWS command line tool (platform-specific installer)
Advantages:
- Latest stable releases
- No compilation required
- Universal across platforms
- Predictable versions
Tier 2: cargo-binstall (Rust Ecosystem)¶
When to use: Rust CLI tools where we want latest versions
Installation target: ~/.cargo/bin
Method: Download pre-compiled Rust binaries (much faster than cargo install)
Tools:
bat- cat alternative (no "batcat" naming issue!)fd- find alternative (no "fdfind" naming issue!)zoxide- cd alternativeeza- ls alternativegit-delta- Git diff vieweroxker- Docker container TUI
Advantages:
- Pre-compiled binaries (fast, 10-30 seconds)
- Latest versions from crates.io
- No naming conflicts
- Consistent across platforms
vs cargo install:
cargo installcompiles from source (5-10 minutes per tool)cargo-binstalldownloads pre-built binaries (10-30 seconds)- Same result, 20x faster!
Tier 3: System Package Managers (Stable)¶
When to use: System utilities where version doesn't matter, or tools with large system dependencies
Installation target: /usr/bin (lowest PATH priority)
Method: apt (Ubuntu), brew (macOS), pacman (Arch)
Tools:
Shell & Terminal:
zsh- Shell itselftmux- Version 3.4 is acceptable (3.5a is only bugfixes)
System utilities:
ripgrep- Currently up-to-date in apt (14.1.0)tree,htop,jq,yq- Stable tools, version doesn't matter
Build tools:
build-essential,curl,wget,unzippkg-config,libssl-dev,ca-certificates
Multimedia (large dependencies):
ffmpeg- Video/audio processingimagemagick- Image manipulationpoppler-utils- PDF toolschafa- Image preview7zip- Archive extraction
Advantages:
- Fast installation (pre-compiled, cached)
- System integration (man pages, completions)
- Security updates via
apt upgrade - Shared dependencies
Disadvantages:
- Outdated versions (6-12+ months behind)
- Naming conflicts on Ubuntu (batcat, fdfind)
Shell Plugins (Git Clone)¶
When to use: ZSH plugins that need to be sourced directly
Installation target: ~/.config/zsh/plugins/
Method: Git clone from upstream repositories
Plugins (defined in management/packages.yml):
git-open- Open repo in browser from terminalzsh-vi-mode- Better vi-mode for ZSHforgit- Interactive git commands with fzfzsh-syntax-highlighting- Fish-like syntax highlighting for ZSH
Advantages:
- Latest versions from upstream
- Easy to update with
git pull - Consistent across all platforms
- No package manager dependencies
Management:
- Install:
task shell:install(reads from packages.yml) - Update:
task shell:updateortask update-all
Installation Location Strategy¶
PATH Priority (highest to lowest):
~/.cargo/bin/ # Tier 2: Rust tools (bat, fd, eza, zoxide, delta)
~/.local/bin/ # Tier 1: GitHub releases (nvim, lazygit, fzf, yq, yazi)
~/go/bin/ # Go-installed binaries (sess, toolbox)
/usr/local/go/bin/ # Go toolchain
~/.local/share/npm/bin # npm global packages
/usr/local/bin/ # Homebrew/system-wide installs
/usr/bin/ # System packages (lowest priority)
Why this order?
- User tools override system - Your latest tools take precedence
- Language ecosystems together - Each package manager in its own directory
- System packages last - Stable but outdated, lowest priority
See PATH Ordering Strategy for complete details.
Special Case: Neovim Directory Structure¶
Why neovim can't be a single binary like lazygit:
Neovim is not a self-contained binary - it's an application bundle with many support files:
~/.local/nvim-linux-x86_64/
├── bin/
│ └── nvim # The executable
├── lib/
│ └── nvim/ # Shared libraries
└── share/
├── nvim/
│ └── runtime/ # CRITICAL: syntax files, plugins, help docs
├── man/ # Man pages
└── locale/ # Translations
The Problem: The nvim binary expects runtime files at ../share/nvim/runtime/ (relative to the binary location).
What happens if we move just the binary:
# DON'T DO THIS:
mv nvim-linux-x86_64/bin/nvim ~/.local/bin/nvim
# Neovim will look for runtime at:
~/.local/share/nvim/runtime/ # Wrong location!
# Actual location:
~/.local/nvim-linux-x86_64/share/nvim/runtime/ # Correct location
# Result: Neovim fails with "runtime files not found"
The Solution: Keep directory structure intact, symlink the binary:
# Extract full structure (neovim changed filename from nvim-linux64 to nvim-linux-x86_64)
tar -C ~/.local -xzf nvim-linux-x86_64.tar.gz
# Creates: ~/.local/nvim-linux-x86_64/
# Symlink binary into PATH
ln -sf ~/.local/nvim-linux-x86_64/bin/nvim ~/.local/bin/nvim
# Now:
# - Binary is in PATH (via ~/.local/bin/nvim)
# - Binary finds runtime (../share/nvim/runtime/ from real location)
# - Everything works perfectly!
Compare to lazygit (single binary):
# lazygit is self-contained:
tar -xzf lazygit.tar.gz lazygit
mv lazygit ~/.local/bin/lazygit # Direct move works!
# Everything it needs is compiled into the single binary
Summary:
- Single binary tools (lazygit, yq, fzf) → Direct to
~/.local/bin/ - Application bundles (neovim) → Extract to
~/.local/{tool-name}/, symlink binary
Version Comparison¶
See Package Version Analysis for detailed version comparisons.
Highlights:
| Tool | Ubuntu 24.04 apt | Latest | Installation Method |
|---|---|---|---|
| fzf | 0.44.1 | 0.66.1 | GitHub releases (22 versions ahead!) |
| neovim | 0.9.5 | 0.11+ | GitHub releases (major version ahead) |
| go | 1.22 | 1.23+ | GitHub releases (official method) |
| bat | 0.24.0 | 0.26.0 | cargo-binstall |
| fd | 9.0.0 | 10.2.0 | cargo-binstall |
| zoxide | 0.8.x | 0.9.6 | cargo-binstall |
| tmux | 3.4 | 3.5a | apt (acceptable, only bugfixes) |
| ripgrep | 14.1.0 | 14.1.0 | apt (current!) |
Implementation¶
Single Source of Truth: packages.yml¶
All package versions, repositories, and configurations are centralized in management/packages.yml. This repo previously maintained both a Brewfile and packages.yml, which guaranteed drift — the migration found ~70 duplicate packages and tools that existed in one list but not the other. Lesson: if two lists describe the same things, one of them is wrong. See GitHub Releases vs System Packages for the decision framework on choosing installation methods.
Package definitions in packages.yml:
runtimes:
go:
min_version: "1.23"
node:
version: "24.11.0"
python:
min_version: "3.12"
github_binaries:
- name: neovim
repo: neovim/neovim
version: "0.11.0"
- name: lazygit
repo: jesseduffield/lazygit
version: "0.44.1"
# ... more tools
cargo_packages:
- bat
- fd-find
- eza
- zoxide
- git-delta
- oxker
uv_tools:
- name: ruff
package: ruff
# ... more tools
All installation scripts read from this single source. Change a version once, and it applies everywhere.
Installation Scripts¶
Located in management/common/install/:
Directory Structure:
github-releases/- Tools installed from GitHub releases (neovim, lazygit, yazi, fzf, etc.)language-managers/- Language runtime installers (go, rust, nvm, uv)language-tools/- Language-specific tools (go-tools, npm-globals, cargo-tools)custom-installers/- Special installers (theme, font, awscli, claude-code)plugins/- Plugin installers (tmux, yazi)fonts/- Font installers
Core Library (management/common/lib/):
failure-logging.sh- Structured failure reportinggithub-release-installer.sh- Shared functions for GitHub release tools
All installer scripts support --update for the update system and use structured error reporting.
Installation Organization¶
Installation scripts are organized by platform under management/:
management/
├── common/ # Shared installation scripts
│ ├── install/ # Tool installers (neovim, lazygit, yazi, etc.)
│ └── lib/ # Shared libraries (github-release-installer.sh)
├── macos/ # macOS-specific installation
│ ├── install/ # macOS installers
│ └── setup/ # macOS system configuration
├── wsl/ # WSL installation scripts
│ └── install/ # WSL-specific installers
└── arch/ # Arch Linux installation scripts
└── install/ # Arch-specific installers
The root install.sh orchestrates the installation process, detecting the platform and running appropriate scripts.
Main Installation Flow¶
install.sh orchestrates installation phases:
- System packages (brew/apt/pacman)
- GitHub release tools
- Rust/cargo tools
- Language package managers
- Shell configuration
- Custom Go applications
- Symlink dotfiles
- Theme system
- Plugin installation
Taskfile Tasks¶
The Taskfile.yml provides convenience tasks for common operations but delegates complex logic to shell scripts:
See Task Reference for all available tasks.
Maintenance¶
Updating tools:
# Rust tools (updates via cargo binstall in update.sh)
cargo binstall -y <package>
# Manually check GitHub releases
task wsl:install-go # Updates if new version available
task wsl:install-neovim # Updates if new version available
task wsl:install-lazygit # Updates if new version available
# System packages
sudo apt update && sudo apt upgrade
Version checking: Each install script checks current version before installing, skipping if acceptable version already present.
Related Documents¶
- PATH Ordering Strategy - How tool resolution works
- Package Version Analysis - Detailed version comparisons
- App Installation Patterns - Go apps vs shell scripts
- Idempotent Installation Patterns - Re-runnable scripts