© David Both 2020
D. BothUsing and Administering Linux: Volume 3https://doi.org/10.1007/978-1-4842-5485-1_17

17. Advanced Package Management

David Both1 
(1)
Raleigh, NC, USA
 

Objectives

In this chapter, you will learn
  • To prepare an rpmbuild directory structure to contain RPMs for different architectures

  • To generate an RPM specification (spec) file that defines the structure of the generated RPM package and the files and embedded scripts to be included in it

  • To build an RPM package that contains user generated scripts and configuration files

Introduction

I have used RPM-based package managers to install software on Red Hat, CentOS, and Fedora since I started using Linux over 20 years ago. From the RPM program itself, to YUM, and then DNF, which is a close descendant of YUM, I have used these tools to install and update packages on my Linux hosts. But that was all about installing packages. The YUM and DNF tools are wrappers around the RPM utility and provide additional functionality such as the ability to find and install package dependencies.

Over the years I have created a number of Bash scripts, some of which have separate configuration files, that I like to install on most of my new computers and virtual machines. It finally reached the point that it took a great deal of time to install all of these packages. I decided to automate that process by creating an RPM package that I could copy to the target hosts and install all of these files in their proper locations. Although the RPM tool was formerly used to build RPM packages, that function of RPM was removed and moved a new tool; RPMBUILD was created to build new RPMs.

When I started this project, I found very little information about creating RPM packages but managed to find a book, Maximum RPM, that enabled me to figure it out. That hard-copy book is now somewhat out of date as is the vast majority of information I have found. It is also out of print, and used copies go for hundreds of dollars. The online version of Maximum RPM1 is available at no charge and is apparently being kept up to date. The RPM web site also has links to other web sites that have a lot of documentation about RPM. What other information there is tends to be very brief and apparently assumes that you already have a good deal of knowledge about the process.

Another good resource for the RPM tools that I have found is at RPM.org. This web page lists most of the available online documentation for RPM. It is mostly link to other web sites and information about RPM itself. I especially like the Fedora RPM Guide.

All of the documents I found assumes that the code needs to be compiled from sources as in a development environment. I am not a developer, I am a SysAdmin, and we SysAdmins have different needs because we don’t – well shouldn’t – be compiling code to use for administrative tasks; we should be using shell scripts. So we have no source code in the sense that it is something that needs to be compiled into binary executables. What we have is source code that is also the executable.

For the most part, the experiments in this chapter should be performed as the non-root user, student. RPMs should never be built by root but only by non-privileged users.

Preparation

There are some things we need to do in order to prepare for building RPMs. This includes installing the rpmbuild software, downloading the tarball that contains the files we will be including in the RPM as well as the spec file used to build the RPM, and creating the build directory structure.

Experiment 17-1

Start this experiment as the root user – one of few exceptions – on StudentVM1. We will install the rpm-build and rpmdevtools packages as they are most likely not already installed. Install it now as root.
[root@studentvm1 ~]# dnf install -y rpm-build rpmdevtools
Now, as the student user, make your home directory (~) the PWD and download2 a tarball that I have prepared of a development directory structure, utils.tar, using the following command.
[student@studentvm1 ~]# wget https://github.com/Apress/using-and-administering-linux-volume-3/raw/master/utils.tar

This tarball includes all of the files and BASH scripts that will be installed by the final RPM. There is also a complete spec file which you can use to build the RPM. We will go into detail about each section of the spec file. We installed the RPM created from this tarball in Chapter 12 of Volume 1.

As user student, using your home directory as your present working directory (PWD), untar the tarball.
[student@studentvm1 ~]$ tar -xvf utils.tar
./
./development/
./development/scripts/
./development/scripts/create_motd
./development/scripts/die
./development/scripts/mymotd
./development/scripts/sysdata
./development/spec/
./development/spec/utils.spec
./development/license/
./development/license/Copyright.and.GPL.Notice.txt
./development/license/GPL_LICENSE.txt
[student@studentvm1 ~]$
Verify that the directory structure of ~/development and the contained files looks like the following output.
[student@studentvm1 ~]$ tree development/
development/
├── license
│   ├── Copyright.and.GPL.Notice.txt
│   └── GPL_LICENSE.txt
├── scripts
│   ├── create_motd
│   ├── die
│   ├── mymotd
│   └── sysdata
└── spec
    └── utils.spec
3 directories, 7 files
[student@studentvm1 ~]$

Change ownership of these files and directories to student.student.

The mymotd script creates a “Message Of The Day” data stream that is sent to STDOUT. The create_motd script runs the mymotd scripts and redirects the output to the /etc/motd file. This file is used to display a daily message to users who login remotely using SSH.

