Chapter 4. Advanced topics
This section covers topics that are beyond the scope of the introductory tutorial but are useful in real-world RPM packaging.
4.1. Signing packages
Packages are signed to make sure no third party can alter their content. A user can add an additional layer of security by using the HTTPS protocol when downloading the package.
There are three ways to sign a package:
4.1.1. Creating a GPG key
Procedure
Generate a GNU Privacy Guard (GPG) key pair:
# gpg --gen-key
Confirm and see the generated key:
# gpg --list-keys
Export the public key:
# gpg --export -a '<Key_name>' > RPM-GPG-KEY-pmanager
NoteInclude the real name that you have selected for the key instead of <Key_name>.
Import the exported public key into an RPM database:
# rpm --import RPM-GPG-KEY-pmanager
4.1.2. Adding a signature to an already existing package
This section describes the most usual case when a package is built without a signature. The signature is added just before the release of the package.
To add a signature to a package, use the --addsign
option provided by the rpm-sign
package.
Having more than one signature enables to record the package’s path of ownership from the package builder to the end-user.
Procedure
Add a signature to a package:
$ rpm --addsign blather-7.9-1.x86_64.rpm
NoteYou are supposed to enter the password to unlock the secret key for the signature.
4.1.3. Checking the signatures of a package with multiple signatures
Procedure
To check the signatures of a package with multiple signatures, run the following:
$ rpm --checksig blather-7.9-1.x86_64.rpm blather-7.9-1.x86_64.rpm: size pgp pgp md5 OK
The two
pgp
strings in the output of therpm --checksig
command show that the package has been signed twice.
4.1.4. A practical example of adding a signature to an already existing package
This section describes an example situation where adding a signature to an already existing package might be useful.
A division of a company creates a package and signs it with the division’s key. The company’s headquarters then checks the package’s signature and adds the corporate signature to the package, stating that the signed package is authentic.
With two signatures, the package makes its way to a retailer. The retailer checks the signatures and, if they match, adds their signature as well.
The package now makes its way to a company that wants to deploy the package. After checking every signature on the package, they know that it is an authentic copy. Depending on the deploying company’s internal controls, they may choose to add their own signature, to inform their employees that the package has received their corporate approval
4.1.5. Replacing the signature on an already existing package
This procedure describes how to change the public key without having to rebuild each package.
Procedure
To change the public key, run the following:
$ rpm --resign blather-7.9-1.x86_64.rpm
NoteYou are supposed to enter the password to unlock the secret key for the signature.
The --resign
option also enables you to change the public key for multiple packages, as shown in the following procedure.
Procedure
To change the public key for multiple packages, execute:
$ rpm --resign b*.rpm
NoteYou are supposed to enter the password to unlock the secret key for the signature.
4.1.6. Signing a package at build-time
Procedure
Build the package with the
rpmbuild
command:$ rpmbuild blather-7.9.spec
Sign the package with the
rpmsign
command using the--addsign
option:$ rpmsign --addsign blather-7.9-1.x86_64.rpm
- Optionally, verify the signature of a package:
$ rpm --checksig blather-7.9-1.x86_64.rpm blather-7.9-1.x86_64.rpm: size pgp md5 OK
When building and signing multiple packages, use the following syntax to avoid entering the Pretty Good Privacy (PGP) passphrase multiple times.
$ rpmbuild -ba --sign b*.spec
Note that you are supposed to enter the password to unlock the secret key for the signature.
4.2. More on macros
This section covers selected built-in RPM Macros. For an exhaustive list of such macros, see RPM Documentation.
4.2.1. Defining your own macros
The following section describes how to create a custom macro.
Procedure
Include the following line in the RPM SPEC file:
%global <name>[(opts)] <body>
All whitespace surrounding \
is removed. Name may be composed of alphanumeric characters, and the character _
and must be at least 3 characters in length. Inclusion of the (opts)
field is optional:
-
Simple
macros do not contain the(opts)
field. In this case, only recursive macro expansion is performed. -
Parametrized
macros contain the(opts)
field. Theopts
string between parentheses is passed togetopt(3)
forargc/argv
processing at the beginning of a macro invocation.
Older RPM SPEC files use the %define <name> <body>
macro pattern instead. The differences between %define
and %global
macros are as follows:
-
%define
has local scope. It applies to a specific part of a SPEC file. The body of a%define
macro is expanded when used. -
%global
has global scope. It applies to an entire SPEC file. The body of a%global
macro is expanded at definition time.
Macros are evaluated even if they are commented out or the name of the macro is given into the %changelog
section of the SPEC file. To comment out a macro, use %%
. For example: %%global
.
Additional resources
For comprehensive information on macros capabilities, see RPM Documentation.
4.2.2. Using the %setup macro
This section describes how to build packages with source code tarballs using different variants of the %setup
macro. Note that the macro variants can be combined The rpmbuild
output illustrates standard behavior of the %setup
macro. At the beginning of each phase, the macro outputs Executing(%…)
, as shown in the below example.
Example 4.1. Example %setup
macro output
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.DhddsG
The shell output is set with set -x
enabled. To see the content of /var/tmp/rpm-tmp.DhddsG
, use the --debug
option because rpmbuild
deletes temporary files after a successful build. This displays the setup of environment variables followed by for example:
cd '/builddir/build/BUILD' rm -rf 'cello-1.0' /usr/bin/gzip -dc '/builddir/build/SOURCES/cello-1.0.tar.gz' | /usr/bin/tar -xof - STATUS=$? if [ $STATUS -ne 0 ]; then exit $STATUS fi cd 'cello-1.0' /usr/bin/chmod -Rf a+rX,u+w,g-w,o-w .
The %setup
macro:
- Ensures that we are working in the correct directory.
- Removes residues of previous builds.
- Unpacks the source tarball.
- Sets up some default privileges.
4.2.2.1. Using the %setup -q macro
The -q
option limits the verbosity of the %setup
macro. Only tar -xof
is executed instead of tar -xvvof
. Use this option as the first option.
4.2.2.2. Using the %setup -n macro
The -n
option is used to specify the name of the directory from expanded tarball.
This is used in cases when the directory from expanded tarball has a different name from what is expected (%{name}-%{version}
), which can lead to an error of the %setup
macro.
For example, if the package name is cello
, but the source code is archived in hello-1.0.tgz
and contains the hello/
directory, the SPEC file content needs to be as follows:
Name: cello
Source0: https://example.com/%{name}/release/hello-%{version}.tar.gz
…
%prep
%setup -n hello
4.2.2.3. Using the %setup -c macro
The -c
option is used if the source code tarball does not contain any subdirectories and after unpacking, files from an archive fills the current directory.
The -c
option then creates the directory and steps into the archive expansion as shown below:
/usr/bin/mkdir -p cello-1.0 cd 'cello-1.0'
The directory is not changed after archive expansion.
4.2.2.4. Using the %setup -D and %setup -T macros
The -D
option disables deleting of source code directory, and is particularly useful if the %setup
macro is used several times. With the -D
option, the following lines are not used:
rm -rf 'cello-1.0'
The -T
option disables expansion of the source code tarball by removing the following line from the script:
/usr/bin/gzip -dc '/builddir/build/SOURCES/cello-1.0.tar.gz' | /usr/bin/tar -xvvof -
4.2.2.5. Using the %setup -a and %setup -b macros
The -a
and -b
options expand specific sources:
The -b
option stands for before
, and it expands specific sources before entering the working directory. The -a
option stands for after
, and it expands those sources after entering. Their arguments are source numbers from the SPEC file preamble.
In the following example, the cello-1.0.tar.gz
archive contains an empty examples
directory. The examples are shipped in a separate examples.tar.gz
tarball and they expand into the directory of the same name. In this case, use -a 1
, if you want to expand Source1
after entering the working directory:
Source0: https://example.com/%{name}/release/%{name}-%{version}.tar.gz
Source1: examples.tar.gz
…
%prep
%setup -a 1
In the following example, examples are provided in a separate cello-1.0-examples.tar.gz
tarball, which expands into cello-1.0/examples
. In this case, use -b 1
, to expand Source1
before entering the working directory:
Source0: https://example.com/%{name}/release/%{name}-%{version}.tar.gz
Source1: %{name}-%{version}-examples.tar.gz
…
%prep
%setup -b 1
4.2.3. Common RPM macros in the %files section
This section lists advanced RPM Macros that are needed in the %files
section of a SPEC file.
Macro | Definition |
---|---|
%license |
The macro identifies the file listed as a LICENSE file and it will be installed and labeled as such by RPM. Example: |
%doc |
The macro identifies a file listed as documentation and it will be installed and labeled as such by RPM. The macro is used for documentation about the packaged software and also for code examples and various accompanying items. In the event code examples are included, care should be taken to remove executable mode from the file. Example: |
%dir |
The macro ensures that the path is a directory owned by this RPM. This is important so that the RPM file manifest accurately knows what directories to clean up on uninstall. Example: |
%config(noreplace) |
The macro ensures that the following file is a configuration file and therefore should not be overwritten (or replaced) on a package install or update if the file has been modified from the original installation checksum. If there is a change, the file will be created with |
4.2.4. Displaying the built-in macros
provides multiple built-in RPM macros.
Procedure
To display all built-in RPM macros, run:
rpm --showrc
NoteThe output is quite sizeable. To narrow the result, use the command above with the
grep
command.To find information about the RPMs macros for your system’s version of RPM, run:
rpm -ql rpm
NoteRPM macros are the files titled
macros
in the output directory structure.
4.2.5. RPM distribution macros
Different distributions provide different sets of recommended RPM macros based on the language implementation of the software being packaged or the specific guidelines of the distribution.
The sets of recommended RPM macros are often provided as RPM packages, ready to be installed with the yum
package manager.
Once installed, the macro files can be found in the /usr/lib/rpm/macros.d/
directory.
To display the raw RPM macro definitions, run:
rpm --showrc
The above output displays the raw RPM macro definitions.
To determine what a macro does and how it can be helpful when packaging RPMs, run the rpm --eval
command with the name of the macro used as its argument:
rpm --eval %{_MACRO}
For more information, see the rpm
man page.
4.2.5.1. Creating custom macros
You can override the distribution macros in the ~/.rpmmacros
file with your custom macros. Any changes that you make affect every build on your machine.
Defining any new macros in the ~/.rpmmacros
file is not recommended. Such macros would not be present on other machines, where users may want to try to rebuild your package.
To override a macro, run :
%_topdir /opt/some/working/directory/rpmbuild
You can create the directory from the example above, including all subdirectories through the rpmdev-setuptree
utility. The value of this macro is by default ~/rpmbuild
.
%_smp_mflags -l3
The macro above is often used to pass to Makefile, for example make %{?_smp_mflags}
, and to set a number of concurrent processes during the build phase. By default, it is set to -jX
, where X
is a number of cores. If you alter the number of cores, you can speed up or slow down a build of packages.
4.3. Epoch, Scriptlets and Triggers
This section covers Epoch
, Scriptlets
, and Triggers
, which represent advanced directives for RMP SPEC files.
All these directives influence not only the SPEC file, but also the end machine on which the resulting RPM is installed.
4.3.1. The Epoch directive
The Epoch
directive enables to define weighted dependencies based on version numbers.
If this directive is not listed in the RPM SPEC file, the Epoch
directive is not set at all. This is contrary to common belief that not setting Epoch
results in an Epoch
of 0. However, the YUM utility treats an unset Epoch
as the same as an Epoch
of 0 for the purposes of depsolving.
However, listing Epoch
in a SPEC file is usually omitted because in majority of cases introducing an Epoch
value skews the expected RPM behavior when comparing versions of packages.
Example 4.2. Using Epoch
If you install the foobar
package with Epoch: 1
and Version: 1.0
, and someone else packages foobar
with Version: 2.0
but without the Epoch
directive, the new version will never be considered an update. The reason being that the Epoch
version is preferred over the traditional Name-Version-Release
marker that signifies versioning for RPM Packages.
Using of Epoch
is thus quite rare. However, Epoch
is typically used to resolve an upgrade ordering issue. The issue can appear as a side effect of upstream change in software version number schemes or versions incorporating alphabetical characters that cannot always be compared reliably based on encoding.
4.3.2. Scriptlets
Scriptlets are a series of RPM directives that are executed before or after packages are installed or deleted.
Use Scriptlets only for tasks that cannot be done at build time or in an start up script.
4.3.2.1. Scriptlets directives
A set of common Scriptlet directives exists. They are similar to the SPEC file section headers, such as %build
or %install
. They are defined by multi-line segments of code, which are often written as a standard POSIX shell script. However, they can also be written in other programming languages that RPM for the target machine’s distribution accepts. RPM Documentation includes an exhaustive list of available languages.
The following table includes Scriptlet directives listed in their execution order. Note that a package containing the scripts is installed between the %pre
and %post
directive, and it is uninstalled between the %preun
and %postun
directive.
Directive | Definition |
---|---|
| Scriptlet that is executed just before installing or removing any package. |
| Scriptlet that is executed just before installing the package on the target system. |
| Scriptlet that is executed just after the package was installed on the target system. |
| Scriptlet that is executed just before uninstalling the package from the target system. |
| Scriptlet that is executed just after the package was uninstalled from the target system. |
| Scriptlet that is executed at the end of the transaction. |
4.3.2.2. Turning off a scriptlet execution
To turn off the execution of any scriptlet, use the rpm
command together with the --no_scriptlet_name_
option.
Procedure
For example, to turn off the execution of the
%pretrans
scriptlets, run:# rpm --nopretrans
You can also use the
-- noscripts
option, which is equivalent to all of the following:-
--nopre
-
--nopost
-
--nopreun
-
--nopostun
-
--nopretrans
-
--noposttrans
-
Additional resources
-
For more details, see the
rpm(8)
man page.
4.3.2.3. Scriptlets macros
The Scriptlets directives also work with RPM macros.
The following example shows the use of systemd scriptlet macro, which ensures that systemd is notified about a new unit file.
$ rpm --showrc | grep systemd -14: transaction_systemd_inhibit %{plugindir}/systemd_inhibit.so -14: _journalcatalogdir /usr/lib/systemd/catalog -14: _presetdir /usr/lib/systemd/system-preset -14: _unitdir /usr/lib/systemd/system -14: _userunitdir /usr/lib/systemd/user /usr/lib/systemd/systemd-binfmt %{?} >/dev/null 2>&1 || : /usr/lib/systemd/systemd-sysctl %{?} >/dev/null 2>&1 || : -14: systemd_post -14: systemd_postun -14: systemd_postun_with_restart -14: systemd_preun -14: systemd_requires Requires(post): systemd Requires(preun): systemd Requires(postun): systemd -14: systemd_user_post %systemd_post --user --global %{?} -14: systemd_user_postun %{nil} -14: systemd_user_postun_with_restart %{nil} -14: systemd_user_preun systemd-sysusers %{?} >/dev/null 2>&1 || : echo %{?} | systemd-sysusers - >/dev/null 2>&1 || : systemd-tmpfiles --create %{?} >/dev/null 2>&1 || : $ rpm --eval %{systemd_post} if [ $1 -eq 1 ] ; then # Initial installation systemctl preset >/dev/null 2>&1 || : fi $ rpm --eval %{systemd_postun} systemctl daemon-reload >/dev/null 2>&1 || : $ rpm --eval %{systemd_preun} if [ $1 -eq 0 ] ; then # Package removal, not upgrade systemctl --no-reload disable > /dev/null 2>&1 || : systemctl stop > /dev/null 2>&1 || : fi
4.3.3. The Triggers directives
Triggers are RPM directives which provide a method for interaction during package installation and uninstallation.
Triggers may be executed at an unexpected time, for example on update of the containing package. Triggers are difficult to debug, therefore they need to be implemented in a robust way so that they do not break anything when executed unexpectedly. For these reasons, {RH} recommends to minimize the use of Triggers.
The order of execution and the details for each existing Triggers are listed below:
all-%pretrans … any-%triggerprein (%triggerprein from other packages set off by new install) new-%triggerprein new-%pre for new version of package being installed … (all new files are installed) new-%post for new version of package being installed any-%triggerin (%triggerin from other packages set off by new install) new-%triggerin old-%triggerun any-%triggerun (%triggerun from other packages set off by old uninstall) old-%preun for old version of package being removed … (all old files are removed) old-%postun for old version of package being removed old-%triggerpostun any-%triggerpostun (%triggerpostun from other packages set off by old un install) … all-%posttrans
The above items are found in the /usr/share/doc/rpm-4.*/triggers
file.
4.3.4. Using non-shell scripts in a SPEC file
The -p
scriptlet option in a SPEC file enables the user to invoke a specific interpreter instead of the default shell scripts interpreter (-p /bin/sh
).
The following procedure describes how to create a script, which prints out a message after installation of the pello.py
program:
Procedure
-
Open the
pello.spec
file. Find the following line:
install -m 0644 %{name}.py* %{buildroot}/usr/lib/%{name}/
Under the above line, insert:
%post -p /usr/bin/python3 print("This is {} code".format("python"))
Install your package:
# yum install /home/<username>/rpmbuild/RPMS/noarch/pello-0.1.2-1.el8.noarch.rpm
Check the output message after the installation:
Installing : pello-0.1.2-1.el8.noarch 1/1 Running scriptlet: pello-0.1.2-1.el8.noarch 1/1 This is python code
To use a Python 3 script, include the following line under install -m
in a SPEC file:
%post -p /usr/bin/python3
To use a Lua script, include the following line under install -m
in a SPEC file:
%post -p <lua>
This way, you can specify any interpreter in a SPEC file.
4.4. RPM conditionals
RPM Conditionals enable conditional inclusion of various sections of the SPEC file.
Conditional inclusions usually deal with:
- Architecture-specific sections
- Operating system-specific sections
- Compatibility issues between various versions of operating systems
- Existence and definition of macros
4.4.1. RPM conditionals syntax
RPM conditionals use the following syntax:
If expression is true, then do some action:
%if expression … %endif
If expression is true, then do some action, in other case, do another action:
%if expression … %else … %endif
4.4.2. RPM conditionals examples
This section provides multiple examples of RPM conditionals.
4.4.2.1. The %if conditionals
Example 4.3. Using the %if conditional to handle compatibility between 8 and other operating systems
%if 0%{?rhel} == 8
sed -i '/AS_FUNCTION_DESCRIBE/ s/^//' configure.in sed -i '/AS_FUNCTION_DESCRIBE/ s/^//' acinclude.m4
%endif
This conditional handles compatibility between RHEL 8 and other operating systems in terms of support of the AS_FUNCTION_DESCRIBE macro. If the package is built for RHEL, the %rhel
macro is defined, and it is expanded to RHEL version. If its value is 8, meaning the package is build for RHEL 8, then the references to AS_FUNCTION_DESCRIBE, which is not supported by RHEL 8, are deleted from autoconfig scripts.
Example 4.4. Using the %if conditional to handle definition of macros
%define ruby_archive %{name}-%{ruby_version} %if 0%{?milestone:1}%{?revision:1} != 0 %define ruby_archive %{ruby_archive}-%{?milestone}%{?!milestone:%{?revision:r%{revision}}} %endif
This conditional handles definition of macros. If the %milestone
or the %revision
macros are set, the %ruby_archive
macro, which defines the name of the upstream tarball, is redefined.
4.4.2.2. Specialized variants of %if conditionals
The %ifarch
conditional, %ifnarch
conditional and %ifos
conditional are specialized variants of the %if
conditionals. These variants are commonly used, hence they have their own macros.
4.4.2.2.1. The %ifarch conditional
The %ifarch
conditional is used to begin a block of the SPEC file that is architecture-specific. It is followed by one or more architecture specifiers, each separated by commas or whitespace.
Example 4.5. An example use of the %ifarch conditional
%ifarch i386 sparc … %endif
All the contents of the SPEC file between %ifarch
and %endif
are processed only on the 32-bit AMD and Intel architectures or Sun SPARC-based systems.
4.4.2.2.2. The %ifnarch conditional
The %ifnarch
conditional has a reverse logic than %ifarch
conditional.
Example 4.6. An example use of the %ifnarch conditional
%ifnarch alpha … %endif
All the contents of the SPEC file between %ifnarch
and %endif
are processed only if not done on a Digital Alpha/AXP-based system.
4.4.2.2.3. The %ifos conditional
The %ifos
conditional is used to control processing based on the operating system of the build. It can be followed by one or more operating system names.
Example 4.7. An example use of the %ifos conditional
%ifos linux … %endif
All the contents of the SPEC file between %ifos
and %endif
are processed only if the build was done on a Linux system.