Dotfiles managed by chezmoi, tools managed by mise. Works on macOS, Linux, and WSL.
-
Install fish manually (it's the one thing kept outside this toolchain):
# macOS (if you have brew): brew install fish # Debian/Ubuntu: sudo apt install fish # Fedora: sudo dnf install fish # Arch: sudo pacman -S fish # NixOS (configuration.nix): environment.systemPackages = [ pkgs.fish ];
-
Clone and bootstrap:
git clone https://github.com/MattKunze/dotfiles.git && cd dotfiles ./bootstrap.sh
Debian/Ubuntu note: also
sudo apt install libatomic1— the node binary needs it (preinstalled on macOS/Fedora/Arch equivalents).NixOS note: enable nix-ld in your configuration first —
programs.nix-ld.enable = true;. All mise tools install as precompiled glibc-linked binaries (the global mise config forcesall_compile = false, since building them from source needs a system toolchain NixOS doesn't have), and nix-ld is what lets those binaries run.This installs mise + chezmoi into
~/.local/bin, applies all dotfiles, installs polytoken (via a chezmoirun_oncescript) and every tool in the mise config, and creates~/.config/secrets/. It prompts for machine type (personal/work); for non-interactive runs (containers, CI) setCHEZMOI_MACHINE_TYPE=personal(orwork) instead. -
Finish by hand (one-time, per machine):
chsh -s "$(command -v fish)" # make fish your login shell gh auth login tg auth login <handle> # tangled.org CLI (OAuth flow, like gh) atuin login # or: atuin register opencode # OpenCode, first-run setup polytoken # first-run setup
Machine-specific keys/env go in
~/.config/secrets/*.fish(fish syntax, sourced at shell startup; see~/.config/secrets/README.md).
| Task | Command |
|---|---|
| Apply dotfile changes | chezmoi apply (abbr: cma) |
| Edit a managed file | chezmoi edit <path> then chezmoi apply |
| Pull + apply everywhere | chezmoi update (abbr: cmu) |
| See what would change | chezmoi diff (abbr: cmd) |
| Install/upgrade tools | mise install (abbr: mi) / mise upgrade |
| Everything at once | mise run update (abbr: mr update) — also updates polytoken |
| Fish abbreviations | all in dot_config/fish/conf.d/abbr.fish |
.chezmoi.toml.tmpl prompts for machine type (personal/work) on init
.chezmoidata.yaml shared defaults (identities, atuin sync servers)
dot_config/ files applied to ~/.config/
fish/ shell config; abbreviations in conf.d/abbr.fish
git/, jj/ identity templated per machine (personal vs work)
atuin/ sync address templated per machine
ghostty/, gh/ terminal + github cli (machine overrides via
secrets/ghostty.conf)
polytoken/ sparse: config.yaml + themes/warm-burnout.yaml only
mise/config.toml global tool list (installed by mise install)
mise/tasks/ mise tasks (mise run update, ...)
secrets/ README only — actual secret files are untracked
run_onchange_install-packages.sh.tmpl re-runs `mise install` on config change
run_onchange_sync-warm-burnout-remote.sh.tmpl repoints the theme checkout
when warm_burnout.url changes
run_once_install-polytoken.sh installs polytoken if missing
run_once_remove-opencode-v1-path.sh one-time PATH cleanup (old opencode)
bootstrap.sh one-shot onboarding script (not applied to $HOME)
Polytoken is not managed by mise — it isn't in
mise's registry, and it ships its own updater (release channels, launch-time
update checks), which would conflict with mise upgrade. Instead it is
installed by the official shell installer into ~/.local/bin alongside
mise/chezmoi:
- Fresh machines:
bootstrap.sh→chezmoi init --applyrunsrun_once_install-polytoken.sh. - Already-bootstrapped machines: the same script runs on the next
chezmoi apply(installs only if the binary is missing). - Updates:
mise run updaterunspolytoken updateand regenerates fish completions; polytoken also checks for updates on its own at launch.
Fish completions live in ~/.config/fish/completions/polytoken.fish and fish
loads them automatically.
The config dir ~/.config/polytoken is managed sparsely — only config.yaml
and the themes/warm-burnout.yaml symlink are in chezmoi; polytoken's own
churn (timestamped config.yaml.*.bak backups, prompt_history/) is
untracked and listed in .chezmoiignore so it never sneaks in. If polytoken
ever rewrites config.yaml (e.g. a config-version migration), chezmoi diff
will show it — review and chezmoi merge ~/.config/polytoken/config.yaml
instead of blindly applying, or you'd revert the migration.
HTTPie is not in mise's registry (the registry's
httpie-go is a separate Go reimplementation), and it's a Python package, so
it's installed with the mise-managed uv tool runner into ~/.local/bin
(http, https, httpie):
- Fresh/changed machines:
run_onchange_install-packages.sh.tmplrunsuv tool install httpie(oruv tool upgrade httpieif already present) aftermise install. - Updates:
mise run updaterunsuv tool upgrade httpie. - Manual:
uv tool install httpie/uv tool upgrade httpie.
mise registry | grep <name> # find its backend
mise use -g <name> # adds to ~/.config/mise/config.toml
mise installGo-based CLIs without a registry entry can be built from source with the go
backend — e.g. tg (tangled.org CLI, module github.com/alyraffauf/tg):
"go:github.com/alyraffauf/tg/cmd/tg" = "latest" # in dot_config/mise/config.tomlmise install compiles it with the mise-managed go toolchain, exposes the
binary via shims, and mise upgrade keeps it current.
Languages work the same way: mise installs the toolchain (go, rust,
node), and binaries you build land in ~/go/bin or ~/.cargo/bin
(both on PATH). Per-project tool versions: mise use <tool>@<version>
inside the project (replaces devenv/direnv).
./test/smoke.sh builds a bare debian:latest image containing only the
documented prerequisites (fish, git, curl, libatomic1), runs bootstrap.sh
inside it, and verifies the results: dotfiles applied, polytoken installed,
fish starts and loads 67+ abbreviations, mise tools installed, templated
config rendered.
The repo is volume-mounted and used directly as the chezmoi source
(DOTFILES_LOCAL_SOURCE), so uncommitted changes are tested. To test the
true onboarding path (clone from origin), comment out the
DOTFILES_LOCAL_SOURCE env in test/smoke.sh after pushing.
Machine type is forced to personal via CHEZMOI_MACHINE_TYPE (no TTY in
containers); set work to test work-machine rendering.
The warm-burnout theme (a fork of
felipefdl/warm-burnout, with a polytoken platform added) is pulled by chezmoi
as an external into ~/.local/share/warm-burnout — no submodules, and it
refreshes with chezmoi update (weekly; force with
chezmoi update --refresh-externals). The checkout is a cache, not a working
tree. Symlinks into it are managed like any other dotfile:
~/.config/opencode/themes/warm-burnout.json(theme file, via external)~/.config/opencode/cli.json(activates the theme + shared UI settings; auth lives in the untrackedservice.json)~/.config/ghostty/themes/warm-burnout-{dark,light}(ghostty config points at these viatheme = dark:warm-burnout-dark,light:warm-burnout-light)~/.config/polytoken/themes/warm-burnout.yaml(polytoken theme, referenced bytheme-filein~/.config/polytoken/config.yaml)
The external's url lives in .chezmoidata.yaml (warm_burnout.url) so it's
declared in one place: .chezmoiexternal.yaml.tmpl clones it on new
machines, and run_onchange_sync-warm-burnout-remote.sh re-runs whenever the
url changes and repoints the checkouts that already exist (chezmoi's git-repo
externals just git pull from whatever origin a checkout already has — they
never notice a url change on their own). Changing warm_burnout.url is
therefore all it takes to switch forks everywhere on the next chezmoi apply.
The upstream repo also ships themes for bat, eza, alacritty, and others — to
wire up another app, add a symlink_ file under the right dot_config/...
path pointing into ~/.local/share/warm-burnout/<app>/... (see existing
examples), then chezmoi apply.
Ghostty's per-machine settings (e.g. the font) are not templated at all: the
managed config ends with
config-file = ?~/.config/secrets/ghostty.conf (rendered absolute). Ghostty
processes included config last, so keys in that untracked file override the
managed defaults, and the ? keeps it optional — machines without the file
use the defaults (font: Maple Mono NF). This keeps chezmoi diff clean no
matter which shell renders the templates.
- New dotfile:
chezmoi add ~/.config/foo/bar.conf, thenchezmoi apply. - Machine-specific bits: wrap content in templates (
.tmpl) and branch on.is_work/.machine_type, or add data to.chezmoidata.yaml. - Longer term, secrets can move to chezmoi's age/1Password support; the
~/.config/secrets/*.fishmechanism won't need to change.