The die script is my own script that wraps the kill command in a bit of code that can find running programs that match a specified string and kill them. It uses kill -9 to ensure that they cannot ignore the kill message. The sysdata script can spew tens of thousands of lines of data about your computer hardware, the installed version of Linux, all installed packages, and the metadata of your hard drives. I use it to document the state of a host at a point in time. I can later use that information for reference. I used to do this to maintain a record of hosts that I installed for customers.

Most of the files and directories in this tree will be installed on Fedora systems by the RPM you create during this project. Some are used to build the RPM.

Now let’s create the build directory structure. The rpmbuild command requires a very specific directory structure. You must create this directory structure yourself because no automated way is provided to do it.

Experiment 17-2

As the student user on StudentVM1, create the following directory structure in your home directory.
~ ─ rpmbuild
    ├── RPMS
    │   └── noarch
    ├── SOURCES
    ├── SPECS
    └── SRPMS
The ~/rpmbuild/RPMS directory contains subdirectories for the finished RPMs based on their architecture. Here is one way to create these directories
[student@studentvm1 ~]$ mkdir rpmbuild
[student@studentvm1 ~]$ cd rpmbuild/
[student@studentvm1 rpmbuild]$ mkdir -p RPMS/noarch SOURCES SPECS SRPMS
[student@studentvm1 rpmbuild]$ tree
.
├── RPMS
│   └── noarch
├── SOURCES
├── SPECS
└── SRPMS
5 directories, 0 files
Here is another way to create these directories. So we can see how this works, first delete the directory tree ~/rpmbuild. Then use the following command, which is part of the rpmdevtools package, to create the ~rpmbuild tree.
[student@studentvm1 ~]$  rpmdev-setuptree
[student@studentvm1 ~]$ tree rpmbuild/
rpmbuild/
├── BUILD
├── RPMS
├── SOURCES
├── SPECS
└── SRPMS
5 directories, 0 files
[student@studentvm1 ~]$

Note that the RPM build process will create the rest of the required directories.

We did not create architecture-specific directories such as the ~/rpmbuild/RPMS/X86_64 directory because our RPM is not architecture specific. We have shell scripts that are not specific to any CPU architecture. In reality we won’t be using the SRPMS directory, either, which would contain source files for the compiler.

Examining the spec file

Each spec file has a number of sections, some of which may be ignored or omitted, depending upon the specific circumstances of the RPM build. This particular spec file is not an example of a minimal file required to work, but it is a good example of a moderately complex spec file that packages files that do not need to be compiled. If a compile were required, it would be performed in the %build section which is omitted from this spec file because it is not required.

As you proceed through this section, change the provided spec file as suggested to be specific for you.

Preamble

This is the only section of the spec file that does not have a label. It consists of much of the information you see when the command rpm -qi [Package Name] is run. Each datum is a single line which consists of a tag which identifies it and text data for the value of the tag.
###########################################################################
# 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

Comment lines are ignored by the rpmbuild program. I always like to add a comment to this section that contains the exact syntax of the rpmbuild command required to create the package. The Summary tag is a short description of the package. The Name, Version, and Release tags are used to create the name of the RPM file, as in utils-1.00-1.rpm. Incrementing the release and version numbers enables creating RPMs that can be used to update older ones.

The License tag defines the license under which the package is released. I always use a variation of the GPL. Specifying the license is important in order to prevent confusion about the fact that the software contained in the package is open source. This is also why I included the license and GPL statement in the files that will be installed.

The URL is usually the web page of the project or project owner. In this case, it is my personal web page. If you have a web page, you can change this to that URL.

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 Applications menu will contain the icon for the executable in this package. Used in conjunction with the Icon tag, which we are not using here, the Group tag allows adding the icon and the required information to launch a program into the Applications 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. Each is a package name. If one of the specified packages is not present, the dnf installation utility will try to locate it in one of the defined repositories defined in /etc/yum.repos.d and install it if it exists. If dnf cannot find one or more of the required packages, it will trow an error indicating which packages are missing and terminate.

The BuildRoot line specifies the top-level directory in which the rpmbuild tool will find the spec file and in which it will create temporary directories while it builds the package. The finished package will be stored in the noarch subdirectory that we specified earlier. The comment showing the command syntax used to build this package includes the option --target noarch which defines the target architecture. Because these are Bash scripts, they are not associated with a specific CPU architecture. If this option were omitted, the build would be targeted to the architecture of the CPU on which the build is being performed.

The rpmbuild program can target many different architectures and using the --target option allows us to build architecture specific packages on a host with a different architecture from the one on which the build is performed. So I could build a package intended for use on an i686 architecture on an x86_64 host, and vice versa.

