This repository contains my personal dotfiles configuration for macOS, featuring thoroughly tailored setups for NeoVim and Zsh with performance in mind.
$ curl -fsSL https://dotfiles.creasty.com/apply | bashIt installs Homebrew, clones this repository to ~/dotfiles, installs Nix, and applies the nix-darwin configuration of flake.nix for the current user, home-manager included.
To apply changes later, run it again:
$ ~/dotfiles/applyIt skips what's already installed, and pulls master first unless the checkout is on another branch or has changes of its own.
Then it switches to the configuration and verifies the result.
Flakes only see files tracked by git, so git add a new file first.
./apply ends by verifying its result with behavioral tests (nix/tests/): shells are started the way terminals start them, and have to find the right runtimes, configs and commands.
To verify a machine again:
$ ./verify
$ ./verify --filter java # options go to bats
$ ./verify --bench # only the benchmarksThe tests compare the machine with what the activated configuration lists in /etc/dotfiles/manifest.json.
Benchmarks, the tests tagged bench such as the shell's startup time, check a budget that a busy machine misses: ./apply and the other runs skip them.
Set DOTFILES_NOVERIFY=1 to apply without verifying.
Versions are pinned, and Renovate bumps them on Friday mornings, in a pull request per kind:
| Pinned in | What | Tested by |
|---|---|---|
nvim/lazy-lock.json |
Neovim plugins, at the commits lazy.nvim installs | Neovim's workflow tests |
flake.lock |
nixpkgs, nix-darwin and home-manager | The apply workflow |
config/mise/config.toml |
Runtimes and tools, which mise installs | The apply workflow |
home/agents/.skill-lock.json |
Agent skills, at commits of their repositories | The apply workflow |
nix/modules/shell.nix |
zsh plugins, at their releases' commits | The apply workflow |
.github/workflows/ |
GitHub Actions, by commit SHA | The workflows themselves |
A Neovim plugin or an agent skill whose repository tags a release of each change within weeks is pinned at a release's commit, the others at a commit of the branch they follow.
Patch releases, minor ones from 1.0 on and flake.lock's refresh merge themselves once CI passes.
After merging, run ./apply again, and run :Lazy restore in Neovim to check out the new plugin commits.
flake.nix configures the system with nix-darwin and the home directory with home-manager, from a module per topic in nix/modules/: each has everything about a tool, for both.
bin/: Commands, onPATHconfig/: Configs, linked into~/.configetc/: Files for machines other than this Mac, such as the setup script of Claude Code's cloud environmentshammerspoon/: Key bindings, on Hammerspoontests/: Specs, on a fake of Hammerspoon's API
home/: Dotfiles, linked into~with a leading dotnix/modules/: The configuration, a module per topictests/: Behavioral tests of the applied configuration, which./verifyruns
nvim/: Neovimtests/: End-to-end workflow tests
shell/: Bash and Zshvscode/: VS Code's settings and key bindings
Files are linked into the home directory by where they are in the repository, with no list to maintain:
| Repository | Home directory |
|---|---|
home/<path> |
~/.<path> (home/aws/config → ~/.aws/config) |
config/<path> |
~/.config/<path> (config/git/config → ~/.config/git/config) |
The modules link the rest: nvim/, hammerspoon/, the shell files and VS Code's settings.
Links point into the checkout, so edits apply without a rebuild.
Command-line tools come from nixpkgs, pinned by flake.lock, ahead of Homebrew's on PATH.
Homebrew installs the apps (casks), and the formulae that nixpkgs lacks or that need a stable path, such as the JDKs and the libraries Ruby is built against.
mise installs the runtimes of config/mise/config.toml on every switch, and takes Homebrew's JDKs for Java.
Homebrew Bundle installs the App Store apps of nix/modules/appstore.nix with mas, which asks for your password (sudo) to install them.
mas can't sign in to the App Store, so sign in before running ./apply.
mas list shows the IDs of installed apps, and mas search <name> those of others.
mas can't install iPhone and iPad apps, such as Kindle's, so get those from the App Store app.
Cloud sessions run on a VM without this Mac's ~/.claude.
A cloud environment set up as follows gives them the same CLAUDE.md and settings.json.
Its setup script runs etc/claude-cloud-setup from master, which writes them into the VM's ~/.claude:
#!/bin/bash
set -o pipefail
curl -fsSL https://dotfiles.creasty.com/etc/claude-cloud-setup | bashdotfiles.creasty.com redirects to raw.githubusercontent.com, but the VM connects to it first, and the default network access, Trusted, doesn't allow it.
So set the network access to Custom, with dotfiles.creasty.com as an allowed domain and the default list included.
A failed download fails the setup, so a session doesn't start without them.
A session has the files as master had them when its setup ran: a VM paused while idle keeps them, and one rebuilt after being reclaimed runs the setup again.
Its environment variables make you the author of the session's commits:
GIT_AUTHOR_NAME=Yuki Iwanaga
GIT_AUTHOR_EMAIL=yuki@creasty.com
Leave the committer to the session, which signs its commits as Claude: GitHub checks a signature against the committer's keys, so a commit with GIT_COMMITTER_* set shows as unverified.
claude.ai's personal preferences hold the rest:
Language:
- Reply in the language the request was written in. An English request gets an English reply.
- For code, comments, commit messages and PR bodies, follow the language the repository already uses.
When the output is Japanese:
- 句読点以外の数字・記号は半角文字を使うこと。ただし入力に全角が含まれており、エコーバックする場合のみ全角を使って良いとする。
Pull Requests:
- Ignore events of pending comments. Wait until the review is submitted before acting on them.
Yuki Iwanaga / @creasty
