How to Build RPM Packages

How to Build RPM Packages

Saving time and effort on installing files and scripts across multiple hosts.
— David Both
Acknowledgments
Compiled from | https://opensource.com/article/18/9/how-build-rpm-packages Author | David Both Translator | Liang Chen (Flowsnow) 🌟🌟🌟🌟 Total translated: 28.0 Contribution time: 1062 days

Saving time and effort on installing files and scripts across multiple hosts.

Since I started using Linux over 20 years ago, I have installed software on Red Hat and Fedora Linux systems using RPM-based package managers. I have used the rpm program itself, as well as yum and dnf, to install and update packages on my Linux hosts, with dnf being a close relative of yum. The yum and dnf tools are wrappers around the rpm utility that provide additional features such as finding and installing package dependencies.

Over the years, I have created many Bash scripts, some of which have separate configuration files, and I want to install these scripts on most new computers and virtual machines. This also addresses the problem of spending a lot of time installing all these packages, so I decided to automate the process by creating an RPM package that I could copy to the target host and install all these files in the appropriate locations. While the rpm tool was previously used to build RPM packages, that functionality has been removed, and a new tool has been created to build new RPMs.

When I started this project, I found little information on creating RPM packages, but I found a book called “Maximum RPM” that helped me understand it. This book is now outdated, and most of the information I found was too. It is also out of print, and used copies can cost hundreds of dollars. The online version of Maximum RPM[1] is available for free and is kept up to date. The RPM website[2] also has links to other sites with a wealth of documentation on RPM. Other information is often brief and clearly assumes you already have a lot of knowledge about the process.

Additionally, every document I found assumed that code needed to be compiled from source in a development environment. I am not a developer. I am a system administrator, and we system administrators have different needs because we do not need or should not compile code for management tasks; we should use shell scripts. So we do not have source code because it needs to be compiled into binary executables. The source code we have should also be executable.

In most cases, this project should be performed as a non-root user. RPM packages should never be built by the root user, but only by unprivileged ordinary users. I will point out which parts should be executed as root and which should be performed by non-root, unprivileged users.

Preparation

First, open a terminal session and su to the root user. Be sure to use the - option to ensure the full root environment is enabled. I do not think system administrators should use sudo to perform any administrative tasks. You can find out why in my personal blog post: Real System Administrators Don’t Use Sudo[3].

[student@testvm1 ~]$ su -
Password:
[root@testvm1 ~]#

Create a normal user student that can be used for this project and set a password for that user.

[root@testvm1 ~]# useradd -c "Student User" student
[root@testvm1 ~]# passwd student
Changing password for user student.
New password: <Enter the password>
Retype new password: <Enter the password>
passwd: all authentication tokens updated successfully.
[root@testvm1 ~]#

Building an RPM package requires the rpm-build package, which may not be installed yet. Install it as root. Note that this command will also install several dependencies. The number may vary depending on the packages already installed on the host; it installed a total of 17 packages on my test virtual machine, which is quite small.

dnf install -y rpm-build

Unless otherwise explicitly stated, the remainder of this project should be performed as the ordinary user student. Open another terminal session and switch to that user using su to perform the remaining steps. Use the following command to download my prepared development directory structure utils.tar from GitHub (LCTT Note: tarball refers to a file packaged and compressed using the tar command):

wget https://github.com/opensourceway/how-to-rpm/raw/master/utils.tar

This tarball contains all the files and Bash scripts that will be installed by the final rpm program. There is also a complete spec file you can use to build the RPM. We will go through each part of the spec file in detail.

As the ordinary user student, use your home directory as the current working directory (pwd) and extract the tarball.

[student@testvm1 ~]$ cd ; tar -xvf utils.tar

Use the tree command to verify the directory structure and files contained in ~/development as follows:

[student@testvm1 ~]$ tree development/
department/
β”œβ”€β”€ license
β”‚   β”œβ”€β”€ Copyright.and.GPL.Notice.txt
β”‚   └── GPL_LICENSE.txt
β”œβ”€β”€ scripts
β”‚   β”œβ”€β”€ create_motd
β”‚   β”œβ”€β”€ die
β”‚   β”œβ”€β”€ mymotd
β”‚   └── sysdata
└── spec
    └── utils.spec

