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 https://www.virtualbox.org/wiki/Linux_Downloads.

 

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: https://www.vagrantup.com/downloads.html

 

Create config files

Three files, read and adapt to your particular needs:

 

./Vagrantfile

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"
    config.vm.network "private_network", ip: '192.168.50.10'
    config.vm.synced_folder "./code", "/home/vagrant/code", type: "nfs"

    # Access app with outer browser
    # otherwise use rails s -b 0.0.0.0 and private ip
    config.vm.network "forwarded_port", guest: 3000, host: 3000

    config.vm.network "public_network", bridge: 'wlan0'

    # http://rvm.io/integration/vagrant
    config.vm.provision :shell, path: 'provision/bootstrap.sh', keep_color: true

    config.vm.provider "virtualbox" do |vb, override|
      # http://www.vagrantbox.es/
      # Prepare with: wget -O ../trusty-vbox.box http://goo.gl/8wqNnb
      override.vm.box_url = "../trusty-vbox.box"
      override.vm.box = "trusty-vbox"
      vb.name = 'project_name'
      vb.memory = ENV['VM_MEM'] || 1024
      vb.cpus = ENV['VM_CPU'] || 2
      vb.customize ['modifyvm', :id, "--natdnshostresolver1", "on"]
    end
  end
end 
 

./provision/bootstrap.sh

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 https://github.com/rails/rails-dev-box

# Configuration variables with custom values
RUBY_VERSION="2.2.1"
APP="project_name"
HOSTNAME='project_name.dev'

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

# Load auxiliary functions:
source /vagrant/provision/functions.sh

# 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_rvm
install_postgres
install 'Nokogiri dependencies' libxml2 libxml2-dev libxslt1-dev
install_node

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

# App specific stuff
install_bower

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

./provision/functions.sh

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 ...
    shift
    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://keys.gnupg.net --recv-keys D39DC0E3'
      echo -e "\nInstalling RVM:"
      su -l vagrant -c 'curl -sSL https://get.rvm.io | 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'
  fi
}

# 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:
      PG_CONF='/etc/postgresql/9.3/main'
      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
  fi
}

# Set VM timezone to have consistent test results
function set_timezone {
    # http://manpages.ubuntu.com/manpages/precise/man7/debconf.7.html
    echo "Setting timezone to: '$1'"
    export DEBCONF_NONINTERACTIVE_SEEN=true DEBIAN_FRONTEND=noninteractive
    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
      fi'

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

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

  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'
  fi
}

Get the base machine

As it is instructed in the comments on Vagrantfile:
wget -O ../trusty-vbox.box http://goo.gl/8wqNnb

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.




 

No comments:

Post a Comment