Automated and Tested Dotfile Deployment Using Ansible and Docker
shell dotfiles ansible travis dockerThis 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
dotfiles
: Provide all Dotfiles, fonts, etc.general
: Installs the most basic tools you will need on all hosts and Dotfile dependencies (This can be moved into thedotfile
role if desired)vim
: Installsvim
/neovim
and all plugin dependencies.
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.:
Makefile
to start specific deployment strategiesdeploy.yml
to call the created roles in a modular mannerupdateDotfiles.yml
only executesdotfiles
role to update the provided files and symlinks on a host
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
-
Support for secret sub repositories can be implemented pretty easy using the
vars_prompt
andwhen
directives. -
The generic package manager
package
can be used to install packages easily without having to specifyapt
orpacman
. -
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.