Keeping my dotfiles in sync with dotfather
TL;DR dotfather is a small dotfile manager. Your dotfiles live in a git repository, the original locations become symlinks into it, sensitive files are encrypted with age, and one command syncs everything. Install with
brew install volodymyrsmirnov/tap/dotfather.
I want my shell, editor and tool configs to be the same on every machine I use. The common answer is chezmoi. It can do a lot, and for my case a lot is more than I need.
Ideally, I want the following:
- Edit a dotfile in its usual place, and the change is already in the repo.
- Keep secrets like SSH keys in the same repo, but encrypted.
- One command to pull, commit and push.
- Set up a new machine with one command.
How it works
The repo lives in ~/.dotfather/ and mirrors the home directory. When you add a file, dotfather moves it into the repo and puts a symlink in its place, so ~/.bashrc points to ~/.dotfather/.bashrc. There is no manifest and no templates, the folder structure in the repo is the whole configuration. So at least there is no config file for the config files.
dotfather add ~/.bashrc ~/.config/nvim
dotfather add --encrypt ~/.ssh/id_rsa
dotfather sync
Encrypted files work differently. They are stored as .age files in the repo, and a normal decrypted copy stays in its place instead of a symlink. On sync, dotfather encrypts the files that changed locally before committing, and decrypts the ones that came from the remote after pulling. The age library is compiled into the binary, so nothing else needs to be installed. The public key is committed, so any machine can encrypt. The private key is in .gitignore and needs to be copied to a new machine by hand, the old-fashioned way.
sync itself pulls, commits with a generated message like Update .config/nvim/init.lua, and pushes. If two machines disagree, --interactive asks for every conflicting file whether to take the local version, the remote one, or open it in the editor and sort it out yourself.
A new machine looks like this:
brew install volodymyrsmirnov/tap/dotfather
dotfather init [email protected]:yourname/dotfiles.git
init clones the repo and creates all the symlinks. If a file already exists in that place, it’s saved as .dotfather-backup first, so nothing sleeps with the fishes. dotfather status then shows the state of every file, i.e. whether a symlink is broken, missing or in conflict.
The code is on GitHub.