Installing ruby with Capistrano & rbenv

An article, posted more than 2 years ago filed in capistrano, rbenv, deployment, script, task, automation & ruby.

While we’re supposed to create docker(y) images and deploy these to the cloud, I’m still comfortable deploying and maintaining quite a range of applications using Capistrano (this builds on the battle tested server management process that I outlined more than 7 years ago). But Capistrano and its plugins are typically aimed at performing application level tasks, and not so much about configuring the environment.

I typically install ruby using rbenv. To deploy ruby apps using rbenv a Capistrano plugin exist (capistrano/rbenv) but it is missing the commands to install and/or update the ruby installation.

This snippet presented here adds a few commands:

Additionally it reimplements the cap rbenv:validate method, as by default it prevents running any capistrano task when the ruby version doesn’t match.

# Capistrano deploy fragment that adds rbenv:install, rbenv:update and reimplements rbenv:validate with a non-exiting warning.
#
# this assumes "capistrano/rbenv" is `require`d in Capfile
#
set :rbenv_ruby, File.read(File.expand_path("../.ruby-version", __dir__)).strip

Rake::Task["rbenv:validate"].clear_actions

namespace :rbenv do
  desc "Install rbenv"
  task :install do
    on roles(:setup) do
      begin
        execute "git clone https://github.com/rbenv/rbenv.git ~/.rbenv"
      rescue SSHKit::Command::Failed
        puts "rbenv already installed, updating..."
        execute "cd ~/.rbenv && git pull"
      end
      # execute "~/.rbenv/bin/rbenv init"
      execute "mkdir -p ~/.rbenv/plugins"
      begin
        execute "git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build"
      rescue SSHKit::Command::Failed
        puts "rbenv/ruby-build plugin already installed, updating..."
        execute "cd ~/.rbenv/plugins/ruby-build && git pull"
      end

      execute "~/.rbenv/bin/rbenv install -s #{fetch(:rbenv_ruby)}"
      execute "~/.rbenv/bin/rbenv global #{fetch(:rbenv_ruby)}"
      execute "~/.rbenv/bin/rbenv local #{fetch(:rbenv_ruby)}"
      execute "export PATH=\"$HOME/.rbenv/bin:$PATH\" && eval \"$(rbenv init -)\" && ruby -v"

      execute "export PATH=\"$HOME/.rbenv/bin:$PATH\" && eval \"$(rbenv init -)\" && gem install bundler --no-document"
      if fetch(:rbenv_ruby).nil?
        puts "\nPlease uncomment the line `# set :rbenv_ruby, File.read('.ruby-version').strip` to enable capistrano rbenv"
      end
  
      execute :echo, "'export PATH=\"$HOME/.rbenv/bin:$PATH\"'", ">>", "~/.bashrc"
      execute :echo, "'eval \"$(rbenv init -)\"'", ">>", "~/.bashrc"
    end
  end
  
  task :validate do
    on release_roles(fetch(:rbenv_roles)) do |host|
      rbenv_ruby = fetch(:rbenv_ruby)
      if rbenv_ruby.nil?
        info 'rbenv: rbenv_ruby is not set; ruby version will be defined by the remote hosts via rbenv'
      end

      # don't check the rbenv_ruby_dir if :rbenv_ruby is not set (it will always fail)
      unless rbenv_ruby.nil? || (test "[ -d #{fetch(:rbenv_ruby_dir)} ]") || ARGV.include?("rbenv:install")
        warn "rbenv: #{rbenv_ruby} is not installed or not found in #{fetch(:rbenv_ruby_dir)} on #{host}"
        exit 1
      end
    end
  end
  
  desc "update ruby"
  task :update do
    on roles(:app), in: :sequence do
      execute "git -C ~/.rbenv/plugins/ruby-build pull"
      execute "RBENV_ROOT=~/.rbenv ~/.rbenv/bin/rbenv install #{fetch(:rbenv_ruby)} -s -k"
      execute "RBENV_ROOT=~/.rbenv ~/.rbenv/bin/rbenv global #{fetch(:rbenv_ruby)}"
      execute "RBENV_ROOT=~/.rbenv RBENV_VERSION=#{fetch(:rbenv_ruby)} ~/.rbenv/bin/rbenv exec gem install -N bundler"
    end
  end
end

A github-gist is provided.

Op de hoogte blijven?

Maandelijks maak ik een selectie artikelen en zorg ik voor wat extra context bij de meer technische stukken. Schrijf je hieronder in:

Mailfrequentie = 1x per maand. Je privacy wordt serieus genomen: de mailinglijst bestaat alleen op onze servers.