Monday, May 18, 2015

A happy developer environment: revisited

Some time ago I wrote A happy developer's environment aiming to share my sanbox creation scripts.

I promised a "part 2" that never was. Sorry, I am busy and/or lazy.

Now that my magic-auto-sandbox thing has evolved, it's time to revisit it.

Get virtualbox

In my case, adding repositories as instructed in


Get vagrant

It is a virtualization automation tool that was a ruby gem in its origins.
Nowadays, the recommended (linux) install procedure is downloading packages for your favourite distro from:


Create config files

Three files, read and adapt to your particular needs:



It is vagrant's config file, it tells vagrant what machine to start if it already exists or how to create it, if it hasn't been yet.

Vagrant.configure("2") do |vagrant|
  vagrant.vm.define "project_name" do |config|

    # Inject code from outer FS (edit outside, run inside)
    # required by type: "nfs" "private_network", ip: ''
    config.vm.synced_folder "./code", "/home/vagrant/code", type: "nfs"

    # Access app with outer browser
    # otherwise use rails s -b and private ip "forwarded_port", guest: 3000, host: 3000 "public_network", bridge: 'wlan0'

    config.vm.provision :shell, path: 'provision/', keep_color: true

    config.vm.provider "virtualbox" do |vb, override|
      # Prepare with: wget -O ../
      override.vm.box_url = "../" = "trusty-vbox" = 'project_name'
      vb.memory = ENV['VM_MEM'] || 1024
      vb.cpus = ENV['VM_CPU'] || 2
      vb.customize ['modifyvm', :id, "--natdnshostresolver1", "on"]


If you happen to be a systems person (or have one near) here is where they will suggest puppet or chef or whatever "configuration automation" tool.
A simple shell script is enough for Xavi Noria, and is enough for me.

#!/usr/bin/env bash
## Ruby on Rails Development Environment Provision Script ##
# Inspired by @fxn's

# Configuration variables with custom values

echo -e "\n== «$APP» on _$RUBY_VERSION_ ==\n"

# Load auxiliary functions:
source /vagrant/provision/

# General System layout
install 'development tools' build-essential
set_timezone 'Etc/UTC'
setup_locale 'LANG="en_US.UTF-8"\nLANGUAGE="en_US:en LC_ALL=en_US.UTF-8"\n'
hostname $HOSTNAME
install Git git

# Ruby & Rails:
install 'Nokogiri dependencies' libxml2 libxml2-dev libxslt1-dev

# Developer happiness stuff:
install 'ACK-Grep' ack-grep
install 'Log colorizer' ccze
install 'Vim' vim

# App specific stuff

echo -e "\033[1;32m All set, rock on! thanks to the awesome @fxn!\033[0m"


These are the parts that change less from project to project, so I have extracted them for easier reuse.

#!/usr/bin/env bash

echo -e "\033[1;32m Loading auxiliary functions for provision...\033[0m"

# The ouput of all these installation steps is noisy. With this utility
# the progress report is nice and concise.
function install {
    echo installing $1 ...
    debian_frontend=noninteractive apt-get -y install "$@" >/dev/null 2>&1
    echo -e "\033[1;32m [OK] \033[0m"

# Executes a command as the vagrant user
# Example:
#   run 'Description' 'command'
function vagrant_run {
    echo -e "\n$1:"
    eval "su -l vagrant -c '$2'"

# Installs ruby environment through RVM
function install_rvm {
  if [ ! -x /home/vagrant/.rvm/scripts/rvm ]; then
      vagrant_run "Getting RVM keyring" \
        'gpg --keyserver hkp:// --recv-keys D39DC0E3'
      echo -e "\nInstalling RVM:"
      su -l vagrant -c 'curl -sSL | bash -s -- stable'
      echo -e "\nLoading RVM to continue:"
      su -l vagrant -c 'source $HOME/.rvm/scripts/rvm'
      echo -e "\nInstalling Ruby ($RUBY_VERSION)"
      su -l vagrant -c "rvm use $RUBY_VERSION --default --install"
      echo -e "\nInstalling Bundler:"
      su -l vagrant -c 'gem install bundler'
      su -l vagrant -c 'echo "gem: --no-rdoc --no-ri" >> /home/vagrant/.gemrc'

# Installs PostgreSQL database
function install_postgres {
  echo -e "\nInstalling Postgres DB and config..."
  if [ ! -x /usr/bin/psql ]; then
      install PostgreSQL postgresql postgresql-contrib libpq-dev
      sudo -u postgres createuser --superuser vagrant
      sudo -u postgres createdb -O vagrant ${APP}_development
      sudo -u postgres createdb -O vagrant ${APP}_test

      # enable pg_admin access from host machine:
      PUBLIC_IP=$(ip -o -4 addr show eth1 | cut -d ' ' -f 7)
      echo "listen_addresses = '*'" >> $PG_CONF/postgresql.conf
      echo "host all all $PUBLIC_IP trust" >> $PG_CONF/pg_hba.conf
      service postgresql restart

# Set VM timezone to have consistent test results
function set_timezone {
    echo "Setting timezone to: '$1'"
    echo "$1" > /etc/timezone
    dpkg-reconfigure tzdata

function setup_locale {
  # Needed for docs generation.
  echo -e "$1" > /etc/default/locale

function install_node {
    install 'ExecJS runtime (for coffee)' nodejs
    install 'Npm' npm
    su -l vagrant -c 'if [ ! -f $HOME/.npmrc ]; then
        echo prefix = $HOME/.node >> $HOME/.npmrc
        echo "export PATH=\"\$PATH:\$HOME/.node/bin\"" >> $HOME/.bashrc
        source $HOME/.bashrc

    if [ ! -f /usr/bin/node ]; then
      ln -s /usr/bin/nodejs /usr/bin/node

function install_bower {
  echo -e "\nInstalling Bower and vendored assets..."
  if [ ! -f $BOWER_FILE ]; then
    echo -e "{\n\t"directory": "vendor/assets/components"\n}" >> $BOWER_FILE

  if [ ! -x /home/vagrant/.node/bin/bower ]; then
      vagrant_run "Installing bower" "npm install -g bower --silent"

      # Installing bower dependencies
      vagrant_run "Installing bower assets" \
        'cd /home/vagrant/code; /home/vagrant/.node/bin/bower install'

Get the base machine

As it is instructed in the comments on Vagrantfile:
wget -O ../

You will only have to do this once for all the projects you may share the base machine with. (All of them in my case)

Create the machine

You use the command:
vagrant up 
and it will create (and provision) the machine the first time is called and all following occasions it will just boot it up.

To connect to your machine you use:
vagrant ssh

To stop it:
vagrant halt

To destroy (in case you are debugging your creation/provision process):
vagrant destroy

There are improvements to the experience if you set up sudo and ~/.ssh/config on the host machine so they never ask you for passwords, but I will let you decide if you want to go that path while you ponder the security implications.