Configuring a Git-Controlled Home Directory with Ansible

Article summary

Ansible is a configuration management tool that uses “playbooks”, special scripts that describe an intended configuration (rather than operations to get there), and “modules”, which make changes to reflect the playbook’s configuration. (For example, “runit should be present”, rather than “install runit”.) Any settings that are already in effect can be skipped, so minor updates to a configuration apply quickly. The same configuration can also set up new computers from scratch.

Mattie’s intro to Ansible at DevOpsWestMI piqued my interest. Aside from a server that runs some personal projects, I wanted to use Ansible to manage my main development environment. Part of this is automating installation of my home directory, which I keep tracked in git (along with my Ansible configurations).

While the main Ansible documentation is good, and there’s a great third-party guide for using Ansible with Vagrant, I didn’t find anything explaining how to clone and set up a git-backed home directory.

The Situation

My home directory setup isn’t especially complicated. It tracks some .dotfiles, scripts in ~/bin/, my Emacs configuration, and a directory (~/docs/notes) that contains a bunch of small, easily searched text files. I have a .gitignore file that contains *, so files need to be explicitly added. That way, other repositories nest without much trouble.

This repo stretches back quite a ways: I imported it into git in 2009; before that, it was in mercurial, and before that, monotone. A lot of my workflow has been captured in these scripts, and I want to make configuring new development environments quick. While I hadn’t previously set up new environments all that often, I’m using virtualization a lot more lately.

The Setup

Pulling the git repo into ~ is a two-step process.

1. Cloning the repository

The Ansible git module is used to clone the home directory repo to ~/dev/homedir/ . (A previous task ensures that ~/dev/, my project directory, already exists). update: no specifies that it should only check out in that spot the first time, since I will be updating it in ~ instead. accept_hostkey: true is used because this may be the first time I connect to my private VC server.

My development environment role in roles/dev/tasks/install_git_homedir.yml contains:

- name: clone homedir git repo in ~
  git:
    repo: 'ssh://{{vc_private_host}}/homedir'
    dest: ~/dev/homedir/
    update: no
    accept_hostkey: true
  notify:
    - install homedir

2. Triggering on the first installation

After the directory is successfully cloned, the notify line triggers a handler in roles/dev/handlers/main.yml, which uses the synchronize module (a wrapper around the ever-handy rsync) to copy it to my home directory:

- name: install homedir
  synchronize:
    src: ~/dev/homedir/
    dest: '{{ansible_env.HOME}}'
    recursive: yes
  delegate_to: '{{ansible_hostname}}'

Ansible’s git module won’t check out directly to the home directory, but doing a checkout to a subdirectory and then copying everything up the first time works just fine. Since the checkout specification is idempotent, it only triggers the handler during the first checkout. While there may be another way to do it that doesn’t keep around a second checkout in ~/dev/, this method works well enough.