I have been an Arch user for a number of years now, since 2019 or so, and in that time i have experimented with a bunch of different desktops.

Shortly after switching to Arch, i fell down the Tiling Window Manager rabbit hole: My first love was Qtile, until i switched to suckless’s DWM about a year later.

I actually really enjoy the tiling window manager workflow, but one downside of becoming a ricer is that it makes setting up new machines quite tedious: There are a lot of dotfiles to symlink in place, a lot of extra packages to set up, a lot of boring busy-work.

Traditionally the solution for this was to create an install script for your custom setup, that you could just curl and pipe into bash.

But i beg to differ, because i have to make a confession:

I really, really hate UNIX shell as a language: its clunky, its arcane, its haphazardly put together.

You could use a better language for this that is almost as universal, like Python, but in order to do it properly you would have to set up virtual environments for dependency management, and its a whole thing…

You could also make your own archiso using the eponymous tools, but that seems a bit too much work.

But fear not fellow ricer! for there is a Third way: Ansible

Why I chose Ansible

If you are not familiar, Ansible is a system that allows you to configure any number of Linux (or Windows) servers, by connecting to them via SSH, and running predefined tasks. The operations are carried out in parallel, so if the task is to ‘install vim on these 5 machines’ then Ansible will attempt to connect to all 5 and install vim on them at the same time.

But this is not the main advantage of Ansible for us, which is instead idempotency: each basic operation in Ansible is idempotent, which means it is only carried out if needed.
Going back to the previous example, this means that if ‘vim’ is already installed on 2 of these 5 machines, those ones will be skipped over because they are already in a state we want them to be in.

How I use Ansible

About a year ago, i tried putting this into practice: the inception for the project was the introduction, in the official Arch image, of the archinstall utility: thanks to that i had a relatively friction-less way to do a base arch install, but i needed a way to go from a basic arch installation with Xorg, to a fully functional desktop.

That’s Ansible’s job.

The code for this project can be found on my GitHub.

This was my first real use of Ansible, so the code is not pretty, and i have kinda twisted Ansible into a use-case that it was not designed for. I will point out these “Ansible abuses” as we go.

What does it do?

The playbook assumes you have completed a guided archinstall, or that you have installed arch the traditional way, using the wiki.

The actual run instructions can be found in the README.

Once you run it and reboot the machine, you should get the following.

  • A suckless desktop with my own custom builds of:

    • dwm (Used as the main window manager)
    • dmenu (Used as the universal run launcher)
    • st (Used as the default terminal)
    • slstatus (Used as the default bar)

    dwm, st, and dmenu will have a custom color scheme matched to your wallpaper. I have included a handful of wallpapers that generate good color schemes, and one will be picked at random once the playbook runs.

  • My dotfiles for stuff like VIM, ZSH, Tmux etc…

  • A basic XFCE4 desktop environment, in case i need a traditional desktop.

  • A smattering of packages which i thing are essentials in every install.

There is no Wayland support, and i consider that a feature as a proud Xorg simp 😎

Structure of the Playbook

The playbook is structured as follows:

  • local.yaml is the main playbook file you will run, which will in turn run a number of roles.
  • hosts is the file that is supposed to contain a list of targets to run Ansible against, but in my case, the only entry it the local machine.
  • host_vars/localhost.yml is a file that defines a couple of variables that you might want to change before running the playbook on a new machine, such as the Git username, email, and default editor.
  • Ansible.cfg if a configuration file for Ansible himself, you don’t need to care about it.
  • The roles folder contains, unsurprisingly, role definitions.

Mechanics and Gachas

The playbook, as we mentioned, runs a number of roles.

Usually in Ansible, roles define a number of actions to be taken against a subset of hosts. For example, you can group a number of hosts under a ‘CI servers’ group, and then create a ‘ci_servers’ role that installs/update Jenkins.

In our case however, since we have a single host, roles are used as a way to group different kinds of task together. By navigating to roles/<role>/tasks/main.yaml once can see what each role is doing once run.

This breakdown into roles is motivated by the fact, that some roles require superuser privileges to run, whereas others do not.

The roles are run in order, as defined in local.yaml
I have tried to structure this in a sensible way and keep the scope of each role somewhat limited, but the setup_homedir role has a lot of miscellaneous tasks.

A lot of the tasks that involve running shell command cannot be idempotent by default, so custom conditions need to be written that allow Ansible to tell if a certain shell-based task needs to be executed.
A lot of these checks are rather inefficient, due to my inexperience with Ansible at the time.

Finally, the install_python_packages role is unused and will not be run: this role installs Python packages via pip, defaulting to whatever the system’s version of Python is. There is no dependency management implemented via virtual environments, and Arch itself discourages the use of pip to install system-wide packages.

Luckily for me, the few Python packages i need have landed in the official repos in the last 3 to 4 years.