dot: My Dotfiles, and One Command for a New Mac
How a folder of config files turned into a small tool that links them into any Mac, never overwrites a file, and survives being moved.
The problem with dotfiles
Every program I use keeps its settings in a hidden file in my home folder: .zshrc for the shell, .gitconfig for git, .vimrc, .tmux.conf, Emacs's init.el, and so on. They are called dotfiles, after the dot that hides them. Years of small changes live in them, and a new machine without them feels like someone else's.
The usual answer is to keep them all in one git repository and put links in the home folder that point into it. Change a setting on one Mac, commit, pull on the other, and both agree.
I started mine in May 2026, and in October I moved it to GNU Stow, which makes those links for you. It worked, until I wanted three things Stow doesn't do:
- A second Mac. A MacBook joined the Mac mini, and not every config belongs on both. Stow has no idea which machine it's on.
- Moving the folder. Stow's links are relative to where the repository sits, so moving it into
~/Devmeant redoing every link by hand. - A new Mac from scratch. Linking configs is the easy part. Before that come Homebrew, the tools themselves, a GitHub sign-in and my Claude Code setup. I wanted one command that does it all and asks me only what it can't know.
So on November 10 I rebuilt it, with Claude Code doing the typing and me deciding.
Packages, laid out like home
The repository now holds one package per tool, each a folder laid out exactly like my home folder:
packages/
zsh/.zshrc
git/.gitconfig
tmux/.tmux.conf
emacs/.emacs.d/init.el
nvim/.config/nvim/init.lua
…
packages/zsh/.zshrc becomes ~/.zshrc. There are ten packages today: the shell, git, GnuPG, Vim, Emacs, Neovim, tmux, ssh, Claude Code's global instructions, and dot itself.
Each Mac keeps its own list of which packages are on, in machines/<name>.txt. The list is in the repository, so a reinstall gets the same set back.
dot
dot is the command that makes the links. With no arguments it opens a picker: space turns a package on or off, enter applies. Before it changes anything, it shows what it will link and unlink, and asks.
dot status shows the same thing as plain text:
$ dot status
dotfiles ~/Dev/dotfiles → ~
this machine: macbook · saved list: machines/macbook.txt
dot ● on
git ● on
gnupg ● on
claude ● on
zsh ● on
vim ● on
emacs ○ off
nvim ◐ partly on (saved: on)
ssh ● on
tmux ✘ blocked by a real file
~/.tmux.conf is a real file (or a link to something else)
➜ Next: a real file is in the way. Move it aside, then run dot on <package>.
The rest is short:
dot on <package>… link packages
dot off <package>… unlink packages
dot relink after moving the folder, or on a new machine
dot setup on a new Mac, or again any time
Three rules it never breaks
It never overwrites a file. If a real file sits where a link should go, that package is blocked. dot shows the file and leaves it alone. When I want the link anyway, it gives me the exact mv that moves the file aside under a dated name, and I run it myself. Nothing in my home folder disappears behind my back.
It links files, not folders. Each file gets its own absolute link, and the folders around it are made as real folders. When Stow found a folder missing, it linked the whole folder instead, so anything an app wrote there later would have landed in my repository. With dot, a cache or a history file an app drops next to my config stays on the machine.
It survives being moved. A link that has gone dead, or points into an old copy of the repository (including Stow's old layout), counts as broken. dot relink removes those and links every package that is on again, from wherever the folder now sits. The repository finds itself from the script's own path, so it doesn't care where it lives.
One command for a new Mac
On a new Mac the plan is a few steps by hand (install Homebrew and the GitHub command line, clone the repository) and then one command:
~/Dev/dotfiles/install.sh
install.sh gets the one tool the questions need (gum, the picker) and hands over to dot setup, which goes step by step:
- Required tools, from a
Brewfilewhere every line says why it's there: among them GnuPG and pinentry for signing commits, the GitHub command line, and the secret scanner that guards every commit. - Required configs, linked without asking: git, GnuPG, the shell, Vim, Claude Code, and
dotitself. - Optional ones, asked one at a time: Yes and link, Only install, or No, not this time. A "no" is asked again on the next run, and the answers are saved for that machine.
- Claude Code: it installs Claude Code, signs in to GitHub over HTTPS, and brings in the rulebook and skills my sessions work by.
If I say no to Homebrew, or stop halfway, nothing is left half linked, and the next dot setup simply asks again. On Linux, for now, it only links the configs.
Built like the other projects
dot is about 560 lines of bash, and plain bash on purpose: the 3.2 that macOS ships, so it runs on a Mac that has nothing installed yet. The only extras are gum for the picker and Homebrew for installs.
It was built the way I build everything now: Claude Code sessions, each step proposed on a review page and approved by me before it lands. A few rules came with it:
- No key, token or password ever goes into the repository. A secret scan runs before every commit and refuses one.
- A config file changes only after I've seen the diff. Claude may audit my configs and suggest changes, but each one waits for my OK.
- Tests run in a fake home folder, so testing
dotnever touches my real one.
What's next
dot setup already covers the tools, the configs and Claude Code. Still to come: offering my projects one by one, each with what it needs installed; a README that walks a new Mac up to that one command, word by word; an SSH key for each Mac, only for ssh between them; importing my GPG key from its backup; and local override files for settings that belong to one machine only. Linux distributions come later.
The repository is private, because it is my machine. The ideas aren't. If you keep dotfiles, three things are worth stealing: never overwrite a file, link files and not folders, and keep a list per machine.