Installing ruby with Capistrano & rbenv

An article, posted more than 3 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:

  • cap rbenv:install # installs rbenv
  • cap rbenv:update # updates rbenv & installs the desired ruby version

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.

Enjoyed this? Follow me on Mastodon or add the RSS, euh ATOM feed to your feed reader.

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.