Adding custom modules to Nginx RPM

Skeptical baby

If you’ve installed Nginx from source, adding custom modules is easy. You simply recompile Nginx and include the module you want. But what if you’ve installed Nginx from RPM? How do you install custom module in this case? Well, you’ll need to repackage RPM and although it may sound really complicated, it’s not that big of a deal. In this example, I’ll explain how to include Frickle’s Nginx slow cache module for caching static files. This module is excellent if your static files are stored on a slow filesystem (e.g. NFS storage, slow HDD’s). To speed things up, Nginx can fetch these files once, and then cache them on a faster filesystem (e.g. local SAS/SSD). This way, Nginx will serve the files much faster and you’ll minimize the network traffic.

Prerequisites

It’s not recommended to build RPM packages as root, so you can create new user just for this purpose:

# useradd -m rpmbuilder

Of course, you’ll need Nginx source RPM. For CentOS 6 they can be found in the official nginx repo. You can install one directly from the specified URL. For example:

# rpm -Uvh http://nginx.org/packages/centos/6/SRPMS/nginx-1.0.14-1.el6.ngx.src.rpm

When you install source RPM, you’ll find in root’s homedir new directory called rpmbbuild. Since we won’t mess around logged in as root, you can copy the this directory to rpmbuilder’s homedir and chown it:

# mv /root/rpmbuild /home/rpmbuilder/ && chown -R rpmbuilder. /home/rpmbuilder/rpmbuild

When you install source RPM, you’ll find in root’s homedir new directory called rpmbbuild. Since we won’t mess around logged in as root, you can copy the this directory to rpmbuilder’s homedir and chown it:

# mv /root/rpmbuild /home/rpmbuilder/ && chown -R rpmbuilder. /home/rpmbuilder/rpmbuild

Repackaging

Log in as user rpmbuilder and you’ll be ready for some serious work. If you peek into rpmbuild directory, you’ll notice couple of subdirectories. For now, we’ll concentrate on SPECS and SOURCES. The SPECS directory contains RPM .spec file. This file provides information about the software being packaged. In SOURCES directory you’ll find source code as well as config files, init scripts etc.

Now when you’re familiar with the basics, you can download the module archive in SOURCES directory:

$ wget -P ~/rpmbuild/SOURCES/ http://labs.frickle.com/files/ngx_slowfs_cache-1.8.tar.gz

Now open the spec file in ~/rpmbuild/SPECS/nginx.spec. Near the top you’ll find the list of source files. It should look something like this:

Source0: http://nginx.org/download/%{name}-%{version}.tar.gz
Source1: logrotate
Source2: nginx.init
Source3: nginx.sysconf
Source4: nginx.conf
Source5: nginx.vh.default.conf
Source6: nginx.vh.example_ssl.conf
Source7: nginx.suse.init

To include module, simply add the name of its archive to the end of the list:

Source0: http://nginx.org/download/%{name}-%{version}.tar.gz
Source1: logrotate
Source2: nginx.init
Source3: nginx.sysconf
Source4: nginx.conf
Source5: nginx.vh.default.conf
Source6: nginx.vh.example_ssl.conf
Source7: nginx.suse.init
Source8: ngx_slowfs_cache-1.8.tar.gz

Now scroll down for couple of lines and find %prep section. You’ll find it if you search for:

%prep
%setup -q

Below these lines, you need to add command for unpacking module’s archive:

%{__tar} zxvf %{SOURCE8}
%setup -T -D -a 8

These lines define unpacking of the SOURCE8 file (i.e. ngx_slowfs_cache-1.8.tar.gz). -T switch disables the automatic unpacking of the archive. -D switch tells the %setup command not to delete the directory before unpacking and -a switch tells the %setup command to unpack only the source directive of the given number after changing directory.

Couple of lines below, you’ll find two ./configure section which look something like this:

./configure \
        --prefix=%{_sysconfdir}/nginx/ \
        --sbin-path=%{_sbindir}/nginx \
        --conf-path=%{_sysconfdir}/nginx/nginx.conf \
        --error-log-path=%{_localstatedir}/log/nginx/error.log \
        --http-log-path=%{_localstatedir}/log/nginx/access.log \
        --pid-path=%{_localstatedir}/run/nginx.pid \
        --lock-path=%{_localstatedir}/run/nginx.lock \
        --http-client-body-temp-path=%{_localstatedir}/cache/nginx/client_temp \
        --http-proxy-temp-path=%{_localstatedir}/cache/nginx/proxy_temp \
        --http-fastcgi-temp-path=%{_localstatedir}/cache/nginx/fastcgi_temp \
        --http-uwsgi-temp-path=%{_localstatedir}/cache/nginx/uwsgi_temp \
        --http-scgi-temp-path=%{_localstatedir}/cache/nginx/scgi_temp \
        --user=%{nginx_user} \
        --group=%{nginx_group} \
        --with-http_ssl_module \
        --with-http_realip_module \
        --with-http_addition_module \
        --with-http_sub_module \
        --with-http_dav_module \
        --with-http_flv_module \
        --with-http_mp4_module \
        --with-http_gzip_static_module \
        --with-http_random_index_module \
        --with-http_secure_link_module \
        --with-http_stub_status_module \
        --with-mail \
        --with-mail_ssl_module \
        --with-file-aio \
        --with-ipv6 \
        --with-debug \
        --with-cc-opt="%{optflags} $(pcre-config --cflags)"
        $*
make %{?_smp_mflags}
%{__mv} %{_builddir}/%{name}-%{version}/objs/nginx \
        %{_builddir}/%{name}-%{version}/objs/nginx.debug
./configure \
        --prefix=%{_sysconfdir}/nginx/ \
        --sbin-path=%{_sbindir}/nginx \
        --conf-path=%{_sysconfdir}/nginx/nginx.conf \
        --error-log-path=%{_localstatedir}/log/nginx/error.log \
        --http-log-path=%{_localstatedir}/log/nginx/access.log \
        --pid-path=%{_localstatedir}/run/nginx.pid \
        --lock-path=%{_localstatedir}/run/nginx.lock \
        --http-client-body-temp-path=%{_localstatedir}/cache/nginx/client_temp \
        --http-proxy-temp-path=%{_localstatedir}/cache/nginx/proxy_temp \
        --http-fastcgi-temp-path=%{_localstatedir}/cache/nginx/fastcgi_temp \
        --http-uwsgi-temp-path=%{_localstatedir}/cache/nginx/uwsgi_temp \
        --http-scgi-temp-path=%{_localstatedir}/cache/nginx/scgi_temp \
        --user=%{nginx_user} \
        --group=%{nginx_group} \
        --with-http_ssl_module \
        --with-http_realip_module \
        --with-http_addition_module \
        --with-http_sub_module \
        --with-http_dav_module \
        --with-http_flv_module \
        --with-http_mp4_module \
        --with-http_gzip_static_module \
        --with-http_random_index_module \
        --with-http_secure_link_module \
        --with-http_stub_status_module \
        --with-mail \
        --with-mail_ssl_module \
        --with-file-aio \
        --with-ipv6 \
        --with-cc-opt="%{optflags} $(pcre-config --cflags)"
        $*
make %{?_smp_mflags}

As you probably know, these are compile-time options and to include the module in the compile process, we’ll use --add-module switch (notice lines 4 and 13):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
...
        --with-debug \
        --with-cc-opt="%{optflags} $(pcre-config --cflags)" \
        --add-module=%{_builddir}/%{name}-%{version}/ngx_slowfs_cache-1.8
        $*
make %{?_smp_mflags}
%{__mv} %{_builddir}/%{name}-%{version}/objs/nginx \
        %{_builddir}/%{name}-%{version}/objs/nginx.debug
./configure \
...
        --with-ipv6 \
        --with-cc-opt="%{optflags} $(pcre-config --cflags)" \
        --add-module=%{_builddir}/%{name}-%{version}/ngx_slowfs_cache-1.8
        $*
make %{?_smp_mflags}

Building the RPM

We’ll build RPM package with the following command:

$ rpmuild -ba ~/rpmbuild/SPECS/nginx.spec

Building the RPM will take a couple of minutes and you might be requested to install additional packages - the ones Nginx depends on or the ones that are required during compile process. After the building process successfully finishes, RPM package bundled with custom module will be saved in ~/rpmbuild/RPMS/ and new source RPM will be saved in ~/rpmbuild/SRPMS/