Skip to main content

Packaging software with RPM, Part 3

Running scripts at install and uninstall

Dan Poirier (poirier@us.ibm.com), Software engineer, IBM
Dan Poirier is an Advisory Software Engineer at IBM. He currently works in Research Triangle Park, North Carolina, on network appliances that run Linux. Contact Dan at poirier@us.ibm.com.

Summary:  RPM is a widely-used tool for delivering software for Linux. Users can easily install an RPM-packaged product. In this article, third in a series, Dan explains how to run scripts when your package is installed or uninstalled, or when other packages are installed or uninstalled.

Date:  01 Feb 2002
Level:  Intermediate
Activity:  8166 views

It's often useful to be able to execute commands when your program is installed or uninstalled on a user's machine. For example, you might need to edit a system configuration file to enable a new service, or need to define a new user to own the program that you're installing.

To get the most out of the advice and examples in this article, take a look at Part 1 and Part 2 of this series, which show you how to use RPM and how to distribute your work, respectively.

How install and uninstall scripts work

Install and uninstall scripts seem simple, but a few surprises in how they work can cause big problems.

Here are the basics. You can add any of the following four sections to your .spec file, which lists shell scripts to run at various points in the install life of your package:

%pre
Runs before the package is installed
%post
Runs after the package is installed
%preun
Runs before the package is uninstalled
%postun
Runs after the package is uninstalled

In particular, note the difference between %install and these sections. %install runs on your development machine when you are building the RPM; it should install the product on your development machine, or into a build root. These sections, on the other hand, specify what will run on the user's machine when the user is installing or uninstalling your RPM package.

Here's an example, building on the previous articles. We want to use install-info to add GNU indent's info file to the directory.


Listing 1. indent-4.spec

# Simplistic example of install scripts - do not use
Summary: GNU indent
Name: indent
Version: 2.2.6
Release: 4
Source0: %{name}-%{version}.tar.gz
License: GPL
Group: Development/Tools
BuildRoot: %{_builddir}/%{name}-root

%description
The GNU indent program reformats C code to any of a variety of
formatting standards, or you can define your own.

%prep
%setup -q

%build
./configure
make

%install
rm -rf $RPM_BUILD_ROOT
make DESTDIR=$RPM_BUILD_ROOT install

%post
if [ -x /sbin/install-info ]; then
  /sbin/install-info /usr/local/info/indent.info /usr/local/info/dir
fi

%preun
if [ -x /sbin/install-info ]; then
  /sbin/install-info --delete /usr/local/info/indent.info /usr/local/info/dir
fi

%clean
rm -rf $RPM_BUILD_ROOT

%files
%defattr(-,root,root)
/usr/local/bin/indent
%doc /usr/local/info/indent.info
%doc %attr(0444,root,root) /usr/local/man/man1/indent.1
%doc COPYING AUTHORS README NEWS

Note first that we check for the install-info tool before trying to use it. We don't want our install to fail just because we cannot provide a link to the product documentation.

But there might be cases where you would like an installation to fail. A good technique is to use the %pre script to check for installation prerequisites that are more complex than what RPM can support directly. If the prerequisites are not met, the script exits with a non-zero status, and RPM does not continue with the installation.

Notice also that we are careful to undo our install script using an uninstall script.


Not so simple: upgrades complicate everything

Now let's turn to upgrades. The instructions in the previous section work fine if users only install and delete your package; they fail badly during upgrades.

Here's how RPM performs an upgrade:

  • Run %pre of new package
  • Install new files
  • Run %post of new package
  • Run %preun of old package
  • Delete any old files not overwritten by newer ones
  • Run %postun of old package

If we do an upgrade using our previous example, the last thing that RPM runs is the %postun script, which removes all the work we did in our install script! This is not exactly what the average RPM-using developer would have guessed. I won't try to explain the reasons for this, just what you have to do about it.

Luckily, there is a way for a script to tell if the package is being installed, deleted, or upgraded -- sort of. Each script is passed a single command-line argument, a number. This is supposed to tell the script how many copies of the package will be installed after the current package has finished installing or uninstalling.

