Skip to content

Custom Installers

custom_installers is not a category of tool. It is the name for no shared mechanism: every entry arrives by a route nothing else in the repo uses, so every entry is its own function in src/dotfiles/providers/custom.py. That module docstring lists the routes and what converging means for each. This page holds the decisions it does not.

Check that it really is custom before writing one

A tool with a GitHub release is a github_releases entry. It gets checksum verification, offline bundling and currency measurement for free, and a function here inherits none of those. Reach for this section only when the bytes genuinely come from somewhere else.

The version and the bytes come from different places

An entry declares repo: even where nothing is downloaded from GitHub. The repo is where the version lives, which stays a declarative fact even when the distribution is not. That is what lets dotfiles check say a custom installer is behind, rather than treating presence as the whole verdict.

Claude Code names no repo, because its install path consults none. Its version lives in the vendor's channel file, the one the install script reads, and custom.version_file points currency there. Presence was never enough for it either. A desk updates Claude Code in the background, and a machine that only runs claude -p falls behind.

Every host an installer reaches is declared by the installer itself

custom.sources() answers where a given tool is fetched from. network._custom_installer_probes is its only caller, so the connectivity probe asks each installer rather than switching on a declared kind. The probe results carry the answer per tool:

dotfiles network check --json | jq -r '.probes[] | select(.name == "theme") | .target'

git is compiled where the OS ships one too old

git runs a hook declared in git config only from 2.54, and Debian's stable releases ship an older one. git publishes tags and source and no binaries, so the git entry clones the newest release tag and runs make install into ~/.local. PATH searches ~/.local/bin before /usr/bin, which is what makes the build the git every command finds. The OS package stays installed beside it, because it is the git that clones this repo, and the source, before anything can be built.

The build leaves out Rust, which git 3.0 makes mandatory. From then the build needs cargo, which a machine gets by declaring a cargo_packages entry.

Rejected: pinning Debian unstable's git. Its package needs a newer glibc than stable has, so the pin drags in unstable's C library with it.

Rejected: Homebrew on Linux, and conda-forge through pixi. Both carry a current git prebuilt. Each is a second package manager on the machine, and each installs to a prefix nothing here puts on PATH.

What is deliberately absent

Rejected: a --print-url protocol. Having the offline bundler ask each script over a pipe which file to fetch puts the staged filename in two places. The declaration's install_url is the one place. Two answers can drift apart, and the bundle then holds a file the install never looks for.

Rejected: measuring Claude Code against anthropics/claude-code releases. The install script never reads them. A comparison there would track a second publisher's schedule rather than the channel file the machine installs from.

Rejected: a shared "run a vendor script" abstraction beyond staging it. Several functions run one, and all they share is where the script comes from — the offline bundle if it holds one, the network otherwise. What each does with the result differs enough that a common wrapper would be branches.

Rejected: a structured failure-log protocol. Scripts emitting FAILURE_MANUAL blocks for a wrapper to collect puts the log format in as many places as there are installers. Each function returns a Result and the stage writes the log.