RPMs - How You Can Build Them!
I am planning a book on creating RPMs, and one part I want to cover is this (arguably "impure") way of creating RPMs very quickly. I just thought I'd put it out there and see if it's useful to anyone, so please, mail me or send feedback online with your thoughts on anything to do with making RPMs (or .DEBs, etc).
- 1. Creating a Simple Binary RPM
- 2. Creating a Useful Binary RPM
- 2.1 Licensing
- 2.2 Building the RPM again
- 2.3 Adding Some Files
- 2.4 Placing the Files
- 2.5 Building the Package (Take Three)
- 2.6 Don't Clean Up!
1. Creating a Simple Binary RPM
There are two types of RPM: a Source RPM, known as an SRPM, and a Binary RPM, which is the type of RPM that most people are familiar with. The Binary RPM does not have to include any binaries, or even any kind of executable code, it is only called Binary because RPM comes from a tradition of creating executable binaries from source code.
The Binary RPM is the simplest type to create, and we shall start off with the simplest of all RPMs - the empty RPM with no files. To get started, create the $HOME/rpmbuild/SPECS
directory, and create a my-first-rpm.spec
file inside it:
[steve@packaging ~]$ cd [steve@packaging ~]$ mkdir rpmbuild [steve@packaging ~]$ cd rpmbuild [steve@packaging rpmbuild]$ mkdir SPECS [steve@packaging rpmbuild]$ cd SPECS [steve@packaging SPECS]$ vi my-first-rpm.specNow, add this text into the file:
Name: my-first-rpm Release: 1 Summary: This is my first ever RPM License: none Version: 1.0 %description My First RPM %files
This is the bare minimum necessary to build an RPM file. We can use this as a template to build upon. It's of no actual use in itself, but we can build it up to see how we create a more complete spec
file, and this process should be useful when you need to create one of your own in real life. The first section defines the name, version, license and so on of the package, followed by the mandatory %description
and %files
sections. Our %files
section is empty, as we have no files (yet), and the %description
is a simple placeholder.
With this my-first-rpm.spec
file, we are ready to create our first ever RPM file. We do this with the rpmbuild
command, passing it the -bb
switch (bb for "Build Binary"), and finally, the name of our my-first-rpm.spec
file:
1 [steve@packaging SPECS]$ rpmbuild -bb my-first-rpm.spec 2 Processing files: my-first-rpm-1.0-1.x86_64 3 Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-1.x86_64 4 warning: Could not canonicalize hostname: packaging.sgpit.com 5 Wrote: /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-1.x86_64.rpm 6 Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.oGgL83 7 + umask 022 8 + cd /home/steve/rpmbuild/BUILD 9 + /bin/rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-1.x86_64 10 + exit 0 11 [steve@packaging SPECS]$
I've added line numbers to the output, so that we can more easily discuss what happens here. On line 1, we run the rpmbuild
command. Line 2 confirms that it is processing the RPM, and shows the full name of the RPM, as derived from the metadata provided in the .spec
file.
On line 3, rpmbuild
checks to see whether there are any additional files in the BUILDROOT
directory which were not mentioned in the .spec
file. We will come to this later, when we start adding files to our RPM, but this is a sanity check to ensure that nothing gets missed.
Line 4 is a warning that rpmbuild
can't resolve the server's hostname (packaging.sgpit.com
, in this case) - we can easily enough resolve this by adding an entry in /etc/hosts
. You may wonder why it even tries to resolve it, but every RPM includes the name of the host that created it, and rpmbuild
tries to find the full DNS name of the server for this purpose. This metadata in the RPM can be useful for lots of reasons, but it's worth bearing in mind that when you create a package, whether for internal use, for a single customer, or for public distribution, all recipients of the RPM file will also know your hostname. So don't call your machine "customers.are.stupid.example.com
", for example!
Line 5 confirms that the RPM file has been successfully created, and tells you where the created RPM file can be found.
The rest of the output, lines 6-10, show the execution of a small shell script that rpmbuild
created. Since we didn't define a %clean
section in our .spec
file, rpmbuild
creates one which deletes everything from the BUILDROOT
directory. Again, we will come to this later, but it is worth being aware that your carefully-crafted contents will be deleted automatically unless you tell it not to. A dangerous side-effect, for the unwary. But we don't have any files (yet), so we don't care (yet)!
We can now inspect our RPM file. The rpm -qip
command is a query for information of the uninstalled package file.
[steve@packaging SPECS]$ rpm -qip ../RPMS/x86_64/my-first-rpm-1.0-1.x86_64.rpm Name : my-first-rpm Relocations: (not relocatable) Version : 1.0 Vendor: (none) Release : 1 Build Date: Sat 03 Jan 2015 11:29:58 GMT Install Date: (not installed) Build Host: packaging.sgpit.com Group : Unspecified Source RPM: my-first-rpm-1.0-1.src.rpm Size : 0 License: none Signature : (none) Summary : This is my first ever RPM Description : My First RPM [steve@packaging SPECS]$
Congratulations! That was a lot of reading and typing, for an entirely useless RPM file, but you have already crossed the biggest hurdle in creating an RPM. Just getting all of this preparation work out of the way is the single biggest obstacle to creating your own bespoke, lovingly hand-crafted RPM files. There's no stopping you now!
2. Creating a Useful Binary RPM
Now that you can create an RPM file, you can start adding files to it, and start to specify all sorts of impressive-looking metadata to the package. We will start with the metadata. Lots of the rpm -qip
output shown at the end of the previous section looks like the RPM was created by somebody who was barely capable of making an RPM at all, and who stopped as soon as they got something that even vaguely worked. We can do so much better than that!
Down the first column, we're pretty much okay. We can specify what group of packages this belongs to (see Appendix A for a list of suggested groups). We've not signed our RPM, but we will come to that later.
The second column is more administrative information, including licensing information. This subject is worthy of a little digression:
2.1 Licensing
In the second column, we have not specfied a Vendor or a License. It is also possible to specify a Packager, if that is different from the Vendor. For the first version of the package, we did actually specify a License of "none" because rpmbuild
refuses to create the package if you don't put anything. It doesn't make sense to distribute anything without a license. The GPL and BSD licenses are the most common in a typical GNU/Linux distribution. You can just as easily specify a proprietary software license here, too. Normally just the word "Proprietary" is used in this case. Whatever the license, it is often useful to include a licensing text file in the package. On Red Hat based systems, this would typically be in /usr/share/doc/%{name}-%{version}/LICENSE. For example, the ethtool
package on Red Hat 6 comes with these files:
[steve@packaging SPECS]$ rpm -ql ethtool /sbin/ethtool /usr/sbin/ethtool /usr/share/doc/ethtool-3.5 /usr/share/doc/ethtool-3.5/AUTHORS /usr/share/doc/ethtool-3.5/COPYING /usr/share/doc/ethtool-3.5/ChangeLog /usr/share/doc/ethtool-3.5/LICENSE /usr/share/doc/ethtool-3.5/NEWS /usr/share/doc/ethtool-3.5/README /usr/share/man/man8/ethtool.8.gz
As well as the binary itself (/usr/sbin/ethtool
is just a link to /sbin/ethtool
, in case you were wondering) and the ethtool.8.gz
man
page, there are a fairly typical set of text files in /usr/share/doc to accompany the program. So the License field in the spec
file does not need to go into great detail, but just let somebody know what class of license the package is distributed under.
2.2 Building the RPM again
For a second version of our RPM, let's make a copy of the spec
file, and add some more detail. The actual name of the spec
file itself doesn't matter - I tend to update its name with each new release, like this:
[steve@packaging SPECS]$ cp my-first-rpm.spec my-first-rpm-2.spec [steve@packaging SPECS]$ vi my-first-rpm-2.spec
We should also add a few lines of description - this is normally a paragraph or so, describing the package in more detail than the one-line format allows. Because this book is Documentation, I will also say that our RPM itself is part of the Documentation group of packages. I will also add a license, and a Vendor. I have increased the Release from 1 to 2. The Release should always be an integer, and it goes up whenever you make a new release from the same version of the software. If the software itself changes, then the Version field would need to be updated, too. You can think of Releases and Versions like this:
Event | Version | Release | RPM Name |
---|---|---|---|
First Release of New Software | 1.0 | 1 | name-1.0-1 |
Software is updated to version 1.1 | 1.1 | 1 | name-1.1-1 |
Version 1.1 is repackaged with a better description | 1.1 | 2 | name-1.1-2 |
Software is updated to version 1.2 | 1.2 | 1 | name-1.2-1 |
Version 1.2 is repackaged with updated Dependencies | 1.2 | 2 | name-1.2-2 |
Version 1.2 is repackaged with updated symbolic links, no code change | 1.2 | 3 | name-1.2-3 |
Software is updated to version 1.3 | 1.3 | 1 | name-1.3-1 |
The resulting my-first-rpm-2.spec
file now looks like this (I have also added the hostname to /etc/hosts
, so we do not get the warning when we build the new RPM):
Name: my-first-rpm Release: 2 Summary: This is my first ever RPM License: GPLv2 Version: 1.0 Vendor: Steve Parker Group: Documentation %description This is my first RPM package. I can describe it more thoroughly here. It is normal to format these sections as 80 columns wide, so that it can easily be read on any screen. %files
So we are ready to build the second release of our package. Back to rpmbuild
:
[steve@packaging SPECS]$ rpmbuild -bb my-first-rpm-2.spec Processing files: my-first-rpm-1.0-2.x86_64 Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-2.x86_64 Wrote: /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-2.x86_64.rpm Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.MdKzNp + umask 022 + cd /home/steve/rpmbuild/BUILD + /bin/rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-2.x86_64 + exit 0 [steve@packaging SPECS]$
When we inspect the new package, the presentation looks much better now:
[steve@packaging SPECS]$ rpm -qip ../RPMS/x86_64/my-first-rpm-1.0-2.x86_64.rpm Name : my-first-rpm Relocations: (not relocatable) Version : 1.0 Vendor: Steve Parker Release : 2 Build Date: Sat 03 Jan 2015 14:02:17 GMT Install Date: (not installed) Build Host: packaging.sgpit.com Group : Documentation Source RPM: my-first-rpm-1.0-2.src.rpm Size : 0 License: GPLv2 Signature : (none) Summary : This is my first ever RPM Description : This is my first RPM package. I can describe it more thoroughly here. It is normal to format these sections as 80 columns wide, so that it can easily be read on any screen. [steve@packaging SPECS]$
2.3 Adding Some Files
The RPM is still pretty useless, as it still contains no files. Create a small shell script, called my-first-program:
#!/bin/bash unset YOUR_NAME CONFIG=/etc/my-first-program.conf [ -f $CONFIG ] && . $CONFIG [ ! -z "$YOUR_NAME" ] && echo -en "Hello, ${YOUR_NAME}." echo "This is a very simple shell script as an example of a packaged program."Like many programs, it uses a configuration file:
# This is a configuration file for my-first-program. # You can define the YOUR_NAME variable here, for example: # YOUR_NAME=Fred YOUR_NAME=
It also has some documentation to go with it, a README file:
This is a very simple program. If YOUR_NAME is defined in /etc/my-first-program.conf, then it will say Hello to you by name. It will then display a message.
As this is a GPLv2 licensed program, I have also taken the COPYING and LICENSE files that we noticed in the ethtool
package earlier. This is a requirement for GPL licensed software; if you are packaging up some existing software, it should come with whatever license files it required. If you are packaging up your own software, you should provide whatever documentation is necessary for the license you choose.
The third iteration of our spec
file looks like this:
Name: my-first-rpm Release: 3 Summary: This is my first ever RPM License: GPLv2 Version: 1.0 Vendor: Steve Parker Group: Documentation %description This is my first RPM package. I can describe it more thoroughly here. It is normal to format these sections as 80 columns wide, so that it can easily be read on any screen. %files /usr/bin/my-first-program %config /etc/my-first-program.conf %doc %name-%version/README %doc %name-%version/COPYING %doc %name-%version/LICENSE
We shall discuss the %config
and %doc
directives in the "Building the Package" section below, though I am sure you can guess what they mean!
2.4 Placing the Files
To get these files included in the RPM, we need to put them into the $HOME/rpmbuild/BUILDROOT
directory. At least, that is true of the first two files; we will look at the documentation files in a moment.
I have currently got the files in a directory called my-first-program
in my home directory. We shall need to copy them from where they are, into a directory called BUILDROOT/%name-%version-%release.%arch
. In this case, that is BUILDROOT/my-first-rpm-1.0-3.x86_64
. We create a mockup of the root filesystem in there, which will be used to create the package:
[steve@packaging SPECS]$ ls ~/my-first-program/ COPYING LICENSE my-first-program my-first-program.conf README [steve@packaging SPECS]$ mkdir -p ../BUILDROOT/my-first-rpm-1.0-3.x86_64/etc [steve@packaging SPECS]$ mkdir -p ../BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin [steve@packaging SPECS]$ cp ~/my-first-program/my-first-program.conf ../BUILDROOT/my-first-rpm-1.0-3.x86_64/etc/ [steve@packaging SPECS]$ cp ~/my-first-program/my-first-program ../BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin/
The documentation files are a little different, because we didn't specify where in the filesystem they should go - "%{name}-%{version}/README
" is a relative path, not an absolute path (it does not begin with "/
"). An operating system could choose to put these files wherever it likes, and it makes no difference to our package, so we can just label them as documentation, using the %doc
directive, specify only a relative path, and let RPM decide where to put them. On a default Red Hat based system, this would be /usr/share/doc
, but it could be anywhere. So for these files, we put them into the BUILD directory, since that is where the packager would expect to find them:
[steve@packaging SPECS]$ mkdir ../BUILD/my-first-rpm-1.0 [steve@packaging SPECS]$ cp ~/my-first-program/LICENSE ../BUILD/my-first-rpm-1.0 [steve@packaging SPECS]$ cp ~/my-first-program/COPYING ../BUILD/my-first-rpm-1.0 [steve@packaging SPECS]$ cp ~/my-first-program/README ../BUILD/my-first-rpm-1.0
The whole tree currently looks like this (we are still in the rpmbuild/SPECS
directory). I have marked our files to be installed in bold text, the other files are from the previous builds:
[steve@packaging SPECS]$ find $HOME/rpmbuild/ -type f /home/steve/rpmbuild/BUILD/my-first-rpm-1.0/LICENSE /home/steve/rpmbuild/BUILD/my-first-rpm-1.0/COPYING /home/steve/rpmbuild/BUILD/my-first-rpm-1.0/README /home/steve/rpmbuild/SPECS/my-first-rpm.spec /home/steve/rpmbuild/SPECS/my-first-rpm-2.spec /home/steve/rpmbuild/SPECS/my-first-rpm-3.spec /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin/my-first-program /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/etc/my-first-program.conf /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-2.x86_64.rpm /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-1.x86_64.rpm [steve@packaging SPECS]$
2.5 Building the Package (Take Three)
We are now ready to build our package. There is quite a lot more output than before, so again I will number the lines so that we can discuss it in detail:
1 [steve@packaging SPECS]$ rpmbuild -bb my-first-rpm-3.spec 2 Processing files: my-first-rpm-1.0-3.x86_64 3 Executing(%doc): /bin/sh -e /var/tmp/rpm-tmp.ryqrzS 4 + umask 022 5 + cd /home/steve/rpmbuild/BUILD 6 + DOCDIR=/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 7 + export DOCDIR 8 + rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 9 + /bin/mkdir -p /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 10 + cp -pr my-first-rpm-1.0/README /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 11 + cp -pr my-first-rpm-1.0/COPYING /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 12 + cp -pr my-first-rpm-1.0/LICENSE /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 13 + exit 0 14 Provides: config(my-first-rpm) = 1.0-3 15 Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1 16 Requires: /bin/bash 17 Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64 18 Wrote: /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-3.x86_64.rpm 19 Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.GpyOXx 20 + umask 022 21 + cd /home/steve/rpmbuild/BUILD 22 + /bin/rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64 23 + exit 0 24 [steve@packaging SPECS]$
The first thing that rpmbuild
does, on lines 3-13, is to process the %doc
directive that we have given it. It executes a small shell script that it created, which copies our document files into the BUILDROOT
tree for us. We had to this for ourselves on the /usr/bin
and /etc
files, but the %doc
directive causes this to be done for us. In the spec
file we specified %name-%version
, as that is the usual convention for the system documentation directory (i.e.,/usr/share/doc/my-first-rpm-1.0/
).
Line 14 shows that we have given it a %config
directive for the /etc/my-first-program.conf
file. Telling rpm
that this is a configuration file allows it to give it special treatment during upgrades. See TODO FIXME ??? Later in doc??? for more information on how rpm
treats configuration files. Line 15 is about the features available in our current version of rpm
- CompressedFileNames means that file names are stored as (dirName,BaseName,dirIndex) rather than as a regular path, and PayloadFilesHavePrefix means that files are stored with a "./
" prefix. None of that is terribly important to us, however. TODO FIXME Link to www.redhat.com/archives/rpm-list/2000-October/msg00209.html
Line 16 shows that rpmbuild
has inspected the actual content of the files that we are packaging. It noticed that the my-first-program
script requires the /bin/bash
executable - if the user did a "yum install my-first-rpm
" then yum
would dig out the bash
package too. If they just did a "rpm -i my-first-rpm-1.0-3.x86_64.rpm
" and bash
wasn't installed, it would refuse to install my-first-rpm
. This automated process is not always foolproof, but it can be a useful automated tool for picking up such dependencies.
The remaining lines are the same as before, including the dangerous %clean
section. Running the find
command again, we see that rpmbuild
has "helpfully" cleaned out our BUILDROOT
(though not the BUILD
) directory:
[steve@packaging SPECS]$ find $HOME/rpmbuild/ -type f /home/steve/rpmbuild/BUILD/my-first-rpm-1.0/LICENSE /home/steve/rpmbuild/BUILD/my-first-rpm-1.0/COPYING /home/steve/rpmbuild/BUILD/my-first-rpm-1.0/README /home/steve/rpmbuild/SPECS/my-first-rpm.spec /home/steve/rpmbuild/SPECS/my-first-rpm-2.spec /home/steve/rpmbuild/SPECS/my-first-rpm-3.spec /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-3.x86_64.rpm /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-2.x86_64.rpm /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-1.x86_64.rpm [steve@packaging SPECS]$
2.6 Don't Clean Up!
Since we carefully put our files there, and in real life may find that we need to create the RPM a few times before we get it exactly how we want it, this behaviour can be a real pain. We can stop it from doing this by adding our own, blank, %clean
directive into the spec
file. Let's update the my-first-rpm-3.spec
file one final time:
Name: my-first-rpm Release: 3 Summary: This is my first ever RPM License: GPLv2 Version: 1.0 Vendor: Steve Parker Group: Documentation %description This is my first RPM package. I can describe it more thoroughly here. It is normal to format these sections as 80 columns wide, so that it can easily be read on any screen. %clean echo "Not cleaning up in this case." %files /usr/bin/my-first-program %config /etc/my-first-program.conf %doc %name-%version/README %doc %name-%version/COPYING %doc %name-%version/LICENSE
We can then repopulate our carefully-configured fake root directory, and create the package again:
[steve@packaging SPECS]$ mkdir -p /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/etc [steve@packaging SPECS]$ mkdir -p /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin [steve@packaging SPECS]$ cp ~/my-first-program/my-first-program.conf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/etc [steve@packaging SPECS]$ cp ~/my-first-program/my-first-program /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin [steve@packaging SPECS]$ rpmbuild -bb my-first-rpm-3.spec Processing files: my-first-rpm-1.0-3.x86_64 Executing(%doc): /bin/sh -e /var/tmp/rpm-tmp.TuSj1K + umask 022 + cd /home/steve/rpmbuild/BUILD + DOCDIR=/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 + export DOCDIR + rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 + /bin/mkdir -p /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 + cp -pr my-first-rpm-1.0/README /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 + cp -pr my-first-rpm-1.0/COPYING /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 + cp -pr my-first-rpm-1.0/LICENSE /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0 + exit 0 Provides: config(my-first-rpm) = 1.0-3 Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1 Requires: /bin/bash Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64 Wrote: /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-3.x86_64.rpm Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.wyKIt5 + umask 022 + cd /home/steve/rpmbuild/BUILD + echo 'Not cleaning up in this case.' Not cleaning up in this case. + exit 0
Now when we look, we can see that our fake root filesystem is still there - it even shows the copied %doc
files, which were put there automatically for us:
[steve@packaging SPECS]$ find $HOME/rpmbuild/ -type f /home/steve/rpmbuild/BUILD/my-first-rpm-1.0/LICENSE /home/steve/rpmbuild/BUILD/my-first-rpm-1.0/COPYING /home/steve/rpmbuild/BUILD/my-first-rpm-1.0/README /home/steve/rpmbuild/SPECS/my-first-rpm.spec /home/steve/rpmbuild/SPECS/my-first-rpm-2.spec /home/steve/rpmbuild/SPECS/my-first-rpm-3.spec /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/bin/my-first-program /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0/LICENSE /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0/COPYING /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/usr/share/doc/my-first-rpm-1.0/README /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-3.x86_64/etc/my-first-program.conf /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-3.x86_64.rpm /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-2.x86_64.rpm /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-1.x86_64.rpm [steve@packaging SPECS]$
It is worth saying that although here we take steps to disable the feature, automatically cleaning out the BUILDROOT
directory is the right thing for rpmbuild
to do, since it assumes that we are using all of its features, and we wouldn't want artifacts left hanging around from previous builds. What we are doing here is a bit of a naughty shortcut for creating RPMs, but this method is Good Enough™ for lots of uses, and I find that a spec
file like my-first-rpm-3.spec
is very nearly a useful template to have around for quickly putting an RPM package together. Two more small tweaks will make it more genuinely useful.
Permissions
We have not specified any permissions on our files. It is possible to stipulate the desired permissions in our BUILDROOT
directory, but it's not desirable (and maybe not even possible) to create root-owned files in your home directory, let alone root-owned SUID files, or other special permissions that you might want your files to be installed with. You may need files to be owned by a particular user, but that user account does not exist on your packaging machine. So your spec
file can be used to specify any permissions required.
As standard, the files will be packaged exactly as they are found in your BUILDROOT
directory. That is fine, as far as it goes, but the files are likely to be owned by your own username, which is unlikely to be what you want.
You can specify the default permissions to use, for when you don't give any direct requirements, with the %defattr
parameter, just after the %files
directive. You can then override this with the %attr
directive for any given file. For example, to set the default permissions as 0755, owner root, group root, but have the configuration file with permissions 0644, you can use this %files
section:
%files %defattr(0755,root,root) /usr/bin/my-first-program %attr(0644,root,root) %config /etc/my-first-program.conf %doc %name-%version/README %doc %name-%version/COPYING %doc %name-%version/LICENSE
Copy the my-first-rpm-3.spec
file to my-first-rpm-4.spec
, update the Release number to 4, and copy the BUILDROOT
to have a directory name which matches the new Release number:
[steve@packaging SPECS]$ cp my-first-rpm-3.spec my-first-rpm-4.spec [steve@packaging SPECS]$ vi my-first-rpm-4.spec (just to update the Release) [steve@packaging SPECS]$ cd ../BUILDROOT/ [steve@packaging BUILDROOT]$ cp -r my-first-rpm-1.0-3.x86_64/ my-first-rpm-1.0-4.x86_64/
ChangeLog
The final thing to add to our simple spec
file is a ChangeLog. This is a useful piece of documentation to add to an RPM, as it can track what changes were made, why, when, and by whom. RPM is rather picky about the formatting of Changelog entries, but this will suffice for now:
%changelog * Mon Jan 5 2015 Steve Parker- Bug fixes and improvements * Sun Jan 4 2015 Steve Parker - Initial release.
Append this to the end of the spec
file, and we should have a presentable RPM file:
[steve@packaging SPECS]$ cat my-first-rpm-4.spec Name: my-first-rpm Release: 4 Summary: This is my first ever RPM License: GPLv2 Version: 1.0 Vendor: Steve Parker Group: Documentation %description This is my first RPM package. I can describe it more thoroughly here. It is normal to format these sections as 80 columns wide, so that it can easily be read on any screen. %clean echo "Not cleaning up in this case." %files %defattr(0755,root,root) /usr/bin/my-first-program %attr(0644,root,root) %config /etc/my-first-program.conf %doc %name-%version/README %doc %name-%version/COPYING %doc %name-%version/LICENSE %changelog * Mon Jan 5 2015 Steve Parker- Bug fixes and improvements * Sun Jan 4 2015 Steve Parker - Initial release.
Create the package, as before:
[steve@packaging SPECS]$ rpmbuild -bb my-first-rpm-4.spec Processing files: my-first-rpm-1.0-4.x86_64 Executing(%doc): /bin/sh -e /var/tmp/rpm-tmp.1dPZUt + umask 022 + cd /home/steve/rpmbuild/BUILD + DOCDIR=/home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0 + export DOCDIR + rm -rf /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0 + /bin/mkdir -p /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0 + cp -pr my-first-rpm-1.0/README /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0 + cp -pr my-first-rpm-1.0/COPYING /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0 + cp -pr my-first-rpm-1.0/LICENSE /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64/usr/share/doc/my-first-rpm-1.0 + exit 0 Provides: config(my-first-rpm) = 1.0-4 Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1 Requires: /bin/bash Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/steve/rpmbuild/BUILDROOT/my-first-rpm-1.0-4.x86_64 Wrote: /home/steve/rpmbuild/RPMS/x86_64/my-first-rpm-1.0-4.x86_64.rpm Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.h0iVIC + umask 022 + cd /home/steve/rpmbuild/BUILD + echo 'Not cleaning up in this case.' Not cleaning up in this case. + exit 0 [steve@packaging SPECS]$
If you have root
access, you can now upgrade to Release 4 of the RPM, and inspect the new permissions and Change Log.
TODO FIXME: Note: For more information on Linux file permission modes, see this image (via https://twitter.com/brutelogic/status/533207014630227969)
[steve@packaging SPECS]$ sudo rpm -Uvh ../RPMS/x86_64/my-first-rpm-1.0-4.x86_64.rpm Preparing... ########################################### [100%] 1:my-first-rpm ########################################### [100%] [steve@packaging SPECS]$ ls -l /usr/bin/my-first-program /usr/share/doc/my-first-rpm-1.0/ /etc/my-first-program.conf -rw-r--r--. 1 root root 140 Jan 5 20:26 /etc/my-first-program.conf -rwxr-xr-x. 1 root root 227 Jan 5 20:26 /usr/bin/my-first-program /usr/share/doc/my-first-rpm-1.0/: total 28 -rwxr-xr-x. 1 root root 17992 Jan 3 15:35 COPYING -rwxr-xr-x. 1 root root 109 Jan 3 15:35 LICENSE -rwxr-xr-x. 1 root root 157 Jan 3 15:20 README [steve@packaging SPECS]$ rpm -q --changelog my-first-rpm * Mon Jan 05 2015 Steve Parker- Bug fixes and improvements * Sun Jan 04 2015 Steve Parker - Initial release. [steve@packaging SPECS]$
That should be enough for a very simple, buy presentable binary RPM package of some prebuilt code. There is still a lot more that RPM can do, such as pre- and post-installation scripts, and patching and compiling source code, but this is in practice often enough for basic sysadmin type scenarios.