3 directories, 7 files
[student@testvm1 ~]$

The mymotd script creates a “message of the day” data stream sent to standard output. The create_motd script runs the mymotd script and redirects the output to the /etc/motd file. This file is used to display daily messages to users logging in remotely via SSH.

The die script is my own script that wraps the kill command in some code that can find running processes matching a specified string and terminate them. It uses kill -9 to ensure the kill command is executed.

The sysdata script can display thousands of lines of data about computer hardware, the installed Linux version, all installed packages, and hard drive metadata, etc. I use it to record the state of a host at a point in time. I can refer back to it later. I used to do this to maintain records of hosts I installed for clients.

You may need to change the ownership of these files and directories to student:student. If necessary, use the following command to do so:

chown -R student:student development

Most of the files and directories in this file tree will be installed on the Fedora system by the RPM package you create during this project.

Creating the build directory structure

The rpmbuild command requires a very specific directory structure. You must create this directory structure yourself because there is no automatic way provided. Create the following directory structure in your home directory:

~ ─ rpmbuild
    β”œβ”€β”€ RPMS
    β”‚   └── noarch
    β”œβ”€β”€ SOURCES
    β”œβ”€β”€ SPECS
    └── SRPMS

We will not create the rpmbuild/RPMS/x86_64 directory because it is for architecture-specific compiled 64-bit binaries. We have shell scripts that are not architecture-specific. In fact, we also will not use the SRPMS directory, which would contain the source files for the compiler.

Checking the spec file

Each spec file has many sections, some of which may be overlooked or omitted depending on the specifics of the RPM build. This specific spec file is not an example of the minimal file required for the job, but it is a good example of a moderately complex spec file that contains files that do not need to be compiled. If compilation were needed, it would be done in the %build section, which is omitted in this spec file because it is not required.

Introduction

This is the only section in the spec file that does not have a label. It contains most of the information seen when running the command rpm -qi [Package Name]. Each piece of data is a line consisting of a label and the text data of the label value.

###############################################################################
# Spec file for utils
################################################################################
# Configured to be built by user student or other non-root user
################################################################################
#
Summary: Utility scripts for testing RPM creation
Name: utils
Version: 1.0.0
Release: 1
License: GPL
URL: http://www.both.org
Group: System
Packager: David Both
Requires: bash
Requires: screen
Requires: mc
Requires: dmidecode
BuildRoot: ~/rpmbuild/

# Build with the following syntax:
# rpmbuild --target noarch -bb utils.spec

The rpmbuild program will ignore comment lines. I always like to include comments in this section that contain the exact syntax of the rpmbuild command required to create the package.

The Summary tag is a brief description of the package.

The Name, Version, and Release tags are used to create the name of the RPM file, such as utils-1.00-1.rpm. By incrementing the release number and version number, you can create RPM packages to update older versions.

The License tag defines the license under which the package is released. I always use a variant of GPL. Specifying the license is very important to clarify the fact that the software included in the package is open source. This is also why I include the License and GPL statement in the files that will be installed.

The URL is usually a webpage for the project or project owner. In this case, it is my personal webpage.

The Group tag is interesting and is usually used for GUI applications. The value of the Group tag determines which group of icons in the application menu will contain the icon for the executable files in this package. When used with the Icon tag (which we are not using here), the Group tag allows adding icons and required information for launching programs in the application menu structure.

The Packager tag is used to specify the person or organization responsible for maintaining and creating the package.

The Requires statements define the dependencies for this RPM package. Each is a package name. If one of the specified packages does not exist, the DNF installation utility will try to find it in one of the defined repositories in /etc/yum.repos.d and install it if it exists. If DNF cannot find one or more of the required packages, it will throw an error indicating which packages are missing and terminate.

The BuildRoot line specifies the top-level directory where the rpmbuild tool will find the spec file and create temporary directories during the package build. The completed package will be stored in the noarch subdirectory we specified earlier.

The comments show the syntax of the command to build this program package, including the --target noarch option that defines the target architecture. Because these are Bash scripts, they are architecture-independent. If this option is omitted, the build will take the architecture of the CPU that is performing the build.

