Managing Dotfiles with a Makefile

Assumed Knowledge: make, GPG, git.

You can manage dotfiles with nothing but make and standard tools.

  • Copy standard configs.
  • Track changes with git.
  • Keep separate branches for each computer.
  • Securely handle secret files with GPG.
  • Manage crontab.

The Setup

Mine looks like this:

 1config/:
 2 aerc         cava       khard         rofi      tut            picom.conf
 3 alacritty    cmus       lf            s         vdirsyncer     powerbash.sh
 4 amfora       dunst      ncmpcpp       sc-im     waybar         powerbashrc
 5 bat          gtk-3.0    newsraft      sway      zathura        redshift.conf
 6 bottom       himalaya   procps        systemd   compton.conf
 7 bugwarrior   i3         profanity     task      mpd.conf
 8 calcurse     i3blocks   qutebrowser   tspreed   mpv.conf
 9
10home/:
11 gnupg   mozilla   vim   bash_profile   gitconfig          inputrc     xinitrc
12local   unison    w3m   bashrc         gitignore_global   tmux.conf   Xresources

Standard configs are made with hard links, which means these two files are the same file:

  • dotfiles/home/bashrc
  • .bashrc

Once you edit ~/.bashrc, that also changes dotfiles/home/bashrc. A make pattern takes care of the path, so there's no need for dots in your dotfile manager.

1$HOME/.%: home/%
2    ln -f $< $@

Command-Based Files

Some files are usually created through commands, like ~/.gitconfig. These can change on different computers, as you add git lfs on one, or a credential helper on another. So if you don't want to keep them in sync, you can just put the universal commands into a Makefile. If your name ever changes, then all computers receive that change, but nothing will touch the strange git helpers installed on that old laptop.

1$(HOME)/.gitconfig: git.mk
2    git config --global user.name "$yerName"
3    ...

The same approach lets you manage secrets. For example, you can set the secret token value in glab (the Gitlab CLI tool):

1git_token != pass gitlab-token
2glab config set token "$(git_token)" --host gitlab.com

Secrets Management

You can use pass to manage secrets with make. For example, to keep ~/.config/aerc/accounts.conf as a secret file, just throw it into pass:

1cat ~/.config/aerc/accounts.conf | pass insert --echo config/aerc/accounts.conf

Add this file as a secret, and make a rule to create these secrets from all the files in ~/.password-store/:

1secrets += ${HOME}/.config/aerc/accounts.conf
2
3$(secrets): ${HOME}/.% : ${HOME}/.password-store/%.gpg
4    mkdir -p $(@D)
5    chmod 700 $(@D)
6	gpg --quiet --decrypt $< > $@ 
7	chmod 600 $@

Handling Crontab

Changes in different computers' crontab files can inflict a lot of merges. Avoid this by just making each set of tasks its own file:

1echo '@daily import_events.sh "https://dmz.rs/events.ical"' > extra/cron/calendar
2echo '@hourly mbsync -a' > extra/cron/email

Combine the lot with cat, then import this into cron:

1cron.txt: $(wildcard extra/cron/*)
2	cat $^ > $@
3    crontab $@

Example Repo

You can find my basic setup here:

1ssh -p 2222 soft.dmz.rs -t mkdots

Or just clone the repo and see how it works:

1git clone ssh://soft.dmz.rs:2222/mkdots .dots
2cd .dots
3make -n