Скриптлеты

Spec-файлы RPM имеют несколько разделов, позволяющих пакетам выполнять код при установке и удалении. Эти фрагменты кода называются скриптлетами и в основном используются для обновления работающей системы информацией из пакета. На этой странице представлен краткий обзор скриптлетов RPM и ряд типовых рецептов для скриптлетов в пакетах. Для более полного описания скриптлетов см. книгу Maximum RPM.

Версия RPM в Fedora также имеет функциональность автоматического запуска скриптов, когда файлы помещаются в определённые места. (См. 1.) Это потенциально устраняет необходимость в большинстве скриптлетов на этой странице, но в настоящее время реализовано не во всех случаях, где это возможно.

Пакеты НЕ ДОЛЖНЫ полагаться на запись в stdout / stderr в скриптлетах RPM для передачи пользователю несмертельных проблем, предупреждений или других сообщений.

Оболочка по умолчанию

В Fedora все скриптлеты могут безопасно предполагать, что они выполняются в оболочке bash, если не указан другой язык.

Синтаксис

Базовый синтаксис аналогичен разделам %build, %install и другим разделам spec-файла rpm. Скрипты поддерживают специальный флаг -p, который позволяет скриптлету напрямую вызывать одну программу вместо того, чтобы порождать оболочку для вызова программ. (т. е. %post -p /usr/bin/ldconfig)

При вызове скриптлетам передаётся аргумент. Этот аргумент, доступный через $1 (для shell-скриптов), — это количество пакетов с этим именем, которые останутся в системе после завершения действия. Таким образом, для распространённых случаев установки, обновления и удаления имеем:

установка

обновление

удаление

%pretrans

$1 == 1

$1 == 2

(Н/Д)

%pre

$1 == 1

$1 == 2

(Н/Д)

%post

$1 == 1

$1 == 2

(Н/Д)

%preun

(Н/Д)

$1 == 1

$1 == 0

%postun

(Н/Д)

$1 == 1

$1 == 0

%posttrans

$1 == 1

$1 == 2

(Н/Д)

Обратите внимание, что эти значения будут различаться, если установлено несколько версий одного и того же пакета (в основном это происходит с параллельно устанавливаемыми пакетами, такими как ядро и multilib-пакеты. Однако это также может произойти, когда ошибки препятствуют завершению обновления пакета.) Поэтому хорошей идеей является использование такой конструкции:

%pre
if [ $1 -gt 1 ] ; then
fi

для скриптов %pre и %post, а не проверять, что оно равно 2.

Все скриптлеты ДОЛЖНЫ завершаться с нулевым кодом выхода. Поскольку RPM в своей конфигурации по умолчанию не выполняет shell-скриптлеты с аргументом -e для оболочки, за исключением явных вызовов exit (что неприветствуется с ненулевым аргументом!), код выхода последней команды в скриптлете определяет его код выхода. Большинство команд в фрагментах в этом документе имеют добавленное "|| :", что является универсальным приёмом для принудительного получения нулевого кода выхода для этих команд независимо от того, сработали они или нет. Обычно наиболее важно применить это к последней выполняемой команде в скриптлете или добавить отдельную команду, такую как простой ":" или "exit 0", в качестве последней в скриптлете. Обратите внимание, что в зависимости от случая другие меры проверки/предотвращения ошибок могут быть более уместны.

Ненулевые коды выхода из скриптлетов могут нарушить установку/обновление/удаление так, что для этого пакета в транзакции больше не будет предприниматься никаких действий (см. [Ordering]), что может, например, помешать удалению старой версии пакета при обновлении, оставив дублирующиеся записи в rpmdb и, возможно, устаревшие, никому не принадлежащие файлы в файловой системе. Есть случаи, когда позволение транзакции продолжиться, когда что-то в скриптлетах не удалось, может привести к частично сломанной установке. Однако часто это ограничивается только этим пакетом, тогда как позволение транзакции продолжиться с выпавшими на лету некоторыми пакетами скорее приведёт к более широким общесистемным проблемам.

Порядок

Скриптлеты в %pre и %post выполняются соответственно до и после установки пакета. Скриптлеты %preun и %postun выполняются до и после удаления пакета. Скриптлеты %pretrans и %posttrans выполняются в начале и в конце транзакции. При обновлении скрипты выполняются в следующем порядке:

  1. %pretrans нового пакета

  2. %pre нового пакета

  3. (установка пакета)

  4. %post нового пакета

  5. %triggerin других пакетов (запускается установкой нового пакета)

  6. %triggerin нового пакета (если какие-либо истинны)

  7. %triggerun старого пакета (если он запускается удалением старого пакета)

  8. %triggerun других пакетов (запускается удалением старого пакета)

  9. %preun старого пакета

  10. (удаление старого пакета)

  11. %postun старого пакета

  12. %triggerpostun старого пакета (если он запускается удалением старого пакета)

  13. %triggerpostun других пакетов (если они запускаются удалением старого пакета)

  14. %posttrans нового пакета