Rather than trying to figure this out, it's probably easier to just look at the values that get passed in various cases.

Here are the actual values passed during an install:

  • Run %pre of new package (1)
  • Install new files
  • Run %post of new package (1)

Here are the values passed during an upgrade:

  • Run %pre of new package (2)
  • Install new files
  • Run %post of new package (2)
  • Run %preun of old package (1)
  • Delete any old files not overwritten by newer ones
  • Run %postun of old package (1)

Here are the values passed during a delete:

  • Run %preun of old package (0)
  • Delete files
  • Run %postun of old package (0)

You can test this yourself by adding something like the next example to your package. Then make a new package with a slightly higher release number, and you can install the first, then upgrade to the second, and finally uninstall it, to see all the possibilities. And of course, you will always want to try this several times yourself before releasing any RPM on an unsuspecting public.


Listing 2. Test order and arguments for script execution

%pre
echo This is pre for %{version}-%{release}: arg=$1

%post
echo This is post for %{version}-%{release}: arg=$1

%preun
echo This is preun for %{version}-%{release}: arg=$1

%postun
echo This is postun for %{version}-%{release}: arg=$1

Here's another example, this time coping correctly with the upgrade process:


Listing 3. indent-5.spec

Summary: GNU indent
Name: indent
Version: 2.2.6
Release: 5
Source0: %{name}-%{version}.tar.gz
License: GPL
Group: Development/Tools
BuildRoot: %{_builddir}/%{name}-root

%description
The GNU indent program reformats C code to any of a variety of
formatting standards, or you can define your own.

%prep
%setup -q

%build
./configure
make

%install
rm -rf $RPM_BUILD_ROOT
make DESTDIR=$RPM_BUILD_ROOT install

%post
if [ "$1" = "1" ] ; then  # first install
 if [ -x /sbin/install-info ]; then
   /sbin/install-info /usr/local/info/indent.info /usr/local/info/dir
 fi
fi

%preun
if [ "$1" = "0" ] ; then # last uninstall
 if [ -x /sbin/install-info ]; then
   /sbin/install-info --delete /usr/local/info/indent.info /usr/local/info/dir
 fi
fi

%clean
rm -rf $RPM_BUILD_ROOT

%files
%defattr(-,root,root)
/usr/local/bin/indent
%doc /usr/local/info/indent.info
%doc %attr(0444,root,root) /usr/local/man/man1/indent.1
%doc COPYING AUTHORS README NEWS

Now the info link will be removed only when the package is being completely deleted.


Triggers -- running scripts when other packages are installed or uninstalled

Suppose you want to run some code in your package when other packages are installed or uninstalled. You can do it with trigger scripts.

Why would you want to do this? Usually because your package uses the services of one or more other packages, or provides services to one or more other packages.

Here's an example. Suppose you're packaging up a nifty add-on tool for the Emacs and Xemacs editors. It can work with either or both, but you need to do some minor configuration depending on which editors are installed.

At install time, you can test for Emacs and Xemacs and configure your tool to be accessible by the available editors. But what happens if the user installs Xemacs later? Your tool won't be usable in Xemacs unless the user uninstalls and reinstalls your tool. Wouldn't it be better if your package could tell RPM, "let me know if Xemacs gets installed"?

That's the idea of trigger scripts. You can add this to your .spec file:


Listing 4. Trigger example

%triggerin -- emacs
# Insert code here to run if your package is already installed,
# then emacs is installed,
# OR if emacs is already installed, then your package is installed

%triggerin -- xemacs
# Insert code here to run if your package is already installed,
# then xemacs is installed,
# OR if xemacs is already installed, then your package is installed

%triggerun -- emacs
# insert code here to run if your package is already installed,
# then emacs is uninstalled

%triggerun -- xemacs
# insert code here to run if your package is already installed,
# then xemacs is uninstalled

%postun
# Insert code here to run if your package is uninstalled

Trigger scripts are passed two arguments. The first argument is the number of instances of your package that will be installed when your trigger script has completed running. The second argument is the number of instances of the triggering package that will be installed when your trigger script has completed running.

