Building, installing, and configuring your own Ruby RPM for CentOS
Published: July 23, 2016
While Ruby managers like rbenv
and rvm
are great, I personally believe they should be only used for development purposes and not in a production environment. However, the packages that Linux distros provide are usually outdated. Luckily, there's another option: roll your own Ruby package. This allows you to use just about any version of Ruby you'd want AND it be handled by the distro's package manager. CentOS is my preferred distro to use for production, so I'll be going over the process of creating a RPM for Ruby 2.3 along with installing and configuring it on a production environment.
These instructions will work for both CentOS 6 and 7 and, in theory, should work for Red Hat Enterprise Linux.
Preperation
I prefer to create my package on a separate system from production. To accomplish this, I have a VirtualBox VM setup with the same distro version production uses (this is a requirement). In my case, I have a VM with CentOS 7 with all package updates applied. You can have either a minimal install (no GUI) or with a Desktop Environment such as GNOME. All commands (both in the VM and production) are done by a non-root user with sudo
access.
The majority of the packages needed to create a RPM come from the "Developer Tools" group. The group includes programs such as gcc
, rpm-build
, and redhat-rpm-config
among others. Not all are needed, but this is a good way to cover all your bases for basic package building in one go. All of them can be installed with the following command:
$ sudo yum groupinstall 'Development Tools'
Seting Up the Build Environment
Run these two commands to set up the needed directories and default configuration for RPM making:
$ mkdir -p ~/rpmbuild/{BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS} $ echo '%_topdir %(echo $HOME)/rpmbuild' > ~/.rpmmacros
The first command creates a series of directories in ~/rpmbuild/
. The three directories we are most interested in are RPMS
, SOURCES
, and SPECS
:
SOURCES
- This directory will hold the compressed Ruby source code.
SPECS
- In here, we'll put a file that describes how the package is to be built. A spec file has metadata about the package along with instructions on how to compile Ruby.
RPMS
- When the package is built, the resulting .rpm
file is placed in here.
The second command creates a configuration "dot file" for rpmbuild
to tell it that all package-related files, by default, are all in ~/rpmbuild/
.
Get Ruby Source Code and .spec File
Now we need the actual source code ruby-lang.org and put it in the SOURCES
directory. This can be done with one command (Replace "2.3.1" with the version of Ruby you want to build):
$ cd ~/rpmbuild/SOURCES && wget ftp://ftp.ruby-lang.org/pub/ruby/ruby-2.3.1.tar.gz
Next, we need a spec file. You're kind of on your own at this point as there is no official spec file people use. The one I use is created by forcefeed on GitHub. The repo can be viewed here which has a spec file for Ruby versions (by switching tags) all the way back to 2.1.0. I'm going to use their latest spec file for Ruby 2.3.1.
Let's download it to our SOURCES
directory. After downloading, you should view it in your favorite text editor and verify it looks on the up and up. You should never blindly download and use a spec file unless you understand what it's doing.
$ cd ~/rpmbuild/SPECS && wget https://raw.githubusercontent.com/feedforce/ruby-rpm/2.3.1/ruby.spec
I'm not going to go over all the details of the file, but I'll point out a couple key areas. If you'd like to follow along by just viewing the file, you can do so here.
%define rubyver 2.3.1 # This is the name of the package. Name: ruby ... # These are packages that are required to be installed when the package installs. # Yum handles this for us. Requires: readline ncurses gdbm glibc openssl libyaml libffi zlib # These packages are needed to create the RPM (In our case, to compile Ruby). # Building will fail if they are not all installed BuildRequires: readline-devel ncurses-devel gdbm-devel glibc-devel gcc openssl-devel make libyaml-devel libffi-devel zlib-devel # This is a reference for the package to tell it where the source code came from. # I use this to know where to grab the source code to put in the SOURCES directory Source0: ftp://ftp.ruby-lang.org/pub/ruby/ruby-%{rubyver}.tar.gz ... # This tells yum what packages and functions our RPM will provide. Provides: ruby(abi) = 2.3 Provides: ruby-irb Provides: ruby-rdoc Provides: ruby-libs Provides: ruby-devel Provides: rubygems ... # These are the commands, switches, and settings used to compile Ruby. # For example, our Ruby won't have any of the tk hooks baked in (--without-tk). %configure \ --enable-shared \ --disable-rpath \ --without-X11 \ --without-tk \ --includedir=%{_includedir}/ruby \ --libdir=%{_libdir} make %{?_smp_mflags} %install make install DESTDIR=$RPM_BUILD_ROOT ... # Rest of the file
Build the Package
Ok, let's try building. We use rpmbuild
to create the package.
$ rpmbuild -ba ~/rpmbuild/SPECS/ruby.spec
Hopefully, if you've been following along, this command should fail:
error: Failed build dependencies: readline-devel is needed by ruby-2.3.1-1.el7.centos.x86_64 ncurses-devel is needed by ruby-2.3.1-1.el7.centos.x86_64 gdbm-devel is needed by ruby-2.3.1-1.el7.centos.x86_64 openssl-devel is needed by ruby-2.3.1-1.el7.centos.x86_64 libyaml-devel is needed by ruby-2.3.1-1.el7.centos.x86_64 libffi-devel is needed by ruby-2.3.1-1.el7.centos.x86_64 zlib-devel is needed by ruby-2.3.1-1.el7.centos.x86_64
This is rpmbuild
letting us know that there's packages we specified as needed to compile Ruby that aren't installed. So, let's install them and try building again.
$ sudo yum install -y readline-devel ncurses-devel gdbm-devel openssl-devel libyaml-devel libffi-devel zlib-devel $ rpmbuild -ba ~/rpmbuild/SPECS/ruby.spec
If all goes well, the package should build without a hitch. You'll see a bunch of blonds, brunettes, and redheads scroll through in your terminal while the Ruby compiling process happens. It's normal and things should complete eventually.
When it's all done, you should have a complete rpm file in ~/rpmbuild/RPMS
. Depending on your version of CentOS, the names will vary slightly...
CentOS 6: ruby-2.3.1-1.el6.x86_64.rpm
CentOS 7: ruby-2.3.1-1.el7.centos.x86_64.rpm
Install on Production
Now that you have a rpm file, we can install it on the production server. I use scp
to push the file up from my build VM to the server, but any standard file transfer process will work.
scp ~/rpmbuild/RPMS/ruby-2.3.1-1.el7.centos.x86_64.rpm myserver:~/ruby-2.3.1-1.el7.centos.x86_64.rpm
SSH into production. To have yum
install the package, we use the --nogpgcheck
as we did not sign our package.
$ sudo yum --nogpgcheck install ~/ruby-2.3.1-1.el7.centos.x86_64.rpm
If installation is successful, verify both the ruby
and gem
binaries are available and the versions are what you expect them to be.
$ ruby -v ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-linux] $ gem -v 2.5.1
Hooray! We got Ruby installed and ready to go! We're done... right?
Allow Gems to Install
Our package provides irb
, ruby
, and gem
, but we don't have bundle
available. This is because, well, bundler is a gem! So we should probably install it...
$ gem install bundler Fetching: bundler-1.12.5.gem (100%) ERROR: While executing gem ... (Gem::FilePermissionError) You don't have write permissions for the /usr/lib64/ruby/gems/2.3.0 directory.
Huh... we can't install bundler... in fact, we can't install any gems! This is due to how Ruby is installed by default. The default path for gems is in /usr/lib64/ruby/gems/2.3.0/
which is owned by root. We could use sudo
to allow gems to install... but we really shouldn't need root access to install them.
Luckily, there's another way. We're going to tell Ruby to install gems in the home directory of the user. Each user on the system will be able to install gems for themselves because they will be installed to directories where they have write permissions. We can do this globally by creating a file at /etc/profile.d/ruby.sh
. All files in /etc/profile.d/
are processed for all users to set up their environment. Our ruby.sh
file will contain the following two lines:
export PATH=$HOME/.gem/ruby/2.3.0/bin:$PATH export GEM_HOME=$HOME/.gem/ruby/2.3.0
These two lines do the following: Sets ~/.gem/ruby/2.3.0/
as the directory where all gems for the user will be installed and adds ~/.gem/ruby/2.3.0/bin/
to PATH
which is where binaries (such as bundle
, rake
, etc) will go. Now, logout and log back into production to get the new environment set. With all that done, gems should install no problem for all users.
$ gem install bundler Successfully installed bundler-1.12.5 Parsing documentation for bundler-1.12.5 Installing ri documentation for bundler-1.12.5 Done installing documentation for bundler after 3 seconds 1 gem installed
Success! A Ruby version we want is installed as system Ruby and gems can be installed. Congratulations. You've successfully built your first RPM and installed it on a production environment. High five