Скриптлет %pretrans

Обратите внимание, что скриптлет %pretrans в частности при установке системы будет выполнен до того, как вообще что-либо было установлено. Это означает, что он не может иметь никаких зависимостей вообще. По этой причине %pretrans лучше избегать, но если он используется, он ДОЛЖЕН (по необходимости) быть написан на Lua. См. https://rpm-software-management.github.io/rpm/manual/lua.html для получения дополнительной информации.

Написание скриптлетов

Вот несколько советов по написанию хороших скриптлетов:

Сохранение состояния между скриптлетами

Иногда скриптлету нужно сохранить некоторое состояние из ранее выполненного скриптлета, чтобы использовать его в позже выполняемом скриптлете. Это особенно часто встречается при попытке оптимизировать скриптлеты. Примеры:

  • Если %posttrans должен отменить регистрацию некоторой информации при обновлении, но файл, содержащий эту информацию, удаляется при удалении старого пакета, скриптлетам нужно сохранить этот файл во время %pre или %post, чтобы скрипт в %posttrans мог получить к нему доступ.

  • Если мы хотим, чтобы программа в %posttrans выполняла свою работу только один раз за транзакцию, нам может понадобиться записать файл-флаг, чтобы %posttrans знал, выполнять ли действие.

Для решения этих проблем скриптлеты, выполняемые раньше, должны записывать информацию, используемую в %posttrans. Мы рекомендуем использовать для этого подкаталог %{_localstatedir}/lib/rpm-state/. Например, скрипты плагина eclipse при установке создают файл в %{_localstatedir}/lib/rpm-state/eclipse/. %posttrans запускает скрипт, который проверяет, существует ли этот файл. Если да, он выполняет своё действие и затем удаляет файл. Таким образом скрипт выполняет своё действие только один раз за транзакцию.

Макросы

Если триггеры файлов RPM не подходят, сложные скриптлеты, разделяемые между несколькими пакетами, МОГУТ быть помещены в макросы RPM. Это имеет два преимущества:

  • Авторам стандартных пакетов нужно помнить только макросы, а не сложные вещи, которые они делают

  • Реализации макросов могут изменяться без необходимости обновлять пакет

При написании макросов FPC всё равно захочет проверить макросы (и, возможно, включить реализацию макросов в руководство, чтобы показать упаковщикам, что происходит за кулисами).

Один из принципов, которого придерживается FPC, заключается в том, что макросы обычно не содержат начала тегов скриптлетов (например, %pre), потому что это затрудняет выполнение дополнительной работы в скриптлете. Это также означает, что один макрос не может быть определён для выполнения действий как в %pre, так и в %post. Вместо этого напишите один макрос, выполняющий действия в %pre, и отдельный макрос, выполняющий действия в %post. Этот принцип позволяет всем spec-файлам использовать ваши макросы одинаковым образом, даже если у них уже определён %pre или %post.

Конечно, в описанной выше ситуации лучше использовать триггеры файлов RPM, если это вообще возможно.

Фрагменты

Некоторые скриптлеты для использования в конкретных ситуациях.

Конфигурационные файлы компоновщика

Пакеты, помещающие конфигурационные файлы компоновщика в /etc/ld.so.conf.d, ДОЛЖНЫ вызывать ldconfig в %post и %postun (во всех выпусках Fedora), даже если они не устанавливают никаких фактических библиотек. Они НЕ ДОЛЖНЫ использовать макросы %ldconfig, %ldconfig_post, %ldconfig_postun или %ldconfig_scriptlets для этого, поскольку эти макросы не оказывают никакого влияния на Fedora. Вместо этого просто вызовите ldconfig напрямую как в %post, так и в %postun, а также добавьте необходимые зависимости, когда это необходимо:

%post -p /usr/bin/ldconfig
%postun -p /usr/bin/ldconfig

или как часть существующих скриптлетов %post или %postun:

Requires(post): /usr/bin/ldconfig
Requires(postun): /usr/bin/ldconfig
[...]
%post
[...]
ldconfig
[...]
%postun
[...]
ldconfig
[...]

Если конфигурационный файл, добавленный в /etc/ld.so.conf.d, указывает каталог, в который другие пакеты могут устанавливать файлы, и этот каталог не находится в иерархии каталогов под одним из /lib, /usr/lib, /lib64 или /usr/lib64, тогда пакет, добавляющий конфигурационный файл, ДОЛЖЕН также включить следующие триггеры файлов, которые заставляют ldconfig запускаться автоматически при необходимости:

%transfiletriggerin -P 2000000 -- DIRECTORIES
ldconfig

%transfiletriggerpostun -P 2000000 -- DIRECTORIES
ldconfig

Замените DIRECTORIES на разделённый пробелами список каталогов, которые пакет добавляет в путь поиска библиотек через конфигурационные файлы в /etc/ld.so.conf.d.

Пользователи и группы

Они обсуждаются на отдельной странице.

GConf

Устаревшая система конфигурации GNOME 2 (всё ещё используемая лишь несколькими пакетами) задокументирована для потомков на другой странице.

Systemd

Пакеты, содержащие файлы юнитов systemd, должны использовать скриптлеты для обеспечения правильной обработки этих служб. Службы могут быть либо включены, либо отключены по умолчанию. Чтобы определить, к какому случаю относится ваша конкретная служба, обратитесь к политике FESCo здесь. При обновлении пакет может только перезапустить службу, если она работает; он не может запустить её, если она выключена. Также служба не может включить себя, если она в данный момент отключена.

Скриптлеты

Пакет systemd предоставляет набор вспомогательных макросов для обработки операций скриптлетов systemd. Эти макросы поддерживают «пресеты» systemd, как описано в 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

Некоторые службы не поддерживают перезапуск (например, D-Bus и различные демоны хранилищ). Если ваша служба не должна перезапускаться при обновлении, а вместо этого должна перезагружаться, используйте следующий скриптлет %postun вместо показанного выше:

%postun
%systemd_postun_with_reload apache-httpd.service

Если ваша служба не должна перезапускаться или перезагружаться, используйте вместо этого следующий скриптлет %postun:

%postun
%systemd_postun apache-httpd.service

Эти макросы принимают несколько аргументов с именами юнитов. Лучше использовать один вызов, чтобы уменьшить количество вызовов.

Если ваш пакет включает один или несколько юнитов systemd, которые должны быть включены по умолчанию при установке пакета, они ДОЛЖНЫ быть охвачены политикой пресетов Fedora.

Пользовательские юниты

Существуют дополнительные макросы для пользовательских юнитов (тех, что устанавливаются в %_userunitdir), которые следует использовать аналогично макросам для системных юнитов. Они включают и отключают пользовательские юниты в соответствии с пресетами и представляют собой %systemd_user_post (для использования в %post) и %systemd_user_preun (для использования в %preun).

BuildRequires: systemd-rpm-macros

[...]
%post
%systemd_user_post %{name}.service

%preun
%systemd_user_preun %{name}.service

%postun
%systemd_user_postun_with_restart %{name}.service
%systemd_user_postun_with_reload %{name}.service
%systemd_user_postun %{name}.service

Макросы %systemd_user_postun_with_restart и %systemd_user_postun_with_reload перебирают работающие экземпляры менеджера пользователя и запрашивают операции перезапуска и перезагрузки для указанных юнитов в каждом из них.

Зависимости от пакета systemd

Если скриптлеты пакета вызывают другие инструменты systemd, например systemd-tmpfiles, пакет ДОЛЖЕН объявить соответствующие зависимости. Макрос %{?systemd_requires} — это сокращение для требования systemd для скриптлетов %post, %preun и %postun. Обратите внимание, что эти зависимости не требуются для макросов %systemd_{post,preun,postun_with_restart,user_post,user_preun}, перечисленных выше.

Если пакет хочет использовать инструменты systemd, если они доступны, но не хочет объявлять зависимость, тогда макрос %{?systemd_ordering} МОЖЕТ быть использован как более слабая форма %{?systemd_requires}, которая объявляет только порядок во время транзакции RPM.

Макросы имеют форму %{?systemd_requires} вместо %systemd_requires, чтобы spec-файл корректно разбирался в случае, когда макросы systemd ещё не установлены.
Детали макросов

Для получения подробной информации о том, во что раскрываются эти макросы, обратитесь к следующим источникам: macros.systemd.in, triggers.systemd.in и daemon(7).

Оболочки

/etc/shells — это текстовый файл, который управляет тем, может ли приложение использоваться в качестве системной оболочки входа пользователей. Он содержит набор допустимых оболочек, которые можно использовать в системе. Если вы упаковываете новую оболочку, вам нужно добавить в этот файл записи, ссылающиеся на добавленные оболочки. См. man 5 SHELLS для получения дополнительной информации.

Поскольку этот файл может редактироваться системными администраторами, нам сначала нужно определить, есть ли соответствующие строки уже в файле. Если их там ещё нет, нам просто нужно вывести путь к двоичному файлу оболочки в файл. Поскольку функция UsrMove в Fedora 17 сделала /bin символьной ссылкой на /usr/bin, нам нужно поместить оба пути в файл /etc/shells. Вот пример скриптлета для пакета с оболочкой под названием «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