Scriptlets
RPM spec files have several sections which allow packages to run code on installation and removal. These bits of code are called scriptlets and are mostly used to update the running system with information from the package. This page offers a quick overview of RPM scriptlets and a number of common recipes for scriptlets in packages. For a more complete treatment of scriptlets, please see the Maximum RPM book.
The version of RPM in Fedora also has functionality to automatically run scripts when files are placed in certain locations. (See 1.) This potentially obviates the need for most of the scriptlets on this page, but is not currently implemented in all cases where it could be.
Default Shell
In Fedora, all scriptlets can safely assume they are running under the bash shell unless a different language has been specified.
Syntax
The basic syntax is similar to the %build, %install, and other sections of the rpm spec file. The scripts support a special flag, -p which allows the scriptlet to invoke a single program directly rather than having to spawn a shell to invoke the programs. (i.e., %post -p /sbin/ldconfig)
When scriptlets are called, they will be supplied with an argument. This argument, accessed via $1 (for shell scripts) is the number of packages of this name which will be left on the system when the action completes, except for %pretrans and %posttrans which are always run with $1 as 0. So for the common case of install, upgrade, and uninstall we have:
install |
upgrade |
uninstall |
|
%pretrans |
$1 == 0 |
$1 == 0 |
(N/A) |
%pre |
$1 == 1 |
$1 == 2 |
(N/A) |
%post |
$1 == 1 |
$1 == 2 |
(N/A) |
%preun |
(N/A) |
$1 == 1 |
$1 == 0 |
%postun |
(N/A) |
$1 == 1 |
$1 == 0 |
%posttrans |
$1 == 0 |
$1 == 0 |
(N/A) |
Note that these values will vary if there are multiple versions of the same package installed (This mostly occurs with parallel installable packages such as the kernel and multilib packages. However, it can also occur when errors prevent a package upgrade from completing.) So it is a good idea to use this construct:
%pre if [ $1 -gt 1 ] ; then fi
…for %pre and %post scripts rather than checking that it equals 2.
All scriptlets MUST exit with the zero exit status. Because RPM in its
default configuration does not execute shell scriptlets with the -e
argument to the shell, excluding explicit exit
calls (frowned upon with
a non-zero argument!), the exit status of the last command in a scriptlet
determines its exit status. Most commands in the snippets in this document
have a “|| :” appended to them, which is a generic trick to force the
zero exit status for those commands whether they worked or not. Usually the
most important bit is to apply this to the last command executed in a
scriptlet, or to add a separate command such as plain “:” or “exit
0” as the last one in a scriptlet. Note that depending on the case, other
error checking/prevention measures may be more appropriate.
Non-zero exit codes from scriptlets can break installs/upgrades/erases such that no further actions will be taken for that package in a transaction (see Ordering), which may for example prevent an old version of a package from being erased on upgrades, leaving behind duplicate rpmdb entries and possibly stale, unowned files on the filesystem. There are some cases where letting the transaction to proceed when some things in scriptlets failed may result in partially broken setup. It is however often limited to that package only whereas letting a transaction to proceed with some packages dropped out on the fly is more likely to result in broader system wide problems.
Ordering
The scriptlets in %pre and %post are respectively run before and after a package is installed. The scriptlets %preun and %postun are run before and after a package is uninstalled. The scriptlets %pretrans and %posttrans are run at start and end of a transaction. On upgrade, the scripts are run in the following order:
-
%pretrans of new package
-
%pre of new package
-
(package install)
-
%post of new package
-
%triggerin of other packages (set off by installing new package)
-
%triggerin of new package (if any are true)
-
%triggerun of old package (if it’s set off by uninstalling the old package)
-
%triggerun of other packages (set off by uninstalling old package)
-
%preun of old package
-
(removal of old package)
-
%postun of old package
-
%triggerpostun of old package (if it’s set off by uninstalling the old package)
-
%triggerpostun of other packages (if they’re set off by uninstalling the old package)
-
%posttrans of new package
The %pretrans Scriptlet
Note that the %pretrans
scriptlet will, in the particular case of system
installation, run before anything at all has been installed. This implies
that it cannot have any dependencies at all. For this reason, %pretrans
is best avoided, but if used it MUST (by necessity) be written in Lua. See
http://rpm.org/user_doc/lua.html for more information.
Writing Scriptlets
Some tips for writing good scriptlets
Saving state between scriptlets
Sometimes a scriptlet needs to save some state from an earlier running scriptlet in order to use it at a later running scriptlet. This is especially common when trying to optimize the scriptlets. Examples:
-
If a
%posttrans
needs to de-register some piece of information when upgrading but the file that has that information is removed when the old package is removed the scriptlets need to save that file during%pre
or%post
so that the script in%posttrans
can access it. -
If we only want the program in
%posttrans
to do its work once per-transaction, we may need to write out a flag file so that the%posttrans
knows whether to perform an action.
To address these issues scriptlets that run earlier need to write out
information that is used in %posttrans
. We recommend using a
subdirectory of %{_localstatedir}/lib/rpm-state/
for that. For instance,
the eclipse plugin scripts touch a file in
%{_localstatedir}/lib/rpm-state/eclipse/
when they’re installed. The
%posttrans
runs a script that checks if that file exists. If it does, it
performs its action and then deletes the file. That way the script only
performs its action once per transaction.
Macros
If RPM file triggers are not appropriate, complex scriptlets which are shared between multiple packages MAY be placed in RPM macros. This has two benefits:
-
The standard package authors only have to remember the macros, not the complex stuff that it does
-
The macros' implementations may change without having to update the package
When writing the macros, the FPC will still want to review the macros (and perhaps include the implementation of the macros in the guideline to show packagers what’s happening behind the scenes).
One principle that the FPC follows is that macros generally don’t contain
the start of scriptlet tags (for instance, %pre
) because this makes it
difficult to do additional work in the scriptlet. This also means that a
single macro can not be defined to do things in both %pre
and
%post
. Instead, write one macro that performs the actions in %pre
and a separate macro that performs the actions in %post
. This principle
makes it so that all spec files can use your macros in the same manner even
if they already have a %pre
or %post
defined.
Of course, in the above situation it is better to use RPM file triggers if at all possible.
Snippets
Linker Configuration Files
Packages which place linker configuration files in /etc/ld.so.conf.d
MUST call ldconfig in %post
and %postun
(on all Fedora releases)
even if they install no actual libraries. They MUST NOT use the
%ldconfig
, %ldconfig_post
, %ldconfig_postun
or
%ldconfig_scriptlets
macros to do this, since these macros do not have
any effect on Fedora 28 and newer. Instead simply call /sbin/ldconfig
directly in both %post
and %postun
as well as adding the necessary
dependencies when necessary:
%post -p /sbin/ldconfig %postun -p /sbin/ldconfig
or, as part of existing %post
or %postun
scriptlets:
Requires(post): /sbin/ldconfig Requires(postun): /sbin/ldconfig [...] %post [...] /sbin/ldconfig [...] %postun [...] /sbin/ldconfig [...]
In addition, in Fedora 28 or newer the following applies: If the
configuration file added to /etc/ld.so.conf.d
specifies a directory into
which other other packages may install files, and that directory is not
located in the directory hierarchy beneath one of /lib
, /usr/lib
,
/lib64
or /usr/lib64
, then the package adding the configuration file
MUST also include the following file triggers which cause ldconfig to be run
automatically when necessary:
%transfiletriggerin -P 2000000 -- DIRECTORIES /sbin/ldconfig %transfiletriggerpostun -P 2000000 -- DIRECTORIES /sbin/ldconfig
Replace DIRECTORIES
with the space-separated list of directories which
the package adds to the library search path via the configuration files in
/etc/ld.so.conf.d
.
Users and groups
These are discussed on a separate page
GConf
GConf is a configuration scheme currently used by the GNOME desktop. Programs which use it setup default values in a [NAME] .schemas file which is installed under %{_sysconfdir}/gconf/schemas/[NAME] .schemas. These defaults are then registered with the gconf daemon which monitors the configuration values and alerts applications when values the applications are interested in change. The schema files also provide documentation about what each value in the configuration system means (which gets displayed when you browse the database in the gconf-editor program).
For packaging purposes, we have to disable schema installation during build, and also register the values in the [NAME] .schemas file with the gconf daemon on installation and unregister them on removal. Due to the ordering of the scriptlets, this is a four step process.
Disabling the GConf installation during the package creation can be done like so:
%install export GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL=1 make install DESTDIR=$RPM_BUILD_ROOT ...
The GCONF_DISABLE_MAKEFILE_SCHEMA_INSTALL environment variable suppresses the installation of the schema during the building of the package. An alternative for some packages is to pass a configure flag:
%build %configure --disable-schemas ...
Unfortunately, this configure switch only works if the upstream packager has adapted their Makefile.am to handle it. If the Makefile.am is not configured, this switch won’t do anything and you’ll need to use the environment variable instead.
Here’s the second part:
BuildRequires: GConf2 Requires(pre): GConf2 Requires(post): GConf2 Requires(preun): GConf2 ... %pre %gconf_schema_prepare schema1 schema2 %gconf_schema_obsolete schema3
In this section we uninstall old schemas during upgrade using one of two macros.
%gconf_schema_prepare
is used for any current GConf schemas. It takes
care of uninstalling previous versions of schemas that this package
currently installs. It takes a space separated list of schema names without
path or suffix that the package installs. Note that behind the scenes, this
macro works with the %post
scriptlet to only process GConf schemas if
changes have occurred.
%gconf_schema_obsolete
is used for schemas that this package previously
provided but no longer does. It will deregister the old schema if it is
present on the system. Nothing will happen if the old schema is not
present. This macro takes a space separated list of schemas to
uninstall. One example of using this might be if the package changed
names. If the old schema was named foo.schemas
and the new schema is
named foobar.schemas
you’d use:
%gconf_schema_prepare foobar %gconf_schema_obsolete foo
The next section does the processing of the newly installed schemas:
%post %gconf_schema_upgrade schema1 schema2
%gconf_schema_upgrade
takes a space separated list of schemas that the
package currently installs just like %gconf_schema_prepare
. Behind the
scenes, it does the actual work of registering the new version of the schema
and deregistering the old version.
The last section is for unregistering schemas when a package is removed:
%preun %gconf_schema_remove schema1 schema2
When a package is upgraded rpm invokes the %pre
scriptlet to register
and deregister the schemas. When a package is uninstalled, the %preun
scriptlet is used. %gconf_schema_remove
takes the list of schemas that
this package currently provides and removes them for us.
Systemd
Packages containing systemd unit files need to use scriptlets to ensure proper handling of those services. Services can either be enabled or disabled by default. To determine which case your specific service falls into, please refer to FESCo’s policy here. On upgrade, a package may only restart a service if it is running; it may not start it if it is off. Also, the service may not enable itself if it is currently disabled.
Scriptlets
The systemd package provides a set of helper macros to handle systemd scriptlet operations. These macros support systemd "presets", as documented in systemd.preset(5).
BuildRequires: systemd-rpm-macros [...] %post %systemd_post apache-httpd.service %preun %systemd_preun apache-httpd.service %postun %systemd_postun_with_restart apache-httpd.service
Some services do not support being restarted (e.g. D-Bus and various storage
daemons). If your service should not be restarted upon upgrade, then use the
following %postun
scriptlet instead of the one shown above:
%postun %systemd_postun apache-httpd.service
If your package includes one or more systemd units that need to be enabled by default on package installation, they MUST be covered by the Fedora preset policy.
User units
There are additional macros for user units (those installed under
%_userunitdir
) that should be used similarly to those for system
units. These enable and disable user units according to presets, and are
%systemd_user_post
(to be used in %post
) and %systemd_user_preun
(to be used in %preun
).
BuildRequires: systemd-rpm-macros [...] %post %systemd_user_post %{name}.service %preun %systemd_user_preun %{name}.service
Dependencies on the systemd package
If package scriptlets call other systemd tools, for example
systemd-tmpfiles
, the package SHOULD declare appropriate dependencies. The
%systemd_requires
macro is a shortcut to require systemd for the %pre
,
%post
, and %postun
scriptlets. Note that those dependencies are not
required for the
%systemd_{post,preun,postun_with_restart,user_post,user_preun}
macros
listed above.
If the package wants to use systemd tools if they are available, but does
not want to declare a dependency, then the %systemd_ordering
macro MAY be
used as a weaker form of %systemd_requires
that only declares an ordering
during an RPM transaction.
Macro details
For details on what these macros evaluate to, refer to the following sources: macros.systemd.in, triggers.systemd.in and daemon(7).
Shells
/etc/shells
is a text file which controls whether an application can be
used as a system login shell of users. It contains the set of valid shells
which can be used in the system. If you are packaging a new shell, you need
to add entries to this file that reference the added shells. See: man 5
SHELLS
for more information.
As this file can be edited by sysadmins, we need to first determine if
relevant lines are already in the file. If they don’t already exist then we
just need to echo the shell’s binary path to the file. Since the UsrMove
Feature in Fedora 17 made /bin
a symlink to /usr/bin
we need to
place both paths into the /etc/shells
file. Here is an example of the
scriptlet to package with shell named "foo":
%post if [ "$1" = 1 ]; then if [ ! -f %{_sysconfdir}/shells ] ; then echo "%{_bindir}/foo" > %{_sysconfdir}/shells echo "/bin/foo" >> %{_sysconfdir}/shells else grep -q "^%{_bindir}/foo$" %{_sysconfdir}/shells || echo "%{_bindir}/foo" >> %{_sysconfdir}/shells grep -q "^/bin/foo$" %{_sysconfdir}/shells || echo "/bin/foo" >> %{_sysconfdir}/shells fi %postun if [ "$1" = 0 ] && [ -f %{_sysconfdir}/shells ] ; then sed -i '\!^%{_bindir}/foo$!d' %{_sysconfdir}/shells sed -i '\!^/bin/foo$!d' %{_sysconfdir}/shells fi