Unlocking the Potential of RPM: Building Package from Script on CentOS

Introduction

If you’re a CentOS user looking to distribute and install and manage your software efficiently, RPM (Red Hat Package Manager) is a powerful tool you should embrace. RPM allows you to package your applications, libraries, or scripts into a format that can be easily installed, upgraded, and managed on CentOS systems. In this article I will explore how to build an RPM package using the rpmbuild tool from your own Bash script and will do it on CentOS 9 (should work same way if you have Centos 7 or 8). This will empower you to distribute your software seamlessly across CentOS environments.

Before we get some practice I would like to describe how the structure of the folder should look like to build the RPM package properly and how we can simplify generation of this skeleton.

First of all let’s see on the directory skeleton below.

There are three basic ingredients required to assemble the source and binary packages:

  • A spec file that must be written that describes the package and how to patch and build it, dependency information etc.
  • A tarball file containing all source code, makefiles, default configuration files, documentation etc.
  • Any patch files needed to be applied to the upstream source encapsulated in the tarball (optional).

Preparation: Create the RPM Build Structure

Before starting creation of our own RPM package I will install two add-ons that are very helpful:

  • rpmdevtools – package that contains rpmdev-setuptree tool which creates a skeleton required for build,
  • rpmlint – to validate and check if our skeleton contains all necessary files, structure of files is good and file syntax correct.

So let’s install both of them:

$ dnf install -y rpmdevtools rpmlint

To build an RPM package, we need to organize our files in a specific directory structure. Inside this directory, the following subdirectories are mandatory: BUILD, RPMS, SOURCES, SPECS, and SRPMS. To automatically create them I will use rpmdev-setuptree tool that will do it for me:

$ rpmdev-setuptree

You should have folder structure similar to mine:

Step 1: Create source script

To create the RPM package we need to have a source. This could be actually anything, an application, a single script or set of scripts, or any other executable code. For my demonstration purpose I will focus on simple Bash script called geolocation that will use the website https://ipinfo.io/ to identify the geographical location of the public IP which will be provided as input by user (including IP validation). Execution of script is looking like this:

Here is the source code of the Bash script:

#!/bin/bash
# Author: Marcin Kujawski
# Description: Script defines geographical location of public IP address provided by user as input parameter
# Version: 1.0
s ipinfo.io/$ip/hostname`

echo -e "\nGEO-LOCATION DETAILS FOR IP '$ip':\n\n"
echo -e "\tCountry:\t$COUNTRY"
echo -e "\tRegion:\t\t$REGION"
echo -e "\tCity:\t\t$CITY"
echo -e "\tLatitude:\t$LATITUDE"
echo -e "\tLongitude:\t$LONGITUDE"
echo -e "\n\tHostname:\t$HOSTNAME\n"

exit 0

Step 2: Writing the Spec File

The spec file is a crucial component of an RPM package. It defines how the package should be built and installed. I created a new file called geolocate-1.0.spec in the SPECS directory. Inside this file, specify details such as the package name, version, release, dependencies, and instructions for installation.

My .spec file looks as follows:

Name:           geolocate
Version:        1.0
Release:        0
Summary:        Get your geolocation details
Packager:       Marcin Kujawski
Group:          Application/Other
License:        GPL
URL:            https://marcinkujawski.pl/
Source0:        %{name}-%{version}.tar.gz
BuildArch:      noarch

%description
Script defines geographical location of public IP address provided by user as input parameter.

%prep
%setup -q


%build

%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/local/sbin
cp scripts/* $RPM_BUILD_ROOT/usr/local/sbin/

%clean
rm -rf $RPM_BUILD_ROOT

%files
%defattr(-,root,root,-)
%dir /usr/local/sbin
/usr/local/sbin/geolocate

%doc

%changelog
* Mon May 10 2021 Marcin Kujawski
- release 1.0 - initial release
Name:           geolocate
Version:        1.0
Release:        0
Summary:        Get your geolocation details
Packager:       Marcin Kujawski
Group:          Application/Other
License:        GPL
URL:            https://marcinkujawski.pl/
Source0:        %{name}-%{version}.tar.gz
BuildArch:      noarch

%description
Script defines geographical location of public IP address provided by user as input parameter.

%prep
%setup -q


%build

%install
rm -rf $RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/usr/local/sbin
cp scripts/* $RPM_BUILD_ROOT/usr/local/sbin/

%clean
rm -rf $RPM_BUILD_ROOT

%files
%defattr(-,root,root,-)
%dir /usr/local/sbin
/usr/local/sbin/geolocate

%doc

%changelog
* Mon May 10 2021 Marcin Kujawski
- release 1.0 - initial release

Step 3: Placing the Source Script

Next thing is to copy my geolocate script into the SOURCES directory within the RPM build structure. This is where the source code and files for your package reside.

$ mkdir -p rpmbuild/SOURCES/geolocate-1.0/scripts
$ cp geolocate rpmbuild/SOURCES/geolocate-1.0/scripts
$ chmod +x rpmbuild/SOURCES/geolocate-1.0/scripts/*

Step 4: Generate Tarball Source File

As last requirement – the tarball file need to be created. Navigate into the SOURCES directory within the RPM build structure and create it with tar command:

$ cd rpmbuild/SOURCES/ 
$ tar -czf geolocate-1.0.tar.gz geolocate-1.0

Finally you should have same rpmbuild structure as mine:

Step 5: Validate Spec File

Last step before RPM build is to validate the .spec file. To do this I use tool installed at the beginning called rpmlint:

rpmlint rpmbuild/SPECS/geolocate-1.0.spec 

You should receive no errors. Warnings can be ignored, as you see in my example I have two warnings, but this is basically ok, if no error are identified we are ready to go.

Step 6: Build the RPM Package

Navigate to the SPECS directory and execute the command rpmbuild to initiate the RPM package build process. The rpmbuild tool will compile, package, and generate the RPM file within the RPMS directory.

$ rpmbuild --bb rpmbuild/SPECS/geolocate-1.0.spec

After all you should have a .noarch.rpm file created within RPMS directory.

This means that we are ready to test our own package.

Step 7: Installing the RPM Package

Congratulations! You have successfully built the RPM package. To install it on a CentOS system, navigate to the RPMS directory and run the command:

$ cd rpmbuild/RPMS/noarch
$ dnf install geolocate-1.0-0.noarch.rpm

This will install your package along with any necessary dependencies. Confirm the installation and test if that package is doing the same as our script did. To test it, I navigate to /tmp directory and check if I can execute it. Output is the same as output of my Bash script. Here is the result:

So by this we have a confirmation that it’s working as expected. Of course having RPM installed gives us all advantages of Package Management, such as upgrades, uninstallation and querying.

To check the package information we can execute the command:

$ rpm -ql geolocate

And we should observe that all information that we put into .spec file is listed here:

Conclusion

By following I have unlocked the potential of RPM packaging using the rpmbuild tool on CentOS. With this knowledge, you can package and distribute your software efficiently, ensuring seamless installation and management across CentOS family based systems. Remember to adapt the steps outlined in this guide to suit your specific software and requirements.

I hope this post provides valuable insights into building RPM packages with the rpmbuild tool. Good luck with your RPM packaging endeavors! Go forth and unleash the full potential of RPM packaging on your CentOS machine!