The rpmbuild program can target many different architectures, and using the --target option allows us to build architecture-specific packages on different architecture hosts that have different architectures than the one performing the build. So I can build a package for the i686 architecture on an x86_64 host and vice versa.

If you have your own website, change the packager’s name to your own website.

Description section (%description)

The %description section of the spec file contains the description of the RPM package. It can be short or contain a lot of information. Our %description section is quite concise.

%description
A collection of utility scripts for testing RPM creation.

Preparation section (%prep)

The %prep section is the first script executed during the build process. This script is not executed during the installation of the package.

This script is just a Bash shell script. It prepares the build directory, creates directories needed for building, and copies the appropriate files into their respective directories. This will include the complete source code required for the build as part of the compilation.

The $RPM_BUILD_ROOT directory represents the root directory of the installed system. Directories created in the $RPM_BUILD_ROOT directory are absolute paths in the real filesystem, such as /user/local/share/utils, /usr/local/bin, etc.

For our package, we do not have pre-compiled sources because all our programs are Bash scripts. Therefore, we will simply copy these scripts and other files into the directories of the installed system.

%prep
################################################################################
# Create the build tree and copy the files from the development directories    #
# into the build tree.                                                         #
################################################################################
echo "BUILDROOT = $RPM_BUILD_ROOT"
mkdir -p $RPM_BUILD_ROOT/usr/local/bin/
mkdir -p $RPM_BUILD_ROOT/usr/local/share/utils

