Templated User/System Configurations With mkdot
I am pretty much constantly tweaking my personal system’s configuration. Ever since discovering the r/unixporn subreddit, I’ve searched for the “perfect” dotfile management and templating system to no avail, which ultimately led to writing my own. My inspiration ultimately came from an Apache Web Server configuration templating system I wrote for our internal servers at Wolfram Research called mkconf, which was in-turn inspired by the Jinja-based templating system employed by many “DevOps” utilities like Salt and Ansible. So, I ended-up writing mkdot: “The handy dotfile templating system”.
Introduction
mkdot
is a Python script that generates user/system configuration files (“dotfiles”) from Jinja2 templates. Like mkconf
, I designed it to work equally-well as both an interactive (“on-demand”) script and a more hands-off automation-ready script. It features useful colored output, optional file logging, dry-run support, existing dotfile backups, configuration via environment variables, and the ability to quickly revert changes. It requires Python 3, PyYAML, Jinja2, and rsync
. In the simplest example, it is invoked like so:
$ mkdot example/example.yaml example/templates
where example/example.yaml
is the “template configuration file” and example/templates
is a directory containing the templates to be translated.
Basic Example
Let’s look at a super simple example. Let’s say we have the following directory structure:
example/
example.yaml
templates/
i3/
config.template
where example/templates/i3/config.template
corresponds to a configuration file template for the i3 window manager. example/example.yaml
might look like the following:
# Example Template Configuration File
# -----------------------------------
templates:
# ----- Window Manager: i3 -----
- file: "i3/config"
template: "i3/config.template"
editor: "emacs"
In short, the templates
key contains a list of dictionaries specifying the files to translate relative to the supplied templates directory. The file
key specifies the name and path of the “output” file, relative to the supplied output path (~/.config
by default), while the template
key specifies the name and path of the “input” file, relative to the supplied templates directory (example/templates
in this case). All other key-value pairs are variables whose values may be referenced by the template file. To see what I mean, let’s look at a chunk of example/templates/i3/config.template
:
# ...
# Set the modifier to the "Windows" key
set $mod Mod4
# Launch the configured text editor
bindsym $mod+e exec {{ this.editor }}
# ...
As you can expect, the expression {{ this.editor }}
will be replaced with emacs
in the resulting configuration file. this
is a shorthand automatically added by mkdot
so that your templates don’t have to crawl through the templates
list themselves.
So one could imagine a situation where, perhaps I want to use emacs
at home but vim
at work. I could let both my home and work computers build from the same base templates, but separate template configuration files.
A More Complex Example
Let’s take the conclusion in the previous section a bit further by having only one repository for both our home and work computers, with a setup that looks like this:
dotfiles/
yaml/
home.yaml
work.yaml
templates/
i3/
config.template
herbstluftwm/
autostart.template
We could configure our home computer to use i3 as its window manager by specifying in home.yaml
:
# Home Configuration File
# -----------------------
templates:
# ----- Window Manager: i3 -----
- file: "i3/config"
template: "i3/config.template"
editor: "emacs"
… and configure our work computer to use herbstluftwm by specifying in work.yaml
:
# Work Configuration File
# -----------------------
templates:
# ----- Window Manager: herbstluftwm -----
- file: "herbstluftwm/autostart"
template: "herbstluftwm/autostart.template"
editor: "vim"
In the above situation, all we would need to do is run
$ mkdot dotfiles/yaml/home.yaml dotfiles/templates
on our home computer and
$ mkdot dotfiles/yaml/work.yaml dotfiles/templates
on our work computer. However, mkdot
actually makes things even easier. If the specified template configuration file is actually a directory instead of a YAML file, it will automatically select a file called mkdot.yaml
within that directory or a YAML file that most closely matches the hostname of the executing machine. This means that if the hostnames of our home and work computers contain the substrings “home” and “work”, we can actually run the following command from either machine to get the same effect:
$ mkdot dotfiles/yaml dotfiles/templates
Neat huh?
Conclusions
So hopefully I’ve convinced you that mkdot
is pretty much the coolest thing on the planet. I recommend interested parties to take a look at my dotfiles for how I currently use the script. You should also read the README and templating documentation for more information on the sorts of options you have when using the script.