Dotfile Management and Documentation with Org-Mode

use org-mode to manage and document your rc files

One day ‘polm23’ asked on Hacker News, what do the readers use to manage dotfiles. I was just experimenting with my method and contributed my two cents. Turns out, rare for me, I stumbled upon an original way to edit, document and deploy dotfiles. Although we Emacs users have been using Org-Mode to wrap our .emacs and document our settings for years, few people that I know have used it for managing more dotfiles.

There are at least 3 advantages of using Org-Mode to manage dotfiles, that I can think of right now.

Here’s how

Requirements

Emacs 24.x up, Org-Mode 8.x or newer, and Your favourite keyboard.

Open a new file in Emacs, call it dotfiles.org or whatever you like.

“Normal” dotfiles

These files directly reside under your home directory. So managing them in Emacs is very straightforward. For ease of organization, you can put files of the same category, e.g. all things related to email, under a heading:

* Email
** Muttrc
** Aliases
** goobookrc

Then, under each subheading, put your files in an src block, like this:

* Email
** Muttrc
 #+BEGIN_SRC conf :tangle ~/.muttrc
 #source "/etc/Muttrc"   # Not available on OS X
 source "gpg --batch --passphrase-file ~/.sec/.passphrase --textmode -d ~/.sec/mutt.gpg |"
 set realname="徐栖"

 set sig_dashes

 ...

 #+END_SRC

Then if you run M-x org-babel-tangle, or press C-c C-v t, the content of the above src block will be written to $HOME/.muttrc, overwriting the file’s content if it exists already.

Similarly, write each file’s content in an src block under the corresponding subheading:

* Email
** Muttrc ...
** Aliases
  #+BEGIN_SRC conf :tangle ~/.aliases
  alias mumon      foobar@example.com
  #+END_SRC
** goobookrc
  #+BEGIN_SRC conf :tangle ~/.goobookrc
  [DEFAULT]
 # The following are optional, defaults are shown

   # This file is written by the oauth library, and should be kept secure,
   # it's like a password to your google contacts.
   ;oauth_db_filename: ~/.goobook_auth.json

   # The client secret file is not really secret.
   ;client_secret_filename: ~/.goobook_client_secret.json

   ;cache_filename: ~/.goobook_cache
   ;cache_expiry_hours: 24
   ;filter_groupless_contacts: yes
  #+END_SRC

Now when you invoke org-babel-tangle, the 3 dotfiles will be written. You can put the .org file under version control, edit the dotfiles within Emacs, and deploy them with one command.

Dotfiles in a dotdir

For example, you want to manage your SSH config file, which is under .ssh. Normally it will not be more difficult than the case above. But if you are setting up a new machine and don’t have .ssh path yet, Org-Mode will complain when you tangle.

In that case, you can write a “magic” line at the beginning of the file:

# -*- eval: (make-directory ".ssh" "~") -*-

Alternatively, you can write an src block before the file content block to run commands that create the directories needed.

#+BEGIN_SRC sh
mkdir ~/.ssh
#+END_SRC

The best approach may be setting a header argument mkdirp to yes, like this:

#+BEGIN_SRC sh :mkdirp yes :tangle ~/.ssh/config
(your .ssh/config contents)
#+END_SRC

If your .ssh directory does not exist yet when you tangle this block, Org-Mode will create it for you.

Emacs dotfiles

If your are using the good old .emacs or init.el to store your Emacs configuration, they are managed in the same manner as the above cases. However, if your configuration are already living in another .org file, you probably don’t want to put it in an “org” src block in your dotfile.org. My solution is to put it in a directory under the directory where my home.org (my dotfile) resides. That makes it a little untidy but I don’t have a better solution right now. Ah, the taste of irony.

Credentials and secrets

If you are putting your dotfiles online, you need to save dotfiles with passwords/secrets in an encrypted format. Luckily, Emacs has very good encryption/decryption support. You can put things you don’t want others to read into a specific .org file, and use epa-encrypt-file to get encrypted file with .gpg suffix. After that you can delete the clear text .org file. Next time when you edit the encrypted .org.gpg file, Emacs will use gpg-agent to ask for the password, and decrypt it for you.

Documenting changes

The above method provides a simple and fast way to put all your dotfiles in a few .org files. But it does not fully justify a migration of all your dotfiles into Org-mode src blocks. The strength of Org-mode based dotfile management lies in seamless documentation and instant deploy of changes.

It is possible to breakdown a very long config file into multiple src blocks, and tangle them into one file for deployment. These src blocks can even be put into different subheadings, according to their categories and functions in the final tangled config.

