This "package" allows you to specify a complete dev env setup, including packages to install (homebrew, yay, pacman, winget, scoop, npm, etc etc).
You write a bootstrap_config.json, then run a python CLI tool.
I use this to keep multiple machines (work mac, home Arch, Docker based arch dev boxes, Windows boxes) all up to date with my dev environment.
There are probably many. However, the well known ones; Puppet, Chef, Ansible, etc, are very heavyweight for my needs.
There are a lot of "dotfiles" repos on github; this depends on that --- it requires you to have a ~/dotfiles/ repo that can be symlinked against, but it goes a lot further (than dotfiles).
- python3 (3.10+ required; I am using 3.11) and
uv - if on mac,
homebrew; if on Arch,yay; if on Windows,winget(ships with Windows 11) and/orscoop - If on mac, XCode command line tools (can't even build
gccfromhomebrewwithout this):sudo xcode-select --install - If on Windows, enable Developer Mode (Settings → System → For developers → Developer Mode). This lets
os.symlinkwork without admin. - make a directory called
dotfiles/in your home directory with all of your dotfiles. I personally have a private githubdotfilesrepo that I clone there. - Write
~/dotfiles/bootstrap_config.json; see the next section
To use this package, you write a bootstrap_config.json in your ~/dotfiles/ directory to describe what you would like linked, installed, ran, etc.
That is the only input, other than a few cmd line parameters.
A sample config is included in this repo; see sample_config.json.
My personal bootstrap_config.json lives in my private dotfiles repo, which I clone into ~/dotfiles/ (see Prerequisites).
Please see the (evolving) jsonschema for the config in bootstrap/schema.py for the full schema.
These are the major sections:
comments: this is a list of strings for your own sanity, since JSON has no native commenting mechanism.initial_mkdirs: directories to be recursively made, grouped by OS (allplusmac/arch/ubuntu/windows). For example,~/.ssh/... Per entry you can setdelfirstto try deleting the directory first (helpful for "rebootstrapping"), andsudoto make it with sudo (for system directories outside~).links: softlinked dotfiles from your~/dotfilesdirectory, grouped by OS — anallkey for cross-platform links plusmac/arch/ubuntu/windowsfor OS-specific ones. Each entry hassrc,dst, and an optionalsudo(for system paths outside~). To vary a link by location (e.g. a different.gitconfigon work vs private machines), put it in the per-location config files described under "Environment-Specific Configs" below.commands: a list of arbitrary commands to run before packages are installed (use it for things package install depends on, like installingscoop). Can be specified as os-agnostic (all) or by OS type. Warning, whatever you put here will be executed! Each entry is either a plain string, or an object withcmd(the command to run), an optionalcheck(a shell command; if it exits 0,cmdis skipped, making the step idempotent), and an optionallabel(human-readable name for logs). If a command needs sudo, it will prompt for your password on the terminal, so privileged commands are fine.packages: a list of packages to install, which can be specified as os-agnostic, or by OS type. You can also include "agnostic" installs in the os-specific sections, for example, "I only want this NPM package installed on my mac".
Supported package managers:
brew/brew_tap/brew_cask: macOS Homebrew packages, taps, and caskspacman/yay: Arch Linux packagesppa/apt/snap: Ubuntu PPAs, apt packages, and snap packageswinget/scoop/scoop_bucket: Windows packages via winget and scoop (with bucket support)npm: Node.js packages (cross-platform; runssudo npmon POSIX, plainnpmon Windows)pip: Python packages —python3 -m pip install --useron POSIX (PEP 668 / system-Python protections),python -m pip installon Windows (winget Python is per-user; its Scripts dir is on PATH whereas the--userScripts dir is not)go_install: Go packages viago install(cross-platform)cargo: Rust packages viacargo install(cross-platform)fisher: Fish shell plugins (cross-platform)
Note: For Ubuntu, ppa entries are automatically processed before apt regardless of order in the config. Similarly on Windows, scoop_bucket is processed before scoop so buckets are available when packages install.
prereq_packages: packages that provide language toolchains (rust/cargo, go, uv) needed before other packages can be installed. These are installed beforepackages. Structure is the same aspackagesbut keyed by OS only (noallsection).file_associations(Windows-only): per-user Windows file extension → app launcher. Each entry has requiredext(.yaml),progid(Neovim.YAML), andcmd(wt -- nvim "%1"), plus optionalname(friendly app name shown in the "Open with" picker),icon(Explorer icon — a.icopath or<path>,<index>to pull a PE resource), andlabel(human-readable name for logs). WritesHKCU\Software\Classesso no admin is needed; it also clearsUserChoice(only when it points at a different app) so Explorer double-click respects your mapping. Runs afterpackagesso the target apps exist.post_commands: same shape ascommands, but runs after packages andfile_associations. Use this for steps that depend on packages already being installed — e.g. registering a font file that scoop installed but didn't put in the user font registry.
You can create separate config files for work vs private environments:
~/dotfiles/bootstrap_config.json- main config (always loaded)~/dotfiles/bootstrap_config_work.json- loaded when--loctype work~/dotfiles/bootstrap_config_private.json- loaded when--loctype private
The extra config files are optional and use the same schema. They're processed after the main config, so you can put environment-specific packages (like kubernetes tools for work) in separate files.
uv sync
uv run runboot --loctype work # work machines
uv run runboot --loctype private # personal machines
The systype (mac, arch, ubuntu, windows) is detected from sys.platform and /etc/os-release. The script is idempotent — safe to run repeatedly. Add packages to bootstrap_config.json and re-run.
- User supplied functions