Automatically Setup Your Computer with Ansible Playbook
Setting up a computer can be a major annoyance. Being ‘the tech guy’, I still have vivid nightmares of spending every other weekend reinstalling friends’ and family members’ Windows boxes that have come to a crawl. Now, most of us know enough to backup the important data and that makes the process easier. (If you don’t have backups, go setup Nextcloud, Syncthing, or some other automatic system. Seriously, it’s gonna come back to bite you sooner or later.) But there is still the matter of installing and configuring all your software. Some people have tackled that by writing install scripts. However, there is a better way - Ansible playbook. Let me introduce Ansible. Ansible is a software for computer provisioning. In computing, provisioning means automated installation and configuration of software and services. Provisioning tools, such as Ansible, Chef and Puppet, help you cut down the setup time from hours to mere minutes. Their main advantage over custom scripts is that they are idempotent. That means they only makes the necessary changes. So, if a task fails (for example because ou forgot to connect your Ethernet cable, oops), you can just run it again without any worries. The reason I chose to use Ansible, is that the tasks are easily configurable using yaml files. It is also written in Python, which makes it easy to write your own module for it, in case of need. So what is Ansible playbook? It is the collection of yaml files, describing the tasks to be executed. You can find an introduction to its syntax in Ansible documentation. I will demonstrate how you can setup your computers using an Ansible playbook, using mine as an example. You can find it at my Gitlab page.
Structure
This is the layout of the playbook:
roles/ vars/ run.sh setup.yaml
Roles, as the names suggests, contains roles. They are collection of tasks, files and variables needed to setup a particular service. Vars contains device-specific Ansible configuration. Setup.yaml is the playbook entry point and run.sh is the script that starts it all. Let’s go through them one by one.
The playbook entry point
Here is a snippet from the setup.yaml file:
--- - hosts: localhost connection: local vars_prompt: - name: config_file prompt: "What computer is this" private: no - name: gitlab_password prompt: "Enter your Gitlab password" private: yes vars_files: - vars/{{ config_file }}.yml roles: - alzadude.firefox-addon - common - desktop - dotfiles - {role: laptop, when: laptop} ...
The first two lines are self explanatory. They specify, that the tasks should be run on the same computer the playbook was executed on. Following is the vars_prompt
, which prompts user for input and stores it in a variable. I use it to determine which device-specific configuration should be loaded. And also to enter the gitlab password - I don’t want to store it in the playbook. The largest chunk is taken by the roles. As you can see, they can be called conditionally. I have a laptop
variable stored in my vars
, to determine whether I should install laptop- mode-tools
.
Device-specific configuration
Now, let’s look at the configuration files. These are the contents of vars/hp-dm1.yaml
, the configuration file for my laptop:
--- user: dak model: dm1 laptop: true ssd: true
As you can see, not much going on here. Just my username for the particular machine and some variables to determine, whether to run certain roles.
Writing your roles
As I mentioned earlier, roles are a collection of tasks, files, variables etc. needed to installl and configure a service. They are the heart of a playbook. The role has a specific directory structure, that I describe below.
example_role/ defaults/ main.yml files/ ... meta/ main.yml tasks/ main.yml templates/ ... vars/ main.yml
template
are files such as systemd timers or configurations, that are necessary for the role. They include variables, which will be filled in, when the template is handled. For things that asre invariable (such as plugins), use the files
directory. defaults
include the default configuration, for other variables vars
is used. meta
contains dependencies. Lastly, tasks
contain a list of tasks to run. Of course, you might not need all of these for your role. Indeed, the only mandatory section are the tasks. So let’s take a look how the task file might look:
--- - name: install terminal applications become: yes package: name: - fish - tmux - guake state: present - name: make fish default shell become: yes user: name: "{{ user }}" shell: /usr/bin/fish - name: copy guake config copy: src: guake/ dest: ~{{ user }}/.gconf/apps/guake/ mode: 755
The first task installs some software using apt. As you can see, it is possible to use substitution, instead of writing a new task for every piece of software. The state=present
means that will check it the item is already on the system, and if not, install it with apt. The next one runs modifies a user on the system. In this case, to make fish its default shell. You could also just run chsh
via the command
module. But running external commands is not idempotent. So you should use the functionality provided by Ansible modules whenever possible. The last two task copies some configuration files. It automatically looks for them in the role’s files
directory.
The script
The last piece of the puzzle is the shell script to run the playbook:
#! /usr/bin/env bash # Stop unpredictible behavior set -o errexit # Exit on most errors set -o nounset # Disallow expansion of unset variables set -o pipefail # Use last non-zero exit code in a pipeline # Define constants readonly PROJECTS_DIR=projects readonly PLAYBOOK_REPO="https://gitlab.com/radek-sprta/ansible-personal.git" readonly PLAYBOOK=setup.yml function main() { # Install Git and Ansible sudo apt install git ansible # Clone the Ansible playbook and run it cd "${HOME}" mkdir -p "${PROJECTS_DIR}" ansible-pull -U "${PLAYBOOK_REPO}" -i localhost, "${PLAYBOOK}" --ask-become-pass # Install Steam sudo apt install steam } # Run the script main "$@"
Of course, you could skip writing a script altogether. You could just install Ansible, clone the git repository and run the playbook directly. But that would beat the purpose of automating the setup. Moreover, you need to install Steam. Because of its EULA, Steam unfortunately cannot be installed by Ansible.
Conclusion
I wrote this blog post to showcase how Ansible can be useful even for home use. If you are thinking about setting up your own computer with Ansible now, check my playbook or head over to Ansible documentation