From the triggers file in the RPM distribution, here's the complete ordering of script executions and file installs and uninstalls during an RPM upgrade:


Listing 5. Script ordering

  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)


Advanced scripting

Alternate interpreters


Ordinarily, all install-time scripts and trigger scripts are run using the /bin/sh shell program. If you prefer another scripting language, say Perl, you can tell RPM that your script should be run using another interpreter by adding -p interpreter to the script line. For example:


Listing 6. Example of alternate interpreters

%post -p /usr/bin/perl
# Perl script here

%triggerun -p /usr/bin/perl -- xemacs
# Another Perl script here

Note that this does not apply to the build-time scripts of RPM, such as %install.

RPM variables

RPM expands RPM variables in your scripts before storing them into the RPM package file, which can be useful sometimes. For example, you can define your own variables near the top of your .spec file, and then refer to them using %{variable_name} all through your .spec file -- and even in your scripts:


Listing 7. Example of RPM variables

...
%define foo_dir /usr/lib/foo
...

%install
cp install.time.message $RPM_BUILD_ROOT/%{foo_dir}

%files
%{foo_dir}/install.time.message

%post
/bin/cat %{foo_dir}/install.time.message


Things to avoid

You might be tempted to do some things at install time that turn out to be a bad idea. For example, any attempt to interact with the user probably won't work very well. RPM is designed to allow batch installs, during which no user is necessarily present. If an RPM package stops during an install to ask a question, and no one sees the question, the install will appear to just hang forever.

Another thing you probably want to avoid is starting any services. During a complete installation, you can't be sure if everything that your program needs is there already (for example, there might not be any network yet); also, if every RPM service tried to start during a complete operating system install, the entire install would probably take significantly longer. What you can do in such cases is print a message telling the user about any required configuration or service that needs to be started. If a user is installing your RPM package manually, he or she will see it; if it's part of a larger batch install, it won't hurt anything, and the machine will almost certainly be rebooted at the end, starting your service.


Things to remember

If your package installs an init script, you can use chkconfig to arrange for your service to be started and stopped in the appropriate run-levels. Though you can accomplish the same thing by installing the necessary symbolic links directly as part of your package, getting them right is a pesky detail that you might prefer to leave to chkconfig.

A number of services run under a specific user id for security; if yours does, you will need to create that user on the system if it doesn't already exist.

If your package installs any GNU info files, they won't be visible in the Info directory unless you use the install-info tool to add them at install time.

Before uninstalling, you should almost certainly attempt to stop any services your package might be running (but be sure the uninstall won't fail if the service isn't running).

At uninstall, of course you should reverse most changes you might have made to the system at install-time. But give a little thought to your actions; uninstalling an RPM package should not, for example, accidentally delete any user-created files. So you might be better off not trying to remove a user ID, or deleting entire directory trees.


Resources

  • Part 1 in this RPM series introduces the process for building software packages with RPM, and Part 2 explains how to build without root, patch software, and distribute RPM.



  • Some Linux distributions use Debian's APT tool for package management.



  • The RPM Web site has pointers to many useful resources. The RPM e-mail list is a good place to ask questions.



  • Maximum RPM is a book about using RPM. It has become quite outdated, but an effort is underway to update it.



  • The RPM HOWTO is also getting somewhat dated. It covers some of the same ground as this article.



  • Eric S. Raymond's Software Release Practice HOWTO document is not specific to RPM or Linux. But it does give many good tips on releasing software in a way that makes it easy for users to use and programmers to contribute fixes and improvements.



  • The Free Software Foundation is the source for GNU Indent and many other useful software packages -- including glibc and emacs.



  • Browse more Linux resources on developerWorks.



  • Browse more Open source resources on developerWorks.

About the author

Dan Poirier is an Advisory Software Engineer at IBM. He currently works in Research Triangle Park, North Carolina, on network appliances that run Linux. Contact Dan at poirier@us.ibm.com.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Linux
ArticleID=11194
ArticleTitle=Packaging software with RPM, Part 3
publish-date=02012002
author1-email=poirier@us.ibm.com
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers