Dotfile-Playbook

Motivation

Dotfile management is hard. Most of us are doing it with complicated series of batch scripts, and some of the smarter among us are using ansible to do the job. Ansible is, however, quite heavy weight for this task, and not really well suited to the simple set-and-forget nature of dotfile directories, however well it does work.

dotfile-playbook should DWIM, and should be idempotent (i.e., it should be safe to run as many times as the user wishes without causing issues).

This blog post serves as a rough outline for the project, and may be edited as time goes on.

Layout

.
|-- data
|   |-- bar
|   |-- baz
|   `-- foo
|-- files
|   |-- Xmodmap
|   |-- bashrc
|   |-- config
|   |   |-- fish
|   |   |   `-- config.fish
|   |   |-- i3
|   |   |   `-- config
|   |   `-- riot-web
|   |       `-- config.json
|   |-- data
|   |   `-- exampleapp
|   |       `-- example.db
|   |-- emacs.el
|   `-- my-x-profile
|-- packages.yml
`-- recipies.yml

Phases

Package installation

dotfile-playbook should first check packages.yml and attempt to install any packages that don’t already exist in some sort of state saving directory.

Dotfile symlinking

dotfile-playbook should, mostly automatically based on the structure of the repository, generate symlinks for the user. It should automatically create any parent directories that don’t already exist.

It should try to respect the XDG Base Directory environment variables, but if they aren’t set, it should default to the ’normal’ locations (e.g. ~/.config/ for $XDG_CONFIG_HOME)

It should generally not create symlinks to directories, instead creating the directory and symlinking to each file in it individually.

DWIM linking

  • XDG dir linking

    dotfile-playbook should first iterate through each of the folders in the files directory, and associate each one with an XDG base directory (e.g. files/conifg would be associated with $XDG_CONFIG_HOME).

    Each XDG folder would then have its contents ’overlayed’ with symlinks to those contents in their associated folder. (e.g. in the above example $XDG_CONFIG_HOME/fish/config.fish would become a symlink to files/config/fish/config.fish).

  • Direct file linking

    Each file inside of files/, but not inside a subfolder (e.g. bashrc) will have a symlink generated to it in the users homefolder, with the name of that symlink being the name of the file, but prefixed with a dot (e.g. files/bashrc becomes ~/.bashrc).

Override linking

dotfile-playbook should parse the recipes.yml file, and look for any applicable linkOverride map, with a format like so

linkOverride:
  "my-x-profile": "~/.Xprofile"
  "emacs.el":
    - "~/.emacs.el"
    - "~/.emacs-2.d/init.el"
  "config/riot-web": "$XDG_CONFIG_HOME/Riot"

This allows overriding the default link location for files or folders. Keys that are directories should “overlay” the source onto the destination, in the same way that the DWIM linking does. Keys that are files should create a direct link. All keys are paths relative to the files directory. Keys that show up in an applicable linkOverride should not have the DWIM linking applied to them.

A list may be used as the value, in this case, all the specified links are created.

In this example (assuming .config for the xdg config directory):

  • ~/.Xprofile is a symlink to files/my-x-profile
  • ~/.emacs.el is a symlink to files/emacs.el
  • ~/.emacs-2.d/init.el is a symlink to files/emacs.el
  • ~/.config/Riot/config.json is a symlink to files/riot-web/config.json

Recipes

The recipes.yml can contain link overrides and additional conditional actions.

Conditionals

The two conditional blocks are as follows, executing the described actions/overrides when the value of the key is equal to the value given, or when they are not equal, as follows. Multiple conditions may be specified, and will be combined with an ’and’ operation.

Conditionals may be arbitrarily nested.

When:

when:
  (condition): (value)
  commands: 
    - (action1):
	- a
	- b
	- c
    - (action2):
	- d
	- e
	- f
    - linkOverrides:
	"a": "b"
	"c": "d"

when-not:

when:
  (condition): (value)
  commands: 
    - (action1):
	- a
	- b
	- c
    - (action2):
	- d
	- e
	- f
    - linkOverrides:
	"a": "b"
	"c": "d"

Supported conditions:

  • distro

    the distro key will either be the operating system, for general unixen (e.g. darwin for macOS, freebsd, netbsd, etc.), or the distribution if it is linux (arch, ubuntu, fedora)

  • linux

    Will be true if dotfile-playbook is being run on linux, false otherwise

  • release

    Will be the VERSION_ID or equivalent from /etc/os-release if it exists, or the OS version number on non-linux unixen

  • hostname

    Will be the output of hostname -s

  • hostname-full

    Will be the output of hostname -f

  • domain

    Will be the output of hostname -d

Actions:

Actions can either be a key in a conditional, or can be free floating in the recipes.yml

Link

Manually links a file or directory. This is independent of the DWIM linking, and, unlike the DWIM linking, will create a symlink to a directory if asked.

It will perform this action for all keys specified under it.

Example:

link:
  "data/foo": "some/random/directory/foo"

Copy

Unconditionally copies a file, overriding it if it exists.

Will overwrite the file every time dotfile-playbook is ran.

Example:

copy:
  "data/foo": "some/random/directory/foo"

Copy-Once

Same as copy, but will not overwrite the file if it exists.

Example:

copy-once:
  "data/bar": "some/random/directory/bar"