Change the packager name to yours and the URL to your own web site, if you have one.

%description

The %description section of the spec file contains a description of the RPM package. It can be very short or can contain many lines of information. Our %description section is rather terse.
%description
A collection of utility scripts for testing RPM creation.

%prep

The %prep section is a script that is the first one 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, creating directories used for the build as required and copying the appropriate files into their respective directories. This would include the sources required for a complete compile as part of the build.

The $RPM_BUILD_ROOT directory represents the root directory of an installed system. The directories created in the $RPM_BUILD_ROOT directory are fully qualified paths such as /user/local/share/utils, /usr/local/bin and so on, in a live filesystem.

In the case of our package, we have no pre-compile sources because all of our programs are BASH scripts. So we simply copy those scripts and other files into the directories where they belong in 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

This section of the spec file defines the files to be installed and their locations in the directory tree. It also specifies the file attributes and the owner and group owner for each file to be installed. The file permissions and ownerships are optional, but I recommend that they be explicitly set to eliminate any chance for those attributes to be incorrect or ambiguous when installed. Directories are created as required during the installation if they do not already exist.
%files
%attr(0744, root, root) /usr/local/bin/∗
%attr(0644, root, root) /usr/local/share/utils/∗

%pre

This section is empty in our lab project's spec file. This would be the place to put any scripts that are required to run during installation of the RPM but prior to the installation of the files.

%post

This section of the spec file is another Bash script. This one runs after the installation of files. This section can be pretty much anything you need or want it to be, 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 those 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 make its purpose clear.

%postun

This section contains a script that would be run after the RPM package is uninstalled. Using rpm or dnf to remove a package removes all of the files listed in the %files section, but it does not remove files or links created by the %post section, so we need to handle that in this section.

This script usually consists of cleanup tasks that simply erasing the files previously installed by the RPM cannot accomplish. In the case of our package, it includes removing the link created by the %post script and restoring the saved original of the 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

%clean

This BASH script performs cleanup after the RPM build process. The two lines in the %clean section below remove 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

%changelog

This optional text section contains a list of changes to the RPM and files it contains. The newest 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 in the SPECS directory of the rpmbuild tree. I find it easiest to create a link to the actual spec file in that directory so that it can be edited in the development directory and there is no need to copy it to the SPEC directory.

Experiment 17-3

As the student user, make the SPECS directory the PWD and then create a link to the spec file.
[student@studentvm1 ~]# cd ~/rpmbuild/SPECS/
[student@studentvm1 ~]# ln -s ~/development/spec/utils.spec ; ll
total 0
lrwxrwxrwx 1 student student 41 Aug 31 11:43 utils.spec -> /home/student/development/spec/utils.spec
[student@studentvm1 SPECS]$
Run the following command to build the RPM. It should only take a moment to create the RPM if no errors occur.
[student@studentvm1 ~]# rpmbuild --target noarch -bb utils.spec
Building target platforms: noarch
Building for target noarch
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.QaPvYe
+ umask 022
+ cd /home/student/rpmbuild/BUILD
+ echo 'BUILDROOT = /home/student/rpmbuild/BUILDROOT/utils-1.0.0-1.noarch'
BUILDROOT = /home/student/rpmbuild/BUILDROOT/utils-1.0.0-1.noarch
+ mkdir -p /home/student/rpmbuild/BUILDROOT/utils-1.0.0-1.noarch/usr/local/bin/
+ mkdir -p /home/student/rpmbuild/BUILDROOT/utils-1.0.0-1.noarch/usr/local/share/utils
+ cp /home/student/development/scripts/create_motd /home/student/development/scripts/die /home/student/development/scripts/mymotd /home/student/development/scripts/sysdata /home/student/rpmbuild/BUILDROOT/utils-1.0.0-1.noarch/usr/local/bin
+ cp /home/student/development/license/Copyright.and.GPL.Notice.txt /home/student/development/license/GPL_LICENSE.txt /home/student/rpmbuild/BUILDROOT/utils-1.0.0-1.noarch/usr/local/share/utils
+ cp /home/student/development/spec/utils.spec /home/student/rpmbuild/BUILDROOT/utils-1.0.0-1.noarch/usr/local/share/utils
+ exit
Processing files: utils-1.0.0-1.noarch
Provides: utils = 1.0.0-1
Requires(interp): /bin/sh /bin/sh /bin/sh
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires(pre): /bin/sh
Requires(post): /bin/sh
Requires(postun): /bin/sh
Requires: /bin/bash /bin/sh
Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/student/rpmbuild/BUILDROOT/utils-1.0.0-1.noarch
Wrote: /home/student/rpmbuild/RPMS/noarch/utils-1.0.0-1.noarch.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.9fGPUM
+ umask 022
+ cd /home/student/rpmbuild/BUILD
+ rm -rf /home/student/rpmbuild/BUILDROOT/utils-1.0.0-1.noarch/usr/local/bin
+ rm -rf /home/student/rpmbuild/BUILDROOT/utils-1.0.0-1.noarch/usr/local/share/utils
+ exit 0
[student@studentvm1 SPECS]$
Check in the ~/rpmbuild/RPMS/noarch directory to verify that the new RPM exists there.
[student@studentvm1 SPECS]$ cd ~/rpmbuild/RPMS/noarch/ ; ll
total 24
-rw-rw-r-- 1 student student 24372 Aug 31 11:45 utils-1.0.0-1.noarch.rpm
[student@studentvm1 noarch]$
Now let’s look at the contents of our ~/rpmbuild directory.
[student@studentvm1 ~]$ tree ~/rpmbuild/
/home/student/rpmbuild/
├── BUILD
├── BUILDROOT
│   └── utils-1.0.0-1.noarch
│       └── usr
│           └── local
│               └── share
├── RPMS
│   └── noarch
│       └── utils-1.0.0-1.noarch.rpm
├── SOURCES
├── SPECS
│   └── utils.spec -> /home/student/development/spec/utils.spec
└── SRPMS

