Automating My Dev Setup

Posted on January 10, 2017 · 6 min read

Switching to a new workstation can be a painful process until you manage to have the exact same setup you did in the previous one.

The last time I was about to upgrade my workstation I began by analyzing which stuff I had installed, which I'd need in the new one and I came up with a list (this recent post goes over most of them).

It wasn't that long but installing everything manually felt like a waste of time, especially because eventually I'd have to upgrade the OS through an installation from scratch or even switch to a newer workstation again.

So I began to look for how did everyone else automate their development environment setup. Most examples were using bash scripts but I also found some that were using Ansible, a configuration management tool like Puppet or Chef. I started reading a bit about it and then focused on how to configure a local machine instead of a remote server, which is the common scenario.

About Ansible

I'm not gonna get into much details about Ansible itself, mainly because my only experience with it is building the playbooks (Ansible's configuration language) to automate the setup of my development environment. All I can say about it is that it makes installation and configuration much easier than, say, using bash scripts.

For instance, the following playbook installs a debian package using apt-get, through the apt module:

https://gist.github.com/pbassiner/073a681726ce742db1b4e87de5f75cda#file-ansible_install_single_package-yml

The key here is that you can parametrize it using the with_items loop statement:

https://gist.github.com/pbassiner/073a681726ce742db1b4e87de5f75cda#file-ansible_install_packages_with_items-yml

Ansible also allows to combine playbooks so, for instance, the list of packages to install can be defined in a separate file and included in others.

Automating the process

The applications and tools to be installed may be distributed differently:

  • Debian packages available through apt-get
  • Debian packages that need to be manually downloaded and installed
  • tar.gz files
  • Binaries
  • Other package managers

That implies that each type of distribution method requires specific installation steps. After having the applications and tools installed, we can also apply a custom configuration like setting up our own dot files.

Parameters

I chose to separate the installation steps from the actual things to be installed. In the vars.yml file I defined the list of stuff to install (for convenience, this is just an excerpt):

https://gist.github.com/pbassiner/073a681726ce742db1b4e87de5f75cda#file-ansible_vars-yml

Debian packages

Installing debian packages requires:

  1. Adding the corresponding apt keys
  2. Adding the corresponding apt repositories
  3. Installing the packages

The apt_key, apt_repository and apt modules, respectively, let you do that:

https://gist.github.com/pbassiner/073a681726ce742db1b4e87de5f75cda#file-ansible_install_debian_packages-yml

On the other hand, manually installed debian packages require:

  1. Downloading the .deb files
  2. Installing them

The apt module can install the package from a local .deb file, which is automatically downloaded if we specify it as an URL:

https://gist.github.com/pbassiner/073a681726ce742db1b4e87de5f75cda#file-ansible_install_debian_files-yml

tar.gz distributed applications

The applications packaged as tar.gz files usually contain a root folder related to the application version (i.e. myApp.2.3.1_build33). That folder might be referenced somewhere else, like a shortcut or a command, and that's likely to break if we upgrade the application. In this case I added an indirection layer using a symlink.

An example of this is IntelliJ IDEA:

  • The tar.gz file you can download is named ideaIC-2016.3.2.tar.gz
  • The extracted folder is named idea-IC-163.10154.41
  • The symlink to the folder, to have the same reference to the binary executable, is ideaIC -> idea-IC-163.10154.41

Thus, the process must:

  1. Download the tar.gz files
  2. Extract them
  3. Generate a symlink for each one of them

The get_url module allows you to download a remote file and the unarchive module lets you extract a compressed file. Finally, with the file module you can create a symlink specifying the state=link property:

https://gist.github.com/pbassiner/073a681726ce742db1b4e87de5f75cda#file-ansible_install_targz_files-yml

Binaries

Installing the binaries requires:

  1. Downloading the binary files
  2. Placing them in /usr/local/bin folder
  3. Making them executable files

With the get_url module you can download a remote file to the desired local folder and make it executable specifying the mode=0755 property, all at once:

https://gist.github.com/pbassiner/073a681726ce742db1b4e87de5f75cda#file-ansible_install_binaries-yml

Other package managers

I use SDKMAN!, a tool for managing parallel versions of multiple SDKs, to install Java, Sbt and Maven. To be able to do so you have to:

  1. Download SDKMAN!
  2. Install it
  3. Then use it to install the desired SDKs

In this case, the installation and configuration is done entirely with bash scripts.

Configuration

This is the most specific part, because it depends on the degree of customization to apply. I do the following:

  • Set zsh as the default shell
  • Create the completions folder for zsh and place the completion files there (for docker-compose, hub and sdk)
  • Install oh-my-zsh by cloning its repo and symlink my custom theme
  • Symlink the dot files .zshrc, .gitconfig, .aliases
  • Create .config folder and symlink configuration files for terminator
  • Create applications folder and symlink idea's desktop file so the application appears in the HUD
  • Create autostart folder and symlink guake's autostart file
  • Install Atom plugins

Bootstrapping

Starting with a freshly installed OS we need to bootstrap the automated setup process. Since the Ansible scripts are stored in a GitHub repository we need to begin with:

  1. Updating OS packages
  2. Installing git and ansible
  3. Cloning the repository
  4. Run the Ansible playbook

Those are the steps that the bootstrap.sh file performs:

https://gist.github.com/pbassiner/073a681726ce742db1b4e87de5f75cda#file-bootstrap-sh

The last mile

While I was developing these scripts I had to execute them a gazillion times, and each and every time I had to:

  1. Open a browser
  2. Go to the GitHub repository
  3. Get the raw content of the bootstrap.sh file
  4. Open a terminal
  5. Create a file
  6. Copy the bootstrap.sh file content to the new file
  7. Save it
  8. Make it executable
  9. Run it

That was clearly improvable, so I googled how to execute a remote bash script and ended up with this:

wget -qO- https://raw.github.com/pbassiner/dev-env/master/bootstrap.sh | bash

After that, running the scripts was just:

  1. Opening a browser
  2. Copying this command (I placed it in the repository README file)
  3. Opening a terminal
  4. Running it

Maintenance

In order to test any changes in the scripts, while keeping the master branch as the working version, the bootstrap.sh file allows to define the branch to use with -b|--branch <BRANCH> (default is master).

The Ansible playbooks I wrote also take care of updating the dependencies. After the initial setup, running again the bootstrap.sh file will handle the upgrades of the applications and tools (you can check the complete bootstrap.sh file here).

Anyway, from time to time I still have to check for updates of the manually installed packages.

References


Comments

Would you like to leave a comment? Since this blog is hosted on GitHub Pages there's no straightforward way to do so.

Instead, you can add a comment in this GitHub issue. If you'd like to see it here, refresh this page after posting the comment.