Blog

Setting Up Devboxen With Ansible

By Jay

Jul 13, 2023 | 5 minutes read

Series: HomeLab

Tags: blog, tech

If you’ve not used it,ASDF is a tool version manager that helps ensure that you - and your teams - are all using the same tool version. It is a simple and elegant solution to the problem that nearly all development teams find themselves in, where different microservices (or different projects) all have differing versions of tooling that they have been written with. ASDF cuts through that problem by allowing you to define a .tool-versions file on a per-directory basis to keep everyone on the same version.

ASDF is also incredibly simple and extendable. You install a plugin that tells ASDF how to install a given tool or program, and then ASDF manages it from there. The plugin syntax is fairly straightforward and there are a number of examples if you needed to add to the ASDF plugin library.

My particular use case here is to setup a development virtual machine or container for my use; I personally use Proxmox as my hypervisor, but this process will work with any provider that allows you to SSH into your new system.

To do this, we are leveraging Ansible to do the heavy lifting. If you’re not familiar with Ansible, it is an IT configuration management tool that is written in Python and leverages SSH to perform configuration management tasks. We will be using Ansible to connect to our newly provisioned systems and configure them according to our tastes. Note that this is my preferred configuration; yours will likely be different.

Ansible starts with an inventory of the systems that you wish to act on; this is a file that contains the system names along with additional information required by Ansible. In this example, we are acting on a system with the name of “host_name” which is part of “group_name” (you can disregard groups for now). There are two configuration tweaks that I have added - I want Ansible to always use Python3 on the host system, and I don’t need strict host checking since nearly all of my systems are internal (you should decide if this is the right call for you and your use case, however).

[all:vars]
# Specify the Python interpreter
ansible_python_interpreter=/usr/bin/python3
# Automatically accept host SSH keys
ansible_ssh_extra_args='-o StrictHostKeyChecking=no'

[group_name]
host_name

The next thing we do is define the variables that Ansible will use for deployment; the example file provided below can be followed. The ASDF website can be used to check plugin names and versions; or you can setup ASDF locally in order to build out the .tool-versions you wish to push to the new systems.

# User configuration
user_name: '<username>'
user_group: '<group>'
user_shell: '<shell_path>'
sudoers_content: '%<group> ALL=(ALL:ALL) NOPASSWD:ALL'
authorized_keys_url: '<authorized_keys_url>'

# ASDF configuration
asdf_init_shell: true
asdf_version: '<asdf_version>'
asdf_user: '<asdf_user>'
asdf_group: '<asdf_group>'
asdf_default_tool_versions_file: '<tool_versions_file_path>'

# ASDF plugins configuration
asdf_plugins:
  - name: '<plugin1>'
    versions:
      - '<version1>'
    global: '<global_version1>'
  - name: '<plugin2>'
    versions:
      - '<version2>'
    global: '<global_version2>'
  # Add more plugins as needed

# Required packages
required_packages:
  - '<package1>'
  - '<package2>'
  - '<package3>'
  # Add more packages as needed

Finally, there is the Ansible playbook; this does the heavy lifting for our process. The playbook contains inline comments to help explain what each step is doing, but in short this playbook:

  • Updates all installed packages to their latest versions.
  • Creates a new user and group.
  • Adds passwordless sudo for the new user.
  • Installs any system level packages required by ASDF.
  • Creates the ASDF plugins.
  • Installs the requested version.
  • Adds the versions installed to the .tool-versions file.
# This playbook is divided into two parts. The first part is executed as root and the second part as the user 'jschmidt'.

# Part 1: Executed as root
- hosts: all
  remote_user: root
  gather_facts: true
  become_method: sudo
  become: true
  tasks:

    # Include our necessary variables
    - name: Include variables from vars.yaml
      include_vars: vars.yaml

    # Update and upgrade apt packages
    - name: Update and upgrade apt packages
      become: true
      apt:
        upgrade: yes
        update_cache: yes
        cache_valid_time: 86400 # Cache valid for one day

    - name: Make sure we have a '{{ user_group }}' group
      group:
        name: "{{ user_group }}"
        state: present

    - name: Add the user '{{ user_name }}' with a specific uid and a primary group of '{{ user_group }}'
      user:
        name: "{{ user_name }}"
        comment: User
        group: "{{ user_group }}"
        shell: "{{ user_shell }}"

    - name: sudo without password for '{{ user_group }}' group
      copy:
        content: "{{ sudoers_content }}"
        dest: "/etc/sudoers.d/{{ user_name }}"
        mode: 0440

    - name: Set authorized keys taken from url
      ansible.posix.authorized_key:
        user: "{{ user_name }}"
        state: present
        key: "{{ authorized_keys_url }}"

    - name: Install required system packages
      apt: name={{ item }} state=latest update_cache=yes
      loop: "{{ required_packages }}"

    - name: Create .tool-versions file
      template:
        src: tool-versions.j2
        dest: "/home/{{ user_name }}/.tool-versions"
      become: yes
      become_user: "{{ user_name }}"


# Part 2: Executed as our user
- hosts: all
  remote_user: root
  gather_facts: true
  become_method: sudo
  become: true
  roles:
    - role: cimon-io.asdf
  tasks:
    - name: Include variables from vars.yaml
      include_vars: vars.yaml

There is one additional file that is important in this implementation, this is the templates/tools-versions.j2 template file that is used to build out the .tool-versions file.

Once everything is setup appropriately, you simply run the playbook.

$ ansible-playbook -i ./ansible-hostfile asdf-playbook.yaml
  • This code is available in my jaydev-tooling repository for those who don’t feel like cutting and pasting.
  • Plugins are installed in the order presented in the file, so if you have dependencies you need to install them earlier in the list. For example, a lot of tooling depends on Python so you are going to want to install that first.
  • Running into issues or have questions? Drop an issue into Github and I’ll try and help.