In this case, the file to write to is not specified in each src block’s attributes (‘head arguments’, as in the official document), but as a property of the subheading under which the contents of the file goes. It is better to illustrate with an example:

* Git
  :PROPERTIES:
  :tangle:   ~/.gitconfig   # <- all src blocks under this 'Git' subtree will be written to ~/.gitconfig
  :END:
** personal information
#+BEGIN_SRC conf
    [user]
        name = John Doe
    email = john.doe@example.net
#+END_SRC
** push settings
#+BEGIN_SRC conf
    [push]
        default = upstream
#+END_SRC
...

When you tangle this file, all src blocks under * Git subtree will be tangled into $HOME/.gitconfig.

Now, suppose we want to change Git’s push settings, it is easy to locate that subtree, and do some editing.

** push settings
#+BEGIN_SRC conf
    [push]
        default = simple
#+END_SRC

It may become difficult to tell which part gets edited after a while. Why not write a little note about the change?

** push settings
#+BEGIN_SRC conf
    [push]
        default = simple
#+END_SRC
[2016-03-19 Sat 22:31] change push default from 'upstream' to 'simple'.

The timestamp can be inserted almost anywhere, by pressing C-u C-c !. If you only need to remember the date but not the time, you can use C-c !.

If you often need to change such settings, it is a good idea to document all possible options:

** push settings
   With ~push.default~ set to ~simple~, ~git push~ will fail if the current local branch is not tracking a remote branch, even if remote has a branch with the same name. This seems to be the safest option. Other possible values are:

 - ~upstream~: push the local branch to its upstream branch.
 - ~current~: push the local branch to a branch of the same name.

#+BEGIN_SRC conf
    [push]
        default = simple
#+END_SRC
[2016-03-19 Sat 22:31] change push default from 'upstream' to 'simple'.

In other cases, you may want to experiment with various combinations of options. You can write them all out, and tell Org-Mode not to tangle some of them:

#+BEGIN_SRC conf :tangle no
safe_threshold=1
encryption_mechanism=ECDHE_RSA
#+END_SRC

#+BEGIN_SRC conf
safe_threshold=0
encryption_mechanism=HMAC-SHA1
#+END_SRC

Only the latter config will enter the config file.

Managing remote dotfiles and configs

In :tangle head argument or subtree property, you can specify a remote location, typically a remote server which you have SSH access. Suppose you are in charge of a web server, you can save yourself a lot of remote editing by using Org-Mode to manage its configuration:

* Nginx
  :PROPERTIES:
  :tangle:   /webadmin@ssh.example.org:configs/nginx.conf
#+BEGIN_SRC conf
  worker_processes 4;

  events { worker_connections 1024; }
  ...
#+END_SRC

#+BEGIN_SRC sh :dir /ssh:webadmin@ssh.example.org|sudo:ssh.example.org :tangle no
cp /home/webadmin/configs/nginx.conf /etc/nginx/
chown nginx:nginx /etc/nginx/nginx.conf
#+END_SRC

The first code block get tangled into the remote file /home/webadmin/nginx.conf, the second code block has :tangle no and will not be tangled into any file, but you can run the code block from your local Emacs, it will ask you your sudo password, and copy the file to the right location and set owners.

Caveats

  • Emacs is single-threaded. If you use Org-Mode to deal with files/shells on remote systems through a slow connection, you will have to wait during tangling remote files and executing remote commands.
  • You don’t have to deal with symlinks, and you don’t get its benefits. For example, you change your Git settings through command git config --global .... Such changes don’t automatically get updated in your Org-Mode file. It is your responsibility to update your dotfile.org by hand.
    • UPDATE: Ken Mankoff sends me a tip that partially solves the problem. You can add the following line to the top of your dotfile.org:

      #+PROPERTY: header-args:conf  :comments link :tangle-mode (identity #o444)

    In Ken’s own words: “This makes the files read-only, so I can’t edit them by mistake. It also creates a commented link at the top of each, so I can jump from the dotfile to its Org origin if I open the dotfile by mistake.” Kudos.

  • Your dotfile.org may become to big and unwieldy. For most people this is not a big deal. On my late-2012 MacBook Pro, opening Org files of a few hundred KB is as smooth as opening a new file. But if this system is used for a long time, the files may grow with all those logs and documents. In that case, you may want to split the files by, say, putting each top-level headline in a separate file. It is easy to create links to other files in Org-Mode, so you can still conveniently navigate through all the files.

Acknowledgements

This system is inspired by Sacha Chua’s Emacs config. I did not realize Org-Mode was such a powerful tool for system administration until I see Howardism’s talk on literate devops. Last but not least, thanks to all the people behind Org-Mode, Tramp and GNU Emacs.