Stranger Systems



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.


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


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/{.verbatim} for $XDG_CONFIG_HOME{.verbatim})

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

DWIM linking

  1. 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]{.spurious-link target=”*Layout”} $XDG_CONFIG_HOME/fish/ would become a symlink to files/config/fish/

  2. 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{.verbatim}).

Override linking

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

  "my-x-profile": "~/.Xprofile"
    - "~/.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{.verbatim} directory. Keys that show up in an applicable linkOverride{.verbatim} 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{.verbatim} for the xdg config directory):


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


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.


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


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

Supported conditions:

  1. distro{.verbatim}

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

  2. linux{.verbatim}

    Will be true{.verbatim} if dotfile-playbook is being run on linux, false{.verbatim} otherwise

  3. release{.verbatim}

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

  4. hostname{.verbatim}

    Will be the output of hostname -s{.verbatim}

  5. hostname-full{.verbatim}

    Will be the output of hostname -f{.verbatim}

  6. domain{.verbatim}

    Will be the output of hostname -d{.verbatim}


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

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.


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


Unconditionally copies a file, overriding it if it exists.

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


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


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


  "data/bar": "some/random/directory/bar"