Automated and Tested Dotfile Deployment Using Ansible and Docker

shell dotfiles ansible travis docker

This is the second part of my posts about Dotfile management. Part one can be found here.

After spending a lot of time and effort on your Dotfiles, it may be useful to setup an automated deployment process. There are existing solutions like GNU Stow, but for maximum flexibility the use of Ansible may be a better option. Using this, files and advanced configuration hierarchies can be distributed easily. This post covers my personal setup, which also includes an automated deployment test approach for multiple linux distributions.

Getting started

Structure

This step will guide you through the process of setting up the required folder hierarchy for the Ansible project. First of all, a folder called roles will be created at the project root to store all created Ansible roles. A role equips a host with the ability to perform required actions and to fulfill a specific ‘role’. Basic roles can be

Each role gets its on subfolder in the roles directory. In a specific role folder, all executed actions will be defined in the file tasks/main.yml, while additional task files can be included at runtime. For more info on this and additional actions, you can refer to the Ansible documentation.

After setting up roles, additional helper scripts and files are created to execute the deployment process, e.g.:

After creating this structure, it can look like this:


├── roles
│   ├── dotfiles
│   │   └── tasks
│   │       ├── main.yml
│   │       └── symlinks.yml
│   ├── general
│   │   ├── files
│   │   └── tasks
│   │       ├── arch.yml
│   │       ├── debian.yml
│   │       ├── main.yml
│   │       └── ubuntu.yml
│   └── vim
│       └── tasks
│           ├── arch.yml
│           ├── debian.yml
│           ├── main.yml
│           └── ubuntu.yml
├── .deployTest.yml
├── deploy.yml
├── Makefile
├── README.md
├── .travis.yml
└── updateDotfiles.yml

Role example

When using Ansible, it’s important to use the predefined modules in order to have idempotency for all actions. Unwanted side effects can occur if this isn’t taken care of. For example, cloning a Dotfiles repository can be as simple as:

- name: Clone Dotfiles repo
  git:
    repo: 'https://github.com/ps1337/Dotfiles.git'
    dest: ~/.dotfiles
    update: yes
    recursive: yes

To create symlinks for specific files, the following structure can be of use:

- name: Create Symlinks for public files
  file:
    src: "{{ srcBase }}/.dotfiles/{{ item.src }}"
    dest: "{{ dstBase }}/{{ item.dst }}"
    state: link
    force: yes
  with_items:
    - { src: "aliases", dst: ".aliases" }
    - { src: "bash_profile", dst: ".bash_profile" }
    - { src: "bashrc", dst: ".bashrc" }
    - { src: "bindings", dst: ".bindings" }
    - { src: "dockerfunc", dst: ".dockerfunc" }
    - { src: "dunst", dst: ".config/dunst" }
    - { src: "exports", dst: ".exports" }

Note that srcBase and dstBase are Ansible variables. These values can be overridden for each user. Using this, multiple users can share a locally cloned repository of Dotfiles.

Additional Tricks

  1. Support for secret sub repositories can be implemented pretty easy using the vars_prompt and when directives.

  2. The generic package manager package can be used to install packages easily without having to specify apt or pacman.

  3. Actions which are specific for a distribution can be included at runtime using:

- include_tasks: ubuntu.yml
  when: ansible_distribution == "Ubuntu"`

Docker + Travis = Automated Testing

After setting up Travis CI for your Dotfile deployment repository, this .travis.yml file can be used to trigger the deployment tests:

services:
  - docker

env:
  - BUILD_CFG=arch
  - BUILD_CFG=ubuntu
  - BUILD_CFG=debian

script:
  - |
      if [ "$BUILD_CFG" == "ubuntu" ]; then
          docker run \
              --rm \
              -v $PWD:/tmp/Dotfile-tools \
              ubuntu:latest \
              bash -c "apt update && \
                       apt -y install \
                           sudo \
                           make && \
                       cd /tmp/Dotfile-tools && \
                       make ansible && \
                       make test"

      elif [ "$BUILD_CFG" == "debian" ]; then
          docker run \
              --rm \
              -v $PWD:/tmp/Dotfile-tools \
              debian:latest \
              bash -c "apt update && \
                       apt -y install \
                           sudo \
                           make \
                           gnupg && \
                       cd /tmp/Dotfile-tools && \
                       make ansible && \
                       make test"

      elif [ "$BUILD_CFG" == "arch" ]; then
          docker run \
              --rm \
              -v $PWD:/tmp/Dotfile-tools \
              alekzonder/archlinux-yaourt:latest \
              bash -c "pacman --noconfirm -Syu \
                           sudo \
                           which \
                           gawk \
                           python \
                           make && \
                       cd /tmp/Dotfile-tools && \
                       sudo -u yaourt bash -c 'cd /tmp/Dotfile-tools && \
                                               make ansible && make test'"
      fi

after_success:
  - // e.g. send notification using Telegram

after_failure:
  - // e.g. send notification using Telegram

As you can see, it’s pretty easy to test the Dotfile provisioning on Arch, Ubuntu and Debian at the same time using Docker and Travis. For each value for BUILD_CFG, a separate deploying process will be started. In each spawned container, Ansible gets installed and all Ansible roles will be executed locally in the container to simulate deploying the Dotfiles and dependencies on a real host.

This can also be performed in intervals using cron jobs to ensure a working deployment suite.

As a bonus, the dependencies of all plugins, helper scripts and other fancy stuff you use will be documented within Ansible roles if done correctly.

This Weird YouTube Trick

python programming shell

How I Over-Engineered My Dotfiles

linux dotfiles

Information Leak in Docker

docker vulnerability