Dotfiles management
Last week I wrote a final (and very NIH) solution to my dotfiles management problem, so here's a small post about it. What motivated me is hearing from a colleague who started using chezmoi and my general distaste for these dedicated kitchen sink solutions when a simple script is perfectly suited for such a task.
My ideal design (and what I initially tried, like a lot of people) would have been for git to allow following symlinks and for filters to also handle path rewriting. Sadly, this isn't the reality we live in and would have been only half of the solution (save/backup, not restore/deploy) anyway.
Another interesting solution I've recently read about is a bare git repo inside $HOME
configured to hide untracked files together with a git --git-dir=/path/to/repo --work-tree=$HOME
alias. Pretty elegant, but there's a
lot of stuff in /etc
I have read access to I also want to manage.
So here it is, at the root of my dotfiles repo: do (so-called because it'll usually be called as .../dotfiles/do sync|restore
).
It's a pretty large POSIX sh script with a section at the end reserved for the user to declare his backup strategy. I'll let the explanatory banner and my example speak for themselves:
################################################################################ # User syncing rules section # ################################################################################ # update update existing files in store dir $1 # # mirror destructively mirror store dir $1 # # generate save/restore file $1 ("!" basename prefix added) using cmd $2/$3 # # # # The "store" is the parent dir of this script, a store path is always # # written relatively to the store's root, the corresponding system path used # # as reference for syncing/restoring is that store path prefixed with "/". # # # # The -m option can be used to alter that mapping for all operations. For # # example, `mirror home` would use "/home" as system reference, but specifying # # `-m home /home/user` changes it to "/home/user". # ################################################################################ update etc/ mirror etc/conf.d/ mirror etc/portage/ update home/ mirror home/.wallpapers/ generate crontab 'crontab -l' 'crontab'
And a small session to show it off:
$ ~/.dotfiles/do sync -m home "$HOME" $ alias dfgit='git -C ~/.dotfiles' $ dfgit status ... $ dfgit add . && dfgit commit -m sync && dfgit push origin HEAD
Then on another machine, for example at work:
$ git -C ~/.dotfiles pull
$ DIFF=colordiff PAGER='less -F' ~/.dotfiles/do restore -m home "$HOME" home/.zshrc
Restoring 'home/.zshrc' would overwrite already existing file '/home/user/.zshrc'
overwrite (y), overwrite all (a), skip (n), quit (q) or show diff and ask again (d)?
--- /home/user/.zshrc 2025-01-04 18:42:18.427402971 +0100
+++ home/.zshrc 2025-01-04 18:36:58.070489857 +0100
@@ -35,6 +35,7 @@
alias rsync='rsync --info=progress2 --sparse --progress --human-readable'
alias rsync_vfat='rsync --no-owner --no-group --no-perms --modify-window=2'
alias rsync_exfat='rsync --no-owner --no-group --no-perms'
+alias startx='startx -- -nolisten tcp -nolisten local vt1'
alias tmux='tmux -u'
alias sbcl='sbcl --noinform'
alias ccl='ccl -b'
overwrite (y), overwrite all (a), skip (n), quit (q) or show diff and ask again (d)?
Except for generate
not yet having that cool git-style interactive menu, I don't
see what else I could ask from this script. Anything more complicated like filtering/preprocessing
can be handled by that generate
catch-all.
It only needs some better documentation in -h
, a more useful -r
(dry run) output and maybe a merge tool option for interactive restore.
I'd love to see other people write about their own way of doing it!