Testing the RPM

As root, install the RPM to verify that it installs correctly and that the files are installed in the correct directories. The exact name of the RPM will depend upon the values you used for the tags in the Preamble section, but if you used the ones in the sample, the RPM name will be as shown in the sample command below.

Experiment 17-4

Perform this experiment as the root user. We will use RPM to install the package and not DNF.
[root@studentvm1 ~]# cd /home/student/rpmbuild/RPMS/noarch/ ; ll
total 24
-rw-rw-r-- 1 student student 24372 Aug 31 11:45 utils-1.0.0-1.noarch.rpm
[root@studentvm1 noarch]# rpm -ivh utils-1.0.0-1.noarch.rpm
Verifying...                        ################################# [100%]
Preparing...                        ################################# [100%]
Updating / installing...
   1:utils-1.0.0-1                  ################################# [100%]
[root@studentvm1 noarch]#

The -i option specifies an install. A -u would indicate performing an upgrade of a newer package over an older one. The -v means verbose and -h means we want to display the progress hash marks.

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

Use the following command to view the changelog. View the files installed by the package using the rpm -ql utils command. (That is a lowercase L in ql.)
[root@studentvm1 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@studentvm1 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@studentvm1 noarch]#

Experimenting

Let’s do some experimenting.

Experiment 17-5

As the root user, remove the installed package. The -e option stands for erase.
[root@studentvm1 noarch]# rpm -e utils
Now you will change the spec file to require a package that does not exist. This will simulate a dependency that cannot be met. Add the following line immediately under the existing Requires line.
Requires: badrequire

Build the package and attempt to install it. What message is displayed?

We used the rpm command to install and delete the utils package. Try installing the package with DNF. You must be in the same directory as the package or specify the full path to the package for this to work.

Rebuilding a corrupted RPM database

I have occasionally encountered errors that indicate the RPM database is corrupted when upgrading, updating, or installing RPMs. This can occur for various reasons, but I have found that I can cause it by breaking out of a running task such as an update or installation.

The RPM database can be easily rebuilt.

Experiment 17-6

Perform this experiment as the root user. We will rebuild the RPM database even though there is nothing wrong with it. We use the -vv option to display a lot of verbose output that enables you to see what is happening.
[root@studentvm1 noarch]# rpm --rebuilddb -vv

Rerun the command without the -vv option.

Chapter summary

There are many tags and a couple sections that we did not cover in this look at the basics of creating an RPM package. Building RPM packages is not difficult, one just needs the right information. I hope this chapter helps you because it took me months to figure things out on my own.

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

Creating RPM packages is another good way to be a lazy SysAdmin and save time and effort. It provides an easy method for distributing and installing the scripts and other files that we as SysAdmins need to install on many hosts.

Exercises

Perform the following exercises to complete this chapter.
  1. 1.

    Try building the utils package using X86_64 as the target architecture. Remove the existing version of the package, install the X86_64 version, and test the programs. Does this cause a problem? Why? Remove this version.

     
  2. 2.

    Create a short script of your own and include it in the RPM. Increment the release number and build the revised RPM.

     
  3. 3.

    Install the original noarch RPM again. Then use DNF to upgrade the RPM to the new version.

     
  4. 4.

    What happens if you make a change to the RPM spec file and rebuild the RPM without updating the release number? Is this also true for the version number?

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.2.78