Cross-Platform
Native Package Creation with EPM
Jeff Layton
I'm a big fan of open source software and also of writing my own
scripts and programs to automate things. Currently, I manage three
major Unix-ish operating systems at work: Solaris, HP-UX, and Debian
GNU/Linux. Tracking all of this added software can be a relatively
large burden, especially if it's just copied or unzipped into place.
Because of this, I've recently come to favor using the "native"
package format for an operating system. If you simply copy or untar
files on a machine, or use a non-standard packaging scheme, it's
quite difficult to know when you are overwriting a file that is
being tracked in the native package catalog, and to uninstall the
files later.
Unfortunately, the process to generate a package for each OS often
varies greatly between platforms. Generating HP-UX depots is very
different from creating a Solaris package. Even on Linux, there
are significant differences between the two major package formats
(RPMs and debs).
The Easy Software Productions Package Manager (EPM)
Enter EPM, the cross-platform packaging tool from Easy Software
Productions (better known for their fabulous CUPS software). EPM
can generate binary packages for many Unix and Linux-based operating
systems, including AIX, BSD, Tru64, Debian, HP-UX, IRIX, OS-X, Red
Hat, and Solaris. It can also build packages in its own format that
includes a GUI setup program. In this article, I will focus on creating
"native" packages -- those that use the packaging scheme bundled
with the OS.
EPM relies on programs that are bundled with an operating system
to package software. Building an RPM package means that you need
an "rpm" binary, and building a package for Solaris requires the
Solaris "pkgmk" tool. Building packages in non-native formats is
possible, but only if you have a version of the packaging tool available
on the system on which you're building the package. For instance,
it's possible to build an RPM package on a Solaris machine, but
an installed version of RPM must exist on your Solaris machine.
The EPM distribution is available from:
http://www.easysw.com/epm
EPM is licensed for distribution under the GNU Public License. To
begin, download the tarball and unpack it into a directory. It requires
an ANSI C compiler, a make program, a Bourne-style shell, and gzip.
If you want to build in support for the GUI setup program, you'll
also need the FLTK library (http://www.fltk.org).
Building and Installing EPM
EPM is an autoconf-managed project, so look over the INSTALL and
README files, and then issue the command:
% ./configure
Or, do the following if you want to install it in a different location
from /usr/local:
% ./configure --prefix=<destination>
Now, build the software with:
% make
Once the software is built, however, do not do the usual "make install".
Instead, you can make EPM a package in the native format of the machine
with the following command:
% ./epm -f native epm
On some architectures (notably, HP-UX), building packages requires
root access, so you may need to be root to execute this command. The
"native" keyword here tells EPM that we want to build a package in
the native format for the machine on which we're working. It's possible
to specify different package formats (e.g., "deb" or "rpm" instead
of native), as long as the proper tools for packaging the software
exist on the system. See the EPM documentation for a list of keywords
for different package types.
After the command completes, the generated package should be inside
a directory named after the OS and architecture of the machine (e.g.,
solaris-2.8-sparc or linux-2.4-intel). Install this package using
the native package installation tools for your platform (e.g., dpkg
on Debian, or pkgadd on Solaris).
Overview of the Packaging Process
The first thing to do when building a package is to obtain the
sources and produce binaries. EPM doesn't compile the software for
you; it only packages the files for distribution.
Once you generate all the files you want packaged for distribution,
you'll need to write a configuration file that describes your package.
These files are called "listfiles" and describe meta-information
on the package itself, which files get installed, what their permissions
and ownership are, and whether there are scripts that should be
executed when the package is installed or removed.
Building a Package
For this example, we will package a copy of OpenSSH (available
at http://openssh.com), built for installation in /usr/local,
with configuration files in /etc/ssh.
Download the sources for the latest portable distribution and
unpack them into a directory. Presuming you have all the necessary
dependencies, run:
./configure --prefix=/usr/local --sysconfdir=/etc/ssh
make (or gmake)
Again, we skip the "make install" stage, because we'll be putting
the files into a package for distribution.
Creating ListFiles
EPM uses package description files called "listfiles". These files
are named with the name of the package and end with the extension
".list". For example, "openssh.list" would be the listfile describing
the "openssh" package. Listfiles are documented in epm.list(5).
Package Identification and Information
Every listfile must contain a set of required information. Here's
an example of the required info for "openssh.list":
%product OpenSSH
%copyright The OpenSSH Team, All Rights Reserved
%vendor http://www.openssh.com/
%license ./LICENSE
%readme ./README
%description Secure Shell
%version 3.6.1p2 3612
The %product, %copyright, %vendor, and %description are simple text
fields that will populate similar fields in the generated package.
%license and %readme are names of files that contain their respective
information. The %version line has two fields. The first is a human-readable
text version. The second is an integer version number. If the integer
version number is omitted, EPM will try to generate one from the human-readable
field.
Files
Next, we can start adding files and directories to be packaged.
The basic format for filesystem entities is:
type mode owner group destination source options
We'll build this package with the build directory of OpenSSH as our
current working directory. So, to add entries for the SSH program
and its manpage, we'll add these lines to our listfile:
f 755 root sys /usr/local/bin/ssh ./ssh
f 644 root sys /usr/local/man/man1/ssh.1 ssh.1.out nostrip()
By default, EPM runs the strip(1) command on all files that
it packages. The strip command removes symbols from object
files, thereby reducing the size of binary programs at the expense
of sometimes useful debugging information. To prevent this behavior,
add the nostrip() option. Since manpages are not binaries,
stripping has no effect; but on some platforms, it generates a warning.
Directories
You can also add directories to the package. Most packaging systems
will create directories for you if you install a file into a directory
that doesn't exist. However, if you want to create empty directories,
have control over permissions and ownership, or want the directories
removed on package removal, then you must specify directory entries.
Here we'll create the directory that will hold the OpenSSH configuration
files:
d 755 root sys /etc/ssh -
The "source" attribute for directory entries is always a hyphen, since
they aren't actually copied from anything.
Symbolic Links
Symbolic links are prefixed with "l" in the listfile. Here we
add the slogin link, which points to the SSH binary:
l 777 root root /usr/local/bin/slogin ssh
The source field for symlinks is interpreted as is, so you can create
relative or absolute symlinks.
Configuration Files
Some packaging systems (e.g., Debian and Red Hat) handle configuration
files differently from regular files. Others (such as HP-UX and
Solaris) make no such distinction. If your packaging system does
distinguish between configuration and regular files, then you can
declare configuration files like this:
c 644 root root /etc/ssh/ssh_config ssh_config
For packaging systems that make no distinction between the two, configuration
files are treated as normal files.
With the above line in your listfile, what happens if this file
already exists at install time? It depends on the packaging system
used. With Debian, for example, you are presented with a dialog
asking whether you want to overwrite the file. On Solaris and HP-UX,
this file is treated like any other and is overwritten.
If in doubt, create a test package for your platform and see how
it handles overwriting an existing file before relying on this feature.
Variables
Arbitrary variables are defined in listfiles by prefixing them
with a dollar sign. Since we'll be installing a lot of our files
under /usr/local, we can set a variable to that location and reference
it throughout the listfile:
$prefix=/usr/local
We can then reference this as either $prefix or ${prefix}.
If you do not use curly braces to reference them, then variables names
end at the first slash, hyphen, or whitespace.
If we use the $prefix variable above to describe the installation
prefix, our file and symlink entries in the listfile might look
like this:
f 755 root sys ${prefix}/bin/ssh ./ssh
f 644 root sys ${prefix}/man/man1/ssh.1 ssh.1.out nostrip()
l 777 root root ${prefix}/bin/slogin ssh
By using variables to describe installation locations, it becomes
easy to change the location of package files. Now, if we want to change
the package so that it installs under /usr instead of /usr/local,
all we need to do is change the $prefix variable.
Variables also make it easy to build listfiles programatically,
and when combined with conditionals (described later in this article)
make it possible to describe very complex packaging scenarios.
A Simple Example
Listing 1 is an example listfile, describing a basic SSH client
package. (Listings for this article are available from the Sys
Admin Web site at: http://www.sysadminmag.com.) After
building SSH and editing the ssh_config file to your liking, copy
this file into the OpenSSH source directory and name it ssh-client.list.
Then run:
epm -f native ssh-client
This should build the package and place it into a directory named
with a combination of your OS and architecture (e.g., "linux-2.4-intel"
or "solaris-2.8-sparc"). You can then install this package using the
proper package installation tools.
Scripts
Most packaging systems provide hooks to allow you to run arbitrary
code during the installation or removal process. EPM supports this
via the following header tags:
%preremove
%postremove
%preinstall
%postinstall
EPM can either be told to run a particular command, or scripts can
be embedded in the package. For example, this line in a listfile will
have EPM generate an SSH host key after installation:
%postinstall /usr/local/bin/ssh-keygen -q -b 1024 -t dsa -N ''
Only one script tag of each type is allowed, but you can embed scripts
that are in other files with a less-than sign:
%postinstall <${srcdir}/generate_keys
Alternatively, you can inline scripts using herefile-like syntax:
%postinstall <<EOT
printf "Generating keys: "
/usr/local/bin/ssh-keygen -q -b 1024 -t dsa -N '' -f etc/ssh/ssh_host_dsa_key
printf "done"
EOT
Package Dependencies
One great thing about using packaging systems is that it facilitates
tracking dependencies and incompatibilities. EPM allows you to declare
a package requirement using the %requires tag. For example, if we
dynamically link OpenSSH, we should require the OpenSSL package
to be installed to ensure that it will work. We'll also declare
that we need version 0.9.7:
%requires openssl 0.9.7
We may obtain packages from other places that depend on the same software
that is in our package, but under a different name. The %provides
tag allows us to "alias" our package:
%provides ssh
We can also declare an incompatibility. Here we'll declare that this
is incompatible with the kerberized version of SSH:
%incompat sshkrb5
This states that this package can replace a packaged commercial version
of SSH that is named "ssh-commercial":
%replaces ssh-commercial
Conditionals
It's possible to have sections of your listfile that are applicable
only on certain packaging systems, operating systems, or architectures.
The %format tag declares that anything following it is applicable
only for the declared packaging system types. The special name "all"
refers to all format types. In this example, anything following
this line applies only to RPM, deb, and Solaris packages:
%format rpm deb pkg
The %system tag is similar to the %format tag, but it works on the
system name as reported by a lowercased uname -s and an optional
OS version as given by uname -r. In this example, we change
the package dependency on Solaris to require the OpenSSL package provided
by sunfreeware.com; everything else will depend on a package called
"openssl":
%system solaris
%requires SMCossl
%system !solaris
%requires openssl
There are also a number of conditional tags that can operate on variables:
%if
%ifdef
%elseif
%elseifdef
%else
%endif
These are primarily of use when you have listfiles that are generated
through some automatic process.
Init Scripts
EPM treats System V init scripts a little differently. Init scripts
are declared much like normal files, but are prefixed with an "i"
instead:
i mode user group service-name source ["options"]
The service-name in this case is what the script that is installed
in the normal init script directory should be named. This varies by
platform, but on Linux and Solaris, it usually gets installed in /etc/init.d,
and on HP-UX in /sbin/init.d.
The runlevel() option lets you control what rc directories
should get symlinks that point to the init script. Any non-zero
runlevel listed will get a start symlink, whereas runlevel 0 will
get a stop symlink.
The start() and stop() options allow you to specify
at what point in the startup and shutdown process the script should
be run. Here's an example:
i 755 root root sshd sshd.rc "runlevel(02) start(82) stop(18)"
This would install the sshd.rc file as "sshd" in the init script directory.
It would then create a symlink in the rc0.d directory called K18sshd,
and one in the rc2.d directory called S82sshd. There are also some
Apple OS X specific options that handle its dependency-oriented init
procedure. See the documentation for more info on it.
A Complete Example Package
Listing 2 shows a listfile for a complete OpenSSH package, which
uses most of the features of EPM I've covered here. As before, copy
this file into the directory where you've built SSH and name it
"openssh.list". Edit the configuration files to your liking and
add any init scripts that are not part of the distribution. Then
run:
epm -f native openssh
It should generate your package and place it into the appropriate
directory.
Generating listfile Entries Automatically
Manually configuring listfiles for a large package can be very
tedious, so EPM comes with another command called mkepmlist,
which will automatically generate listfile entries for everything
under a given directory. When generating a package with a lot of
files, I often do an initial install in a temporary directory and
use mkepmlist to generate the list of files that would be
installed. I then do a search and replace to change the locations
of the files to their actual location. This is particularly useful
when installing a lot of files in a directory shared among many
packages (such as /usr or /usr/local).
Conclusion and Resources
EPM has transformed how we approach software maintenance at my
site. By making it simple to generate custom packages, we can install
software in a consistent manner across many machines as well as
track versions and dependencies. This makes it much easier to deploy
"standard" machine configurations and ensure consistency across
many machines.
The EPM site is the primary site for EPM-related news and information
(http://www.easysw.com/epm). It also contains more comprehensive
documentation on using EPM.
Major kudos and thanks to the ESP folks for making another great
software tool, and for being so helpful via their newsgroups and
mailing lists.
Jeff Layton has been working with computers since he got his
paws on a TRS-80 in 1981 at age 10. After working in the Macintosh
and PC realm for more than a decade and attaining a degree in Physics
Education, he came to his senses and made the jump to Unix administration
in 1996. He is currently employed as a Senior Unix Systems Administrator
at Bowe Bell and Howell.
|