Configuring a Laptop with Ansible, Part Two

I recently got a new Macbook Pro laptop, and used Ansible for most of the setup. In my last post, I covered essential Ansible usage. This post will walk through an example laptop configuration.

Grabbing a Snapshot of the Ansible Config

If the Ansible playbook is in a remote git repo, it can just be installed with:

$ git clone ssh://DOMAIN/PATH_TO_REPO/

Depending on where the git repo is hosted, this may require logging in. Running git for the first time may automatically download the Command Line Tools. If not, they will be downloaded within the next few steps.

Bootstrapping Ansible

The example repo has a couple scripts in the bin/directory. In particular, bin/bootstrap_osx will install homebrew and then download Ansible.

The recommended install method involves curling directly to Ruby. My bootstrap script downloads the installer, ensures that it downloaded completely, and then prompts whether to run it or not (giving a chance to audit the installer).

If necessary, installing homebrew will also install the Xcode Command Line Tools package, which provides a C compiler and other important dependencies.

Public Keys

Since Ansible is typically ssh-ing into remote computers, it’s best to use public keys and ssh-admin, rather than entering passwords all the time. One of the first tasks in my ‘common’ role is to install SSH public keys with the authorized_key module if not already present.

To create an SSH key for the new laptop, run:

$ ssh-keygen

It will create private and public keys. By default, the public key will be in ~/.ssh/id_rsa.pub. If the Ansible playbook involves signing in to remote servers, it will be much less trouble to add the key to their authorized keys.

To add the key to the SSH agent on OSX, use ssh-add KEY_FILE. (Linux and BSD users will probably need to run something like eval $(ssh-agent -s) to start and/or reconnect to their SSH agent.)

The authorized_key module can do this:

- name: install SSH key (LAPTOP NAME)
  sudo: no
  authorized_key:
    key: "ssh-rsa ... [email protected]"
    user: '{{ansible_user_id}}'
    state: present

The password will need to be typed during the first connection, but the playbook’s remaining tasks can use it.

The example repo’s bin/setup_ssh_key script will run ssh-keygen to generate a new key (if not present), and then add it to the Ansible playbook as described above.

Running the Playbook

Then, just run the playbook. Either specify a hosts file (via symlink or
-i, for “inventory”), or edit the default one to include “localhost”. (The included script, bin/link_paths, will create these symlinks.)

ansible-playbook will need to be able to log in to the target computer. Either configure SSH so that ssh localhost works without a password prompt, or provide the -c local option to explicitly run the playbook locally.

# -K: prompt for sudo password; -c local: use local connection
# if the playbook has more than one host, use -l HOSTNAME to limit

$ ansible-playbook -K -c local playbookname.yml

and let it run. Since the playbook is installing stuff, it will probably take some time downloading packages the first time.

If the git_homedir role is used, the variables in group_vars/all that define the git repository host and path will need to be configured.

Recovering from Mistakes Along the Way

Playbooks may need a few minor adjustments along the way. (Mine error’d out because gdb isn’t in base homebrew anymore.) After making updates, the playbook can be resumed with ansible-playbook --start TASK_NAME. This is also useful while developing new playbooks.

The -K option prompts for a sudo password upfront, and catches most things, but sometimes commands require password input along the way. For example, the playbook may need to log in to a remote git repository. If this times out and the playbook fails, --start can resume there, prompt immediately, and continue.

The homebrew and homebrew_cask Modules

Many packages can be installed via either the homebrew or homebrew_cask modules. To search homebrew, use brewformulas. For homebrew_cask, use caskroom.

To install packages via homebrew:

- name: install stuff
  homebrew:
    name: "{{item}}"
    state: present
  with_items:
    - cowsay
    - fuzzy-find
    - tmux

For homebrew_cask, use:

- name: install casks
  homebrew_cask:
    name: "{{item}}"
    state: present
  with_items:
    - arduino
    - flux
    - mou
    ...

Other Setup

Many other OSX preferences can be set via the defaults command, and I found several useful settings options in Mathias Bynens’s dotfiles repo.

Individual shell commands can be run like this:

- name: automatically hide and show the Dock
  sudo: yes
  command: defaults write com.apple.dock autohide -bool true

- name: significantly speed up expose (skip animation)
  sudo: yes
  command: defaults write com.apple.dock expose-animation-duration -int 0
  notify:
    - restart the dock

The defaults command seems to be idempotent, so writing settings with it multiple times should be fine.

My home directory is already tracked in git and set up via Ansible.

After the playbook finishes, my laptop is up to date. Down the line, if I make changes via the Ansible playbook, I’ll know that I can reproduce my setup.