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