PythOps

Setup your Linux workstation with Ansible

Last update: 11 October 2021

Introduction

In this article I'm gonna talk about a common challenge we're all facing as software engineers that we become more aware of once we want to reinstall our workstations.

The problem we are all facing is how can we replicate easily and smoothly the setup of our workstations in another machine ? In other words, as a software engineer, how can I have my whole setup (tools, packages, libraries, dev environments, editor, configuration files...) in another machine in couple of minutes ?


The common approach, the dotfiles approach

Before I dive into my approach, let's see how most of the people do today.

The common way is to have a Github repo, usually called dotfiles, where you store the config files of the common tools you use, that you update a couple of times per year. And once you want to replicate your installation somewhere else, you clone your dotfile repo, and start copying each file individually, what a tedious task ! Without mentioning that most of the time it does not work straightforward as you usually forget to install some dependencies or probably the config file is not updated, 🥴 !

If you have been already in that situation, you're not alone. I have good news for you, there is a better way. So let me introduce my approach.


My approach

My approach tries to address two major challenges :

  1. I want an easy way to replicate my whole setup in just a couple of minutes.
  2. I want to be able to test new tools/setup easily without interfering with my machine, and also I want to be able to do that in couple of minutes.

Let's dig into the first challenge:

You may be attempted to create some sort of bash script to install your packages and copy different files, that would work in theory, but it's going to be hard to maintain, not easy to read, not easy to export to other platforms... That's where Ansible make its appearance 😎

In nutshell, Ansible is a radically simple IT automation engine that automates cloud provisioning, configuration management, application deployment, intra-service orchestration, and many other IT needs.

So all what you need to do, is to describe in yaml files the state of your machine, and Ansible will make the necessary to have your machine setup as you describe it.

One powerful feature of Ansible that I want to insist on is Idempotence. It means, if you run a task multiple times, you'll get the same output. Why it does matter here ? Well, imagine you have a bash script, you need to handle the cases of commands failing if the resources you want to create exists or modified (example you want to clone a repo but if it does exist you want just to pull the latest changes, or add a new user to your system ...), with Ansible, you don't need to worry about running those tasks multiple times, your machine will be the mirror of what you define in the yaml files.

Let's take an example.

- name: Install zsh
  community.general.pacman:
    name: zsh
    state: latest
  become: yes

- name: Install oh my zsh
  git:
    repo: https://github.com/ohmyzsh/ohmyzsh.git
    dest: ~/.oh-my-zsh
    depth: 1

- name: Copy zshrc
  copy:
    src: zsh/zshrc
    dest: ~/.zshrc
    mode: 0644

- name: Install spaceship prompt
  git:
    repo: https://github.com/spaceship-prompt/spaceship-prompt.git
    dest: ~/.oh-my-zsh/custom/themes/spaceship-prompt
    depth: 1

- name: Enable spaceship theme
  file:
    src: ~/.oh-my-zsh/custom/themes/spaceship-prompt/spaceship.zsh-theme
    dest: ~/.oh-my-zsh/custom/themes/spaceship.zsh-theme
    state: link

I think you can guess easily without even knowing yaml or Ansible what those lines do. But let's explain what it is.

This defines 5 tasks:

  1. The first one installs zsh package using pacman (pacman is the package manager for Arch Linux). state: latest means that Ansible will install the latest version of zsh. become: yes means this task is performed with root.

  2. The second one, we clone the repo https://github.com/ohmyzsh/ohmyzsh.git into ~/.oh-my-zsh. If we run this task multiple time, we won't get the error as if we use the git command fatal: destination path '~/.oh-my-zsh' already exists and is not an empty directory. but instead, it will pull the latest changes.

  3. The third one, it copies the local file zsh/zshrc into ~/.zshrc as you would do with the cp command but you can set the permissions as well.

  4. The fourth one clones https://github.com/spaceship-prompt/spaceship-prompt.git repo

  5. The last one creates a symlink as you would do with ln -s command. The difference with ln -s is that this is idempotent, whereas with ln -s you'll get this error: (ln: failed to create symbolic link 'spaceship.zsh-theme': File exists)

You can check here to know more about Ansible. It's really a great and easy tool to learn, you'll love it for sure.

Now that we figured out how to replicate our setup quite easily, let's see how can we test it.

One way to test our setup is to create a VM and run our setup inside it. It's quite easy and straightforward.

Let's see a demo with my setup, so you can visualize all the steps


Demo

I'm gonna demo my approach with my workstation setup that you can find in here in github.

Let's clone it first

$ git clone https://github.com/pythops/workstation

For this to work we're going to need :

  1. Virtualbox
  2. Vagrant to create the VM in an easy way.

Once we have that installed, we can create our test VM and run our setup with the following command:

$ vagrant up
Bringing machine 'workstation' up with 'virtualbox' provider...
==> workstation: Importing base box 'archlinux/archlinux'...
.
.
.
==> workstation: Running provisioner: ansible...
    workstation: Running ansible-playbook...
PLAY [all] *********************************************************************

TASK [Gathering Facts] *********************************************************
ok: [workstation]

TASK [setup : Install packages] ************************************************
changed: [workstation]

TASK [setup : Create ~/bin directory] ******************************************
changed: [workstation]
.
.
.
TASK [setup : Install fzf-tab plugin] ******************************************
changed: [workstation]

TASK [Add ssh keys] ************************************************************
changed: [workstation]

TASK [Change the shell for vagrant user] ***************************************
changed: [workstation]

PLAY RECAP *********************************************************************
workstation                : ok=65   changed=63   unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

==> workstation: Running provisioner: reload...
.
.
.


Once this operation is done, you can open Virtualbox and show the running VM:

The username is vagrant and the password is vagrant


Once you log in, you press <Window key> + <Enter key> then a terminal should appear


Now that I have a working setup, I can apply the setup to a new machine. I can replicate the whole setup with make all or just part of it, to install only tmux for instance I just need to run $ make tmux. This puts my github repo as source of truth for my whole setup.


Conclusion

As you can see, this new approach lets you have a working setup that you can replicate easily in other machines. So far this does not cover all yet. We are still missing couple of the things like the bootloader, kernel, initramfs, disk encryption ... This is something I'll cover soon, so stay tuned if you want to see how it can be done.