Harrison Totty

Templated User/System Configurations With mkdot

Mar 19 2019


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.