cp /home/student/development/utils/scripts/* $RPM_BUILD_ROOT/usr/local/bin
cp /home/student/development/utils/license/* $RPM_BUILD_ROOT/usr/local/share/utils
cp /home/student/development/utils/spec/* $RPM_BUILD_ROOT/usr/local/share/utils

exit

Note that the exit statement at the end of this section is required.

Files section (%files)

The %files section of the spec file defines the files to be installed and their locations in the directory tree. It also specifies the file attributes (%attr) for each file to be installed, as well as the owner and group owner. File permissions and ownership are optional, but I recommend explicitly setting them to eliminate any possibility of these attributes being incorrect or ambiguous at install time. If a directory does not already exist, it will be created as needed during installation.

%files
%attr(0744, root, root) /usr/local/bin/*
%attr(0644, root, root) /usr/local/share/utils/*

Pre-installation (%pre)

This section is empty in the spec file for our lab project. This should place scripts that need to be executed before files in the RPM are installed.

Post-installation (%post)

This section of the spec file is another Bash script. This runs after the files are installed. This section can be anything you need or want, including creating files, running system commands, and restarting services to reinitialize them after making configuration changes. The %post script for our RPM package performs some of these tasks.

%post
################################################################################
# Set up MOTD scripts                                                          #
################################################################################
cd /etc
# Save the old MOTD if it exists
if [ -e motd ]
then
   cp motd motd.orig
fi
# If not there already, Add link to create_motd to cron.daily
cd /etc/cron.daily
if [ ! -e create_motd ]
then
   ln -s /usr/local/bin/create_motd
fi
# create the MOTD for the first time
/usr/local/bin/mymotd > /etc/motd

The comments included in this script should clarify its purpose.

Post-uninstallation (%postun)

This section contains scripts that will run after the RPM package is uninstalled. Removing the package with rpm or dnf will remove all files listed in the %files section, but it will not remove files or links created by the post-installation section, so we need to handle that in this section.

This script usually consists of cleanup tasks that simply remove files previously installed by rpm, but that rpm cannot clean up itself. For our package, it includes removing the link created by the %post script and restoring the original motd file.

%postun
# remove installed files and links
rm /etc/cron.daily/create_motd

# Restore the original MOTD if it was backed up
if [ -e /etc/motd.orig ]
then
   mv -f /etc/motd.orig /etc/motd
fi

Cleanup (%clean)

This Bash script begins cleanup after the RPM build process. The two lines in the %clean section below delete the build directories created by the rpm-build command. In many cases, additional cleanup may also be required.

%clean
rm -rf $RPM_BUILD_ROOT/usr/local/bin
rm -rf $RPM_BUILD_ROOT/usr/local/share/utils

Change log (%changelog)

This optional text section contains a list of changes to the RPM and its included files. The latest changes are recorded at the top of this section.

%changelog
* Wed Aug 29 2018 Your Name <[email protected]>
  - The original package includes several useful scripts. it is
    primarily intended to be used to illustrate the process of
    building an RPM.

Replace the data in the header line with your own name and email address.

Building the RPM

The spec file must be located in the rpmbuild directory tree in the SPECS directory. I found the easiest way is to create a link to the actual spec file in that directory so that it can be edited in the development directory without having to copy it to the SPECS directory. Set the SPECS directory as the current working directory and create the link.

cd ~/rpmbuild/SPECS/
ln -s ~/development/spec/utils.spec

Run the following command to build the RPM. If no errors occur, it will just take a little time to create the RPM.

rpmbuild --target noarch -bb utils.spec

Check the ~/rpmbuild/RPMS/noarch directory to verify that the new RPM exists.

[student@testvm1 ~]$ cd rpmbuild/RPMS/noarch/
[student@testvm1 noarch]$ ll
total 24
-rw-rw-r--. 1 student student 24364 Aug 30 10:00 utils-1.0.0-1.noarch.rpm
[student@testvm1 noarch]$

Testing the RPM

Install the RPM as the root user to verify that it installs correctly and that the files are placed in the correct directories. The exact name of the RPM will depend on the values of the labels in the introduction section, but if you used the values from the example, the RPM name will be as shown in the example command below:

[root@testvm1 ~]# cd /home/student/rpmbuild/RPMS/noarch/
[root@testvm1 noarch]# ll
total 24
-rw-rw-r--. 1 student student 24364 Aug 30 10:00 utils-1.0.0-1.noarch.rpm
[root@testvm1 noarch]# rpm -ivh utils-1.0.0-1.noarch.rpm
Preparing...                          ################################# [100%]
Updating / installing...
   1:utils-1.0.0-1                    ################################# [100%]

Check /usr/local/bin to ensure the new files exist. You should also verify that the create_motd link in /etc/cron.daily has been created.

Use the rpm -q --changelog utils command to view the change log. Use the rpm -ql utils command (the ql is lowercase L) to see the files installed by the package.

[root@testvm1 noarch]# rpm -q --changelog utils
* Wed Aug 29 2018 Your Name <[email protected]>
- The original package includes several useful scripts. it is
    primarily intended to be used to illustrate the process of
    building an RPM.

[root@testvm1 noarch]# rpm -ql utils
/usr/local/bin/create_motd
/usr/local/bin/die
/usr/local/bin/mymotd
/usr/local/bin/sysdata
/usr/local/share/utils/Copyright.and.GPL.Notice.txt
/usr/local/share/utils/GPL_LICENSE.txt
/usr/local/share/utils/utils.spec
[root@testvm1 noarch]#

Remove the package.

rpm -e utils

Experiment

Now, you will change the spec file to require a non-existent package. This will simulate an unsatisfied dependency. Immediately add the following line under the existing Requires line:

Requires: badrequire

Build the package and try to install it. What message does it show?

We use the rpm command to install and remove the utils package. Try using yum or dnf to install the package. You must be in the same directory as the package or specify the full path to the package for it to work.

Conclusion

In this overview of the basics of creating RPM packages, we did not cover a lot of tags and many sections. The resources listed below can provide more information. Building RPM packages is not difficult; you just need the right information. I hope this helps youβ€” it took me months to figure it out myself.

We did not cover building from source code, but if you are a developer, that should be a simple step from here.

Creating RPM packages is another great way to be a lazy system administrator, saving time and effort. It provides a simple method for distributing and installing those scripts and other files that we as system administrators need to install on many hosts.

References

β—ˆ Edward C. Baily, “Maximum RPM”, published by Sams in 2000, ISBN 0-672-31105-4
β—ˆ Edward C. Baily, “Maximum RPM[1]“, updated online version
β—ˆ RPM Documentation[4]: This webpage lists most of the available online documentation for RPM. It includes links to many other sites and information about RPM.

via: https://opensource.com/article/18/9/how-build-rpm-packages

Author: David Both[6] Topic: lujun9972 Translator: Flowsnow Proofreader: wxy

This article is a collaborative translation by LCTT, proudly presented by Linux China

Leave a Comment