Фундамент RPM: Архитектура и принципы работы

Глубокое погружение во внутреннее устройство пакетного менеджера RPM. Курс детально разбирает физическую структуру пакетов, архитектуру базы данных, жизненный цикл транзакций и механизмы взаимодействия с файловой системой ОС.

1. Физическая структура RPM-пакета: Заголовок, метаданные и полезная нагрузка

Любой RPM-пакет, который вы устанавливаете в Rosa Linux, физически представляет собой один бинарный файл с расширением .rpm. Для конечного пользователя это просто «установщик программы», но для мейнтейнера — это строго структурированный контейнер. Понимание того, как байты организованы внутри этого контейнера, позволяет диагностировать ошибки сборки, восстанавливать повреждённые пакеты и оптимизировать процесс доставки ПО.

Файл RPM не является монолитным архивом. Он состоит из четырёх последовательных секций, каждая из которых выполняет свою уникальную задачу. Процесс чтения пакета пакетным менеджером всегда идёт строго сверху вниз.

!Структура бинарного файла RPM

Четыре кита RPM-пакета

Физическая структура файла делится на следующие блоки (в порядке их расположения):

  • Lead (Вводная часть)
  • Signature (Подпись)
  • Header (Заголовок или Метаданные)
  • Payload (Полезная нагрузка)
  • Рассмотрим каждую секцию под микроскопом.

    1. Lead: Историческое наследие и идентификация

    Lead — это самая первая структура данных в файле. Её размер всегда строго фиксирован и составляет 96 байт.

    Главная задача этой секции — сообщить операционной системе и утилитам (например, команде file), что перед ними именно RPM-пакет, а не MP3-трек или текстовый документ. Это достигается с помощью магического числа (magic number).

    Магическое число — это уникальная последовательность байтов в самом начале файла. Для RPM это всегда четыре байта: ed ab ee db (в шестнадцатеричной системе).

    > Магические числа — стандартный подход в UNIX-подобных системах для определения типа файла без оглядки на его расширение. Вы можете переименовать package.rpm в package.txt, но система всё равно распознает в нём RPM-архив.

    Помимо магического числа, Lead содержит:

  • Мажорную и минорную версии формата RPM (обычно 3.0).
  • Тип пакета (бинарный или исходный — SRPM).
  • Архитектуру, для которой собран пакет (например, x86_64 или aarch64).
  • Имя пакета (ограничено 66 символами).
  • Практический нюанс: В современных версиях RPM секция Lead считается устаревшей (legacy). Пакетный менеджер читает из неё только магическое число, чтобы убедиться в правильности формата. Вся остальная информация (имя, архитектура) дублируется и читается из секции Header, так как ограничение в 66 символов для имени пакета давно стало недостаточным.

    Вы можете увидеть Lead своими глазами, используя утилиту hexdump, которая выводит содержимое файла в шестнадцатеричном формате:

    В первой строке вывода вы чётко увидите байты ed ab ee db.

    2. Signature: Гарантия целостности и подлинности

    Сразу после Lead идёт секция Signature (Подпись). Её расположение неслучайно: пакетный менеджер должен убедиться, что файл не повреждён и получен из доверенного источника, до того, как начнёт распаковывать сложные метаданные или тяжеловесный архив.

    Секция подписи содержит криптографические хеши и цифровые подписи:

  • MD5 / SHA256 хеши: Гарантируют целостность. Если при скачивании пакета потерялся хотя бы один байт, пересчитанный хеш не совпадёт с тем, что записан в Signature, и установка будет прервана с ошибкой.
  • GPG-подпись: Гарантирует подлинность. Она подтверждает, что пакет был собран официальной сборочной системой Rosa Linux (или конкретным разработчиком), и в него не был внедрён вредоносный код третьими лицами.
  • Если пакет не подписан GPG-ключом, система выдаст предупреждение NOKEY при попытке установки. Для мейнтейнера Rosa Linux подписание своих пакетов перед отправкой в репозиторий — обязательный шаг.

    3. Header: Мозг пакета

    Header (Заголовок) — это самая важная часть для пакетного менеджера. Именно здесь хранятся все метаданные: зависимости, описание, скрипты установки (pre/post install), список файлов и информация о лицензии.

    Структура Header спроектирована как миниатюрная, невероятно быстрая база данных. Она состоит из трёх частей:

  • Вводная часть заголовка (Header Structure Header) — содержит магическое число заголовка и размеры следующих блоков.
  • Массив индексов (Index Array).
  • Хранилище данных (Data Store).
  • Чтобы понять, как это работает, представим библиотеку. Хранилище данных — это полки с книгами (сами данные), а Массив индексов — это картотека, где написано, на какой полке лежит нужная книга.

    Каждая запись в массиве индексов состоит из 16 байт и содержит четыре поля:

  • Tag (Тег): Числовой идентификатор того, что мы ищем. Например, тег 1000 означает имя пакета, 1001 — версию, 1049 — список зависимостей (Requires).
  • Type (Тип): Тип данных (строка, целое число, массив строк).
  • Offset (Смещение): Указатель. Показывает, на сколько байт нужно отступить в Хранилище данных, чтобы начать читать значение.
  • Count (Количество): Сколько элементов нужно прочитать.
  • !Интерактивная визуализация работы RPM Header

    Такая архитектура позволяет утилите rpm работать молниеносно. Когда вы выполняете команду rpm -qp --requires package.rpm (показать зависимости пакета), утилите не нужно читать весь файл. Она находит тег зависимостей в массиве индексов, смотрит смещение и мгновенно считывает нужные байты из хранилища данных. Полезная нагрузка при этом вообще не затрагивается.

    4. Payload: Полезная нагрузка

    После того как метаданные закончились, начинается Payload — непосредственно файлы, которые будут установлены в файловую систему Rosa Linux (бинарные исполняемые файлы, библиотеки, конфигурационные файлы, документация).

    Полезная нагрузка представляет собой сжатый архив формата cpio (copy in, copy out).

    Почему создатели RPM выбрали cpio, а не привычные tar или zip?

  • cpio исторически лучше работает с жесткими ссылками (hardlinks) и специальными файлами устройств (device nodes).
  • Формат cpio позволяет извлекать файлы потоково, без необходимости читать весь архив целиком или создавать временные индексы в оперативной памяти.
  • Сам по себе cpio не сжимает данные, он только объединяет их в один поток. Поэтому поверх него применяется алгоритм сжатия. Эволюция алгоритмов сжатия в RPM выглядит так:

    | Алгоритм | Скорость распаковки | Степень сжатия | Статус в современных дистрибутивах | | :--- | :--- | :--- | :--- | | gzip | Высокая | Низкая | Устарел, использовался в ранних версиях | | bzip2 | Низкая | Высокая | Устарел, слишком медленный | | xz (LZMA) | Средняя | Очень высокая | Долгое время был стандартом | | zstd | Очень высокая | Высокая | Современный стандарт (в т.ч. для Rosa Linux) |

    Современные версии Rosa Linux используют алгоритм zstd (Zstandard). Он обеспечивает степень сжатия на уровне xz, но распаковывается в несколько раз быстрее, что критически важно при обновлении сотен пакетов одновременно.

    #### Практика: Извлечение файлов без установки

    Как мейнтейнеру, вам часто придётся заглядывать внутрь пакета, не устанавливая его в систему. Поскольку Payload — это просто сжатый cpio архив, мы можем отделить его от заголовков и распаковать стандартными утилитами.

    Для этого используется команда rpm2cpio, которая отрезает Lead, Signature и Header, выдавая в стандартный вывод чистый поток cpio:

    Разбор ключей cpio:

  • -i (extract) — извлечь файлы.
  • -d (make directories) — создавать директории по мере необходимости.
  • -m (preserve modification time) — сохранить оригинальное время изменения файлов.
  • -v (verbose) — выводить список извлекаемых файлов на экран.
  • Типичные проблемы и подводные камни

    Понимание структуры спасает при отладке. Вот несколько частых ситуаций:

  • Ошибка "Header V4 RSA/SHA256 Signature, key ID ...: NOKEY"
  • Система прочитала секцию Signature, нашла там подпись, но в вашей системе нет публичного ключа для её проверки. Пакет не повреждён, проблема в доверии.

  • Ошибка "cpio: read failed - Inappropriate ioctl for device" или "Payload is not an archive"
  • Это означает, что секции Lead и Header прочитались успешно, но система не может распаковать Payload. Чаще всего это происходит, если пакет собран в новом дистрибутиве с использованием сжатия zstd, а вы пытаетесь установить его в старой системе, где пакетный менеджер понимает только xz или gzip.

  • Повреждение файла при передаче (Truncated file)
  • Если файл скачался не до конца, проверка Signature (MD5/SHA) сразу выдаст ошибку. Но если вы принудительно отключите проверку подписи, rpm сможет прочитать Header (он находится в начале файла), покажет вам список файлов, но при попытке установки упадёт на этапе чтения Payload, так как архив оборван.

    Архитектура RPM-пакета — это пример элегантного инженерного решения. Разделение на легковесные метаданные (Header) и тяжеловесный архив (Payload) позволяет пакетным менеджерам мгновенно разрешать зависимости графа из десятков тысяч пакетов, скачивая только заголовки, и загружать полезную нагрузку только тогда, когда план установки полностью утверждён.

    10. Архитектурные различия: Source RPM (SRPM) против бинарных пакетов

    Программное обеспечение существует в двух фундаментально разных состояниях: в виде исходного кода, понятного человеку, и в виде скомпилированных бинарных инструкций, понятных процессору. В экосистеме Rosa Linux это разделение физически воплощено в двух типах пакетов: Source RPM (SRPM) и Binary RPM.

    Понимание архитектурной разницы между ними, их внутреннего устройства и механизмов взаимодействия — это базовый навык, без которого невозможно создание качественных пакетов. Ошибка начинающих мейнтейнеров часто заключается в восприятии пакетного менеджера исключительно как инструмента доставки готовых файлов. В реальности RPM — это мощная сборочная система, где SRPM выступает чертежом, а бинарный пакет — готовым зданием.

    Анатомия бинарного пакета (Binary RPM)

    Бинарный RPM-пакет — это конечный продукт сборочного конвейера. Его главная задача — безопасно и предсказуемо доставить готовые к выполнению файлы в файловую систему целевой машины, зарегистрировать их в базе данных RPMDB и выполнить необходимые настроечные скрипты (scriptlets).

    Ключевая характеристика бинарного пакета — его жесткая привязка к аппаратной архитектуре или среде выполнения. В имени файла это выражается суффиксом: x86_64, aarch64, i586 или noarch (для независимых от архитектуры данных, таких как скрипты Python, шрифты или документация).

    Внутри полезной нагрузки (Payload) бинарного пакета находится архив cpio, содержащий структуру директорий, которая в точности повторяет иерархию целевой файловой системы.

    Например, если вы распакуете бинарный пакет утилиты htop (командой rpm2cpio htop.x86_64.rpm | cpio -idmv), вы увидите: * ./usr/bin/htop (скомпилированный исполняемый файл в формате ELF) * ./usr/share/man/man1/htop.1.gz (сжатое руководство пользователя) * ./usr/share/applications/htop.desktop (ярлык для меню приложений)

    Бинарный пакет не содержит ни строчки исходного кода на языке C, из которого был скомпилирован htop. Он оптимизирован для минимального размера и максимальной скорости установки.

    Архитектура Source RPM (SRPM)

    Source RPM (с суффиксом .src.rpm) решает совершенно иную задачу. Это транспортный контейнер для исходных кодов, инструкций по сборке и модификаций. Его цель — обеспечить воспроизводимость сборки (reproducible build). Любой разработчик, скачавший SRPM, должен иметь возможность пересобрать из него бинарный пакет, получив побайтово идентичный результат.

    В отличие от бинарных пакетов, SRPM всегда архитектурно-независим. Исходный код на языке C или Python одинаков, независимо от того, на каком процессоре он будет компилироваться в дальнейшем.

    Содержимое полезной нагрузки SRPM

    Если распаковать SRPM-пакет, внутри архива cpio вы не найдете структуры системных директорий (/usr/bin или /etc). Вместо этого там будет плоский список файлов, необходимых для сборки:

  • Оригинальный архив исходных кодов (Upstream Tarball): Обычно это файл вида program-1.2.3.tar.gz. Это неизмененный код, скачанный с официального сайта разработчика программы (апстрима).
  • Патчи (Patches): Текстовые файлы с расширением .patch или .diff. Они содержат специфичные для Rosa Linux исправления: закрытие уязвимостей (CVE), адаптацию путей под стандарты дистрибутива или исправление ошибок компиляции.
  • Дополнительные исходники (Sources): Файлы, которые не предоставляет разработчик программы, но они нужны дистрибутиву. Например, конфигурационные файлы по умолчанию, systemd-юниты (program.service) или иконки.
  • Spec-файл: Текстовый файл с расширением .spec. Это «мозг» пакета — сценарий, который объясняет утилите rpmbuild, как распаковать тарбол, в каком порядке наложить патчи, какими командами запустить компиляцию и как упаковать результат.
  • Философия неизменного апстрима

    Важнейший архитектурный принцип SRPM — разделение оригинального кода и модификаций дистрибутива.

    Мейнтейнер Rosa Linux никогда не распаковывает оригинальный tar.gz, не меняет там код руками и не запаковывает обратно. Оригинальный архив помещается в SRPM в первозданном виде (часто его целостность проверяется по криптографическому хешу SHA256). Все изменения вносятся исключительно через патчи, которые накладываются динамически в процессе сборки.

    Этот подход решает три критические задачи: * Аудит безопасности: Любой проверяющий может легко посмотреть файл .patch и увидеть, что именно мейнтейнер изменил в коде, не сравнивая тысячи файлов вручную. * Обновление версий: Когда выходит новая версия программы (например, 1.2.4), мейнтейнеру достаточно заменить оригинальный тарбол. Старые патчи часто применяются автоматически к новому коду утилитой patch. * Лицензионная чистота: Лицензия GPL требует предоставления исходных кодов вместе с бинарными файлами. Распространение SRPM автоматически закрывает юридические требования.

    Структурное тождество: Почему SRPM — это тоже RPM?

    Несмотря на радикальные различия в содержимом и назначении, на бинарном уровне файлы .rpm и .src.rpm абсолютно идентичны. Это один и тот же формат контейнера.

    Оба файла начинаются с устаревшей секции Lead, содержат криптографические подписи (Signature), базу метаданных (Header) и сжатый архив файлов (Payload).

    Как же система понимает, с чем имеет дело? Разница кроется в метаданных (тегах) внутри секции Header:

    * В бинарном пакете тег RPMTAG_ARCH содержит значение x86_64, а тег RPMTAG_SOURCERPM содержит имя исходного пакета (например, htop-3.2.2-1.rosa2021.1.src.rpm), из которого он был собран. * В SRPM-пакете тег RPMTAG_ARCH содержит зарезервированное значение src, а тег RPMTAG_SOURCERPM отсутствует (так как он сам является исходником).

    Эта архитектурная унификация гениальна в своей простоте. Разработчикам RPM не пришлось писать две разные утилиты для работы с исходниками и бинарниками. Команды проверки подписи (rpm -K), просмотра метаданных (rpm -qi) и извлечения файлов работают с SRPM точно так же, как с обычными пакетами.

    Однако поведение команды установки (rpm -i) кардинально отличается.

    Если вы выполните rpm -i htop.x86_64.rpm от имени root, файлы установятся в систему, а пакет зарегистрируется в RPMDB. Если вы выполните rpm -i htop.src.rpm от имени обычного пользователя, пакет не будет установлен в систему. Вместо этого RPM распакует содержимое Payload (тарбол, патчи, spec-файл) в ваше локальное сборочное дерево, которое мы настраивали в предыдущей статье через макрос %_topdir (в директории ~/rpmbuild/SOURCES и ~/rpmbuild/SPECS).

    Архитектура «Один ко многим» (Subpackages)

    Самое важное архитектурное различие между исходными и бинарными пакетами заключается в их количественном соотношении. Это отношение один ко многим ().

    Один Source RPM может (и часто должен) генерировать несколько независимых Binary RPM. Этот механизм называется подпакетами (subpackages).

    !Схема генерации нескольких бинарных пакетов из одного исходного

    Зачем это нужно? Рассмотрим пример библиотеки glibc (GNU C Library) — фундаментального компонента любой Linux-системы. Исходный код glibc огромен. После компиляции получается множество файлов: сами разделяемые библиотеки (.so), заголовочные файлы для программистов (.h), утилиты для настройки локалей и гигабайты документации.

    Если упаковать всё это в один бинарный пакет, его размер превысит 100 мегабайт. На сервере, который просто раздает статические веб-страницы, заголовочные файлы для программирования на C и документация абсолютно не нужны. Они будут впустую тратить дисковое пространство и оперативную память.

    Поэтому в spec-файле glibc.spec мейнтейнер описывает логику разделения:

  • Базовый пакет (glibc): Содержит только критически важные библиотеки (libc.so.6). Устанавливается на все системы.
  • Пакет для разработки (glibc-devel): Содержит заголовочные файлы (stdio.h). Нужен только программистам и сборочным серверам.
  • Пакет документации (glibc-doc): Содержит man-страницы и info-файлы. Устанавливается по желанию.
  • Пакет локалей (glibc-langpack-ru): Содержит поддержку русского языка.
  • При запуске сборки (rpmbuild -ba glibc.spec) система компилирует исходный код один раз, но на этапе упаковки (фаза %files) раскладывает сгенерированные файлы по разным cpio-архивам, создавая четыре разных бинарных RPM-пакета.

    Математика экономии масштабируется колоссально. Сохранение 50 МБ дискового пространства за счет вынесения документации в отдельный подпакет на паркете из 10 000 серверов экономит 500 гигабайт быстрого SSD-хранилища.

    !Интерактивный симулятор разделения пакетов

    Пакеты отладочной информации (Debuginfo)

    Особым типом бинарных пакетов, генерируемых из SRPM, являются пакеты -debuginfo. Их существование — это элегантное решение проблемы баланса между производительностью и возможностью поиска ошибок.

    Когда компилятор (например, GCC) превращает исходный код на C в исполняемый бинарный файл, он может включить в него отладочные символы (debug symbols). Эти символы связывают машинные инструкции с конкретными строками в исходном коде. Если программа падает (segmentation fault), отладчик gdb использует эти символы, чтобы сказать: «Ошибка произошла в файле main.c на строке 42».

    Проблема в том, что отладочные символы увеличивают размер бинарного файла в 3–5 раз. Загрузка такого файла в оперативную память замедляет запуск программы.

    Архитектура RPM решает это автоматически. В процессе сборки пакета, макросы RPM запускают специальный скрипт find-debuginfo.sh. Этот скрипт:

  • Находит все скомпилированные ELF-файлы.
  • Вырезает из них отладочные символы (операция strip), делая бинарники легкими и быстрыми.
  • Сохраняет вырезанные символы в отдельные файлы с расширением .debug.
  • Автоматически генерирует дополнительный бинарный пакет с суффиксом -debuginfo (например, htop-debuginfo.x86_64.rpm), куда помещает эти .debug файлы и копии исходного кода.
  • В результате обычные пользователи получают быстрый и легкий пакет htop. А если у разработчика программа падает, он устанавливает htop-debuginfo, и отладчик автоматически подхватывает символы, позволяя расследовать проблему без необходимости пересобирать программу из исходников.

    Жизненный цикл трансформации

    Чтобы закрепить понимание архитектурных различий, проследим полный путь трансформации SRPM в бинарные пакеты. Этот процесс управляется утилитой rpmbuild.

  • Распаковка (Prep): rpmbuild читает SRPM, извлекает тарбол в директорию BUILD и применяет патчи утилитой patch. На этом этапе абстрактный исходный код адаптируется под реалии Rosa Linux.
  • Компиляция (Build): Выполняются команды компиляции (обычно make). Исходный код превращается в бинарные файлы. Это самый долгий этап, зависящий от архитектуры процессора сборочной машины.
  • Установка (Install): Скомпилированные файлы копируются в директорию BUILDROOT — временную песочницу, имитирующую корневую файловую систему (/).
  • Упаковка (Package): rpmbuild анализирует секции %files в spec-файле. Он берет файлы из BUILDROOT, сжимает их в архивы cpio, добавляет метаданные (зависимости, скриптлеты) и формирует итоговые файлы .rpm в директории RPMS.
  • Создание нового SRPM: В самом конце rpmbuild берет оригинальный тарбол, патчи и spec-файл, и запаковывает их в новый .src.rpm в директории SRPMS.
  • Зачем создается новый SRPM, если у нас уже был исходный? В процессе работы мейнтейнер мог изменить spec-файл или добавить новый патч в локальной директории. Итоговый SRPM фиксирует это финальное состояние, гарантируя, что исходники полностью соответствуют собранным бинарникам.

    В инфраструктуре Rosa Linux (на платформе ABF, которую мы изучим в следующих статьях) мейнтейнеры загружают на серверы только исходные коды и spec-файлы. Сборочные кластеры автоматически генерируют SRPM, а затем распределяют его по виртуальным машинам с разными архитектурами (x86_64, aarch64, e2k), где из одного SRPM параллельно рождаются десятки бинарных пакетов для разных платформ.

    Понимание того, что SRPM — это неизменный источник истины, а бинарный пакет — лишь его временная, архитектурно-зависимая проекция, является ключом к профессиональной сборке пакетов.

    11. Механизмы проверки целостности: Хэш-суммы и криптографические подписи GPG

    Любая современная операционная система, включая Rosa Linux, опирается на доверие. Когда пакетный менеджер загружает бинарный файл из интернета и выполняет его от имени суперпользователя (root) в процессе установки, система должна быть абсолютно уверена в двух вещах. Во-первых, файл не был поврежден при передаче (случайная ошибка сети или битый сектор на диске). Во-вторых, файл был создан именно официальным мейнтейнером дистрибутива, а не злоумышленником, который подменил пакет на зеркале загрузки.

    В архитектуре RPM эти две задачи — обеспечение целостности (integrity) и подлинности (authenticity) — решаются с помощью математического аппарата криптографических хеш-функций и асимметричного шифрования стандарта GPG (GNU Privacy Guard).

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

    Уровень 1: Проверка целостности через хеш-суммы

    Фундаментом безопасности RPM является криптографическое хеширование. Хеш-функция — это математический алгоритм, который принимает на вход данные произвольного размера (например, RPM-пакет размером 50 МБ) и возвращает строку фиксированной длины (например, 64 символа).

    В криптографии этот процесс часто описывается базовой концепцией:

    Где — исходное сообщение (message), — хеш-функция, а — результирующий хеш (digest).

    Для того чтобы хеш-функция могла использоваться в RPM, она должна обладать тремя строгими свойствами:

  • Детерминированность: Один и тот же файл всегда дает абсолютно одинаковый хеш.
  • Необратимость: Имея хеш, математически невозможно (или вычислительно нецелесообразно) восстановить исходный файл.
  • Лавинный эффект (Avalanche effect): Изменение хотя бы одного бита в исходном файле (например, замена буквы «А» на «а» в текстовом файле внутри архива cpio) приводит к полному, непредсказуемому изменению итогового хеша.
  • !Интерактивная демонстрация лавинного эффекта в SHA-256

    Исторически RPM использовал алгоритм MD5, однако из-за найденных уязвимостей (возможности создания коллизий, когда два разных файла дают одинаковый хеш) современные дистрибутивы, включая Rosa Linux, перешли на семейство алгоритмов SHA-2 (в частности, SHA-256).

    Как RPM использует хеширование

    Вспомним физическую структуру RPM-пакета из первой статьи курса. Пакет состоит из секций: Lead, Signature, Header и Payload.

    Хеш-суммы не вычисляются для всего файла .rpm целиком как единого монолита. Архитектура RPM разделяет проверки:

    * Хеш полезной нагрузки (Payload Hash): Вычисляется только для сжатого архива cpio. Этот хеш хранится в метаданных (в секции Header). * Хеш заголовка (Header Hash): Вычисляется для самой базы данных метаданных. Этот хеш хранится в секции Signature, которая предшествует заголовку.

    Когда вы запускаете команду проверки пакета, RPM выполняет математическую сверку:

    Шестнадцатеричная строка после gpg-pubkey- — это короткий идентификатор ключа (Key ID). Вы можете посмотреть детальную информацию о конкретном ключе, как если бы это был обычный пакет:

    Здесь мы видим три независимые проверки:

  • Payload SHA256 digest: OK — Архив cpio не поврежден.
  • Header SHA256 digest: OK — База метаданных пакета не повреждена.
  • Header V4 RSA/SHA256 Signature: OK — Цифровая подпись валидна.
  • Обратите внимание на строку V4 RSA/SHA256. * RSA — это алгоритм асимметричного шифрования, использованный для создания подписи. * SHA256 — это алгоритм хеширования. V4 (Version 4) — это критически важный архитектурный маркер. В старых версиях RPM использовались подписи V3, которые подписывали только* секцию Header. Это оставляло теоретическую уязвимость: злоумышленник мог отрезать легитимный Header с валидной подписью V3 и приклеить его к вредоносному Payload. Подписи стандарта V4 (используемые в Rosa Linux по умолчанию) вычисляют криптографический хеш от непрерывного блока данных, включающего и Header, и Payload, полностью исключая возможность подмены.

    Практика мейнтейнера: Настройка среды для подписания

    В инфраструктуре Rosa Linux (ABF) пакеты подписываются автоматически на сборочных серверах ключом дистрибутива. Однако при локальной разработке, тестировании собственных репозиториев или передаче пакетов коллегам, мейнтейнер должен уметь подписывать пакеты своим личным ключом.

    Для этого необходимо связать утилиту rpm с вашим локальным хранилищем GPG. Это делается через конфигурационный файл ~/.rpmmacros, который мы изучали ранее.

    Добавьте в ~/.rpmmacros следующие строки:

    После настройки вы можете подписать уже собранный бинарный пакет или SRPM командой --addsign:

    Обработка ошибок и граничные случаи

    Что произойдет, если пользователь попытается установить пакет, подписанный неизвестным ключом (которого нет в RPMDB)?

    Высокоуровневый менеджер читает конфигурацию репозитория (файлы в /etc/yum.repos.d/), видит директиву gpgcheck=1 и принудительно останавливает процесс, если RPM возвращает статус NOKEY.

    Ротация ключей и отзыв

    Криптографические ключи имеют срок годности. Если приватный ключ дистрибутива скомпрометирован или истек его срок действия, выпускается новый ключ.

    Для мейнтейнера это означает, что старые пакеты, подписанные отозванным ключом, перестанут проходить проверку. В таких случаях инфраструктура переподписывает весь репозиторий новым ключом (используя команду rpm --resign, которая удаляет старую подпись и накладывает новую), а пользователям доставляется обновление пакета rosa-release, содержащее новый публичный ключ в /etc/pki/rpm-gpg/.

    Понимание этой механики позволяет мейнтейнеру не паниковать при виде ошибок GPG check FAILED, а четко диагностировать проблему: проверить наличие ключа в RPMDB (rpm -qa | grep gpg-pubkey), сверить Key ID подписи с установленными ключами и, при необходимости, импортировать недостающий публичный ключ.

    12. Управление конфигурационными файлами: Поведение RPM при обновлении конфигов

    Фундаментальный конфликт любого пакетного менеджера заключается в столкновении интересов создателя программы и системного администратора. Мейнтейнер выпускает новую версию пакета, в которой добавлены новые параметры конфигурации, оптимизированы настройки по умолчанию и удалены устаревшие директивы. Одновременно с этим системный администратор на конкретном сервере потратил часы на тонкую настройку текущего конфигурационного файла: прописал пароли к базам данных, изменил порты и настроил пути к логам.

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

    Для элегантного решения этой проблемы архитектура RPM использует механизм трехстороннего сравнения хешей (Three-Way Hash Comparison) и систему резервного копирования с суффиксами .rpmnew, .rpmsave и .rpmorig.

    Маркировка конфигурационных файлов в spec-файле

    По умолчанию RPM считает все файлы в пакете обычными бинарниками или ресурсами. Если пользователь изменит исполняемый файл /usr/bin/htop, при следующем обновлении RPM безжалостно перезапишет его новой версией.

    Чтобы включить механизм защиты пользовательских данных, мейнтейнер обязан явно указать в секции %files spec-файла, какие именно файлы являются конфигурационными. Для этого используются два макроса, определяющие фундаментально разную философию обновления.

    Макрос %config

    Использование базового макроса %config говорит пакетному менеджеру: «Новые настройки по умолчанию критически важны для работы этой версии программы. Если возникает конфликт, приоритет отдается файлу из нового пакета, но пользовательские данные нужно сохранить в резервную копию».

    Пример в spec-файле:

    Этот макрос обычно применяется для сервисов, где изменение мажорной версии влечет за собой полное изменение синтаксиса конфигурации. Если оставить старый файл, демон (например, база данных PostgreSQL при переходе с версии 14 на 15) просто упадет при старте с ошибкой синтаксиса.

    Макрос %config(noreplace)

    Использование макроса %config(noreplace) транслирует другую логику: «Пользовательские настройки — это абсолютная святыня. Если администратор внес хотя бы одно изменение, ни в коем случае не трогай рабочий файл. Просто положи новые настройки по умолчанию рядом, чтобы администратор мог изучить их позже».

    Пример в spec-файле:

    В современных дистрибутивах, включая Rosa Linux, %config(noreplace) является стандартом де-факто для 95% конфигурационных файлов. Это гарантирует, что рутинное обновление системы через dnf update (который под капотом вызывает транзакции RPM) никогда не сломает работающие веб-серверы, почтовые агенты или сетевые интерфейсы.

    Математика состояний: Трехстороннее сравнение

    Чтобы принять решение о том, как поступить с файлом, RPM не анализирует текст построчно. Вместо этого он оперирует криптографическими хешами (SHA-256), что обеспечивает мгновенную скорость работы и приватность (RPM не читает ваши пароли в конфигах, он лишь сверяет их математический отпечаток).

    В момент обновления пакета RPM собирает данные из трех независимых источников:

  • — Хеш оригинального файла из старого пакета. Это значение RPM извлекает из своей локальной базы данных (RPMDB). Оно показывает, каким файл был в момент первоначальной установки.
  • — Хеш файла, который прямо сейчас физически лежит на жестком диске сервера. RPM вычисляет его на лету.
  • — Хеш нового файла, который находится внутри скачанного RPM-пакета (в архиве cpio).
  • !Схема трехстороннего сравнения хешей в RPM

    Имея эти три переменные, RPM выстраивает логическую матрицу решений. Рассмотрим все возможные сценарии.

    Сценарий 1: Идеальное обновление (Никто ничего не менял)

    Условие: и

    Администратор установил пакет год назад и ни разу не открывал конфигурационный файл. Хеш на диске совпадает с хешем в базе данных. Мейнтейнер при этом выпустил новую версию с обновленным конфигом.

    Действие RPM: Поскольку пользовательских данных в файле нет, RPM безопасно удаляет старый файл и записывает на его место новый из пакета. Это происходит абсолютно прозрачно, независимо от того, использовался ли noreplace.

    Сценарий 2: Локальные правки без апстрим-изменений

    Условие: и

    Администратор активно редактировал файл (хеш на диске изменился). Мейнтейнер выпустил обновление программы (например, исправил уязвимость в бинарном коде), но сам конфигурационный файл по умолчанию в новой версии остался точно таким же, как в старой.

    Действие RPM: RPM видит, что мейнтейнеру нечего предложить нового в плане конфигурации. Пакетный менеджер просто оставляет пользовательский файл на диске нетронутым. Обновляется только бинарная часть пакета.

    Сценарий 3: Конфликт изменений (Изменили оба)

    Условие: и и

    Это самый сложный и самый частый случай в продакшене. Администратор настроил файл под свои нужды. Мейнтейнер тоже изменил файл в новой версии пакета. Возникает прямой конфликт: чьи данные важнее?

    Именно здесь вступает в игру разница между макросами, определенными в spec-файле.

    !Интерактивный симулятор разрешения конфликтов конфигурации

    Анатомия файлов .rpmnew и .rpmsave

    Когда RPM сталкивается с конфликтом (Сценарий 3), он никогда не уничтожает данные бесследно. Он использует системный вызов rename() для безопасного перемещения файлов.

    Рождение .rpmnew (Приоритет пользователя)

    Если файл был помечен макросом %config(noreplace), RPM принимает сторону системного администратора.

    Рабочий файл на диске (например, /etc/redis/redis.conf) остается на своем месте. Служба Redis продолжит работать с пользовательскими настройками. Однако новые настройки от мейнтейнера тоже нужно доставить в систему. RPM извлекает новый файл из архива cpio и сохраняет его рядом, добавляя суффикс .rpmnew.

    В файловой системе это выглядит так: * /etc/redis/redis.conf — ваш рабочий файл с вашими паролями. * /etc/redis/redis.conf.rpmnew — новый эталонный файл от мейнтейнера.

    В терминале во время транзакции вы увидите предупреждение: warning: /etc/redis/redis.conf created as /etc/redis/redis.conf.rpmnew

    Рождение .rpmsave (Приоритет мейнтейнера)

    Если файл был помечен строгим макросом %config, RPM принимает сторону мейнтейнера.

    Пакетный менеджер берет текущий рабочий файл пользователя и переименовывает его, добавляя суффикс .rpmsave. После этого на освободившееся оригинальное имя записывается новый файл из пакета.

    В файловой системе: * /etc/postgresql/15/main/postgresql.conf — новый файл от мейнтейнера (служба запустится с ним). * /etc/postgresql/15/main/postgresql.conf.rpmsave — ваши старые настройки, которые нужно перенести вручную.

    Предупреждение в терминале: warning: /etc/postgresql/15/main/postgresql.conf saved as /etc/postgresql/15/main/postgresql.conf.rpmsave

    Граничный случай: Появление .rpmorig

    Существует редкий, но важный сценарий, порождающий файлы с расширением .rpmorig.

    Представьте, что в версии пакета 1.0 мейнтейнер забыл указать макрос %config для файла /etc/myapp/settings.ini. RPM считал его обычным файлом. Пользователь, тем не менее, отредактировал этот файл.

    При выпуске версии 2.0 мейнтейнер осознал ошибку и добавил %config(noreplace) в spec-файл. Когда RPM начинает обновление, он пытается выполнить трехстороннее сравнение. Но у него нет значения для этого файла в базе данных, потому что в версии 1.0 файл не отслеживался как конфигурационный!

    Поскольку RPM не может гарантировать безопасность слияния без , он перестраховывается. Он устанавливает новый файл как /etc/myapp/settings.ini, а пользовательский файл переименовывает в /etc/myapp/settings.ini.rpmorig.

    Поведение при удалении пакетов

    Жизненный цикл конфигурационных файлов отличается от обычных бинарников не только при установке, но и при удалении пакета (команда rpm -e или dnf remove).

    Когда вы удаляете пакет, RPM проверяет хеш конфигурационного файла на диске () и сравнивает его с хешем, с которым файл был установлен ().

    * Если (файл не менялся), RPM спокойно удаляет его вместе с остальными файлами пакета. Мусора в системе не остается. * Если (пользователь вносил изменения), RPM отказывается удалять файл. Он переименовывает его в .rpmsave и оставляет на диске.

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

    Практика мейнтейнера: Управление миграциями

    Понимание механики .rpmnew и .rpmsave — это лишь половина работы мейнтейнера. Настоящее мастерство заключается в том, чтобы избавить системного администратора от необходимости вручную сливать (merge) эти файлы после каждого обновления.

    Если в новой версии программы изменилось название критической директивы (например, BindAddress переименовали в ListenIP), использование %config(noreplace) приведет к тому, что у пользователя останется старый файл со старой директивой, и служба не запустится. Использование %config затрет пользовательский IP-адрес, и служба запустится на стандартном 127.0.0.1, отрезав сервер от сети.

    Оба варианта неприемлемы. Правильный архитектурный подход — использовать предтранзакционные (%pre) или посттранзакционные (%post) bash-скриптлеты для автоматической миграции пользовательских данных.

    Пример безопасной миграции в spec-файле:

    В этом сценарии мейнтейнер использует %config(noreplace), чтобы сохранить файл пользователя, но берет на себя ответственность за программное обновление синтаксиса внутри этого файла.

    Инструменты администратора: Утилита rpmconf

    Хотя этот курс ориентирован на сборку пакетов, мейнтейнер должен понимать, как администраторы работают с результатами его труда.

    Со временем на сервере, пережившем несколько мажорных обновлений дистрибутива, скапливаются десятки файлов .rpmnew и .rpmsave. Оставлять их без внимания — плохая практика, так как система продолжает работать на устаревших настройках, игнорируя новые параметры безопасности, поставляемые апстримом.

    Для автоматизации процесса слияния в экосистеме RPM существует утилита rpmconf. Администратор запускает её командой:

    Утилита сканирует всю файловую систему на наличие файлов .rpmnew и .rpmsave. Найдя конфликт, она предлагает интерактивное меню, похожее на разрешение конфликтов в Git:

  • Оставить текущую версию (Keep current).
  • Принять новую версию от мейнтейнера (Use package version).
  • Посмотреть разницу (Diff).
  • Слить изменения вручную (Merge) — открывает файлы в vimdiff или meld.
  • Задача хорошего мейнтейнера Rosa Linux — писать spec-файлы и скриптлеты миграции так, чтобы администраторам приходилось запускать rpmconf как можно реже, а обновления проходили бесшовно и безопасно для пользовательских данных.

    13. Архитектурные теги: Специфика платформозависимых сборок и пакетов noarch

    Каждый RPM-пакет имеет строго определенное место в экосистеме операционной системы. Это место определяется не только его именем и версией, но и фундаментальной физической характеристикой — целевой аппаратной платформой. В имени файла nginx-1.24.0-1.x86_64.rpm суффикс x86_64 является не просто информационной меткой, а строгим контрактом между скомпилированным кодом и центральным процессором сервера.

    Понимание архитектурных тегов, концепции Multilib и правил создания универсальных пакетов — это критический навык для мейнтейнера Rosa Linux. Ошибка в определении архитектуры приводит к неработоспособным программам, конфликтам в файловой системе и пустой трате вычислительных ресурсов сборочной фермы (ABF).

    Биологическая аналогия: ДНК пакета

    Центральный процессор (CPU) понимает только машинный код — последовательность нулей и единиц, организованную в специфические инструкции. Разные семейства процессоров имеют разные наборы инструкций (Instruction Set Architecture, ISA).

    Процессоры Intel и AMD используют архитектуру x86 (и ее 64-битную версию x86_64). Процессоры в смартфонах, планшетах и современных серверах часто используют архитектуру ARM (aarch64). Российские процессоры «Эльбрус» используют архитектуру VLIW (e2k).

    Скомпилированный бинарный файл, созданный для x86_64, физически не может быть выполнен на процессоре aarch64. Это равносильно попытке воспроизвести виниловую пластинку на CD-плеере — носитель информации принципиально несовместим со считывающим устройством.

    Архитектурный тег в RPM — это маркер совместимости. Когда пакетный менеджер (DNF или низкоуровневый RPM) пытается установить пакет, он в первую очередь сверяет архитектурный тег пакета с архитектурой текущего ядра операционной системы (которую можно узнать командой uname -m). Если они не совпадают (и не настроена трансляция совместимости), транзакция блокируется.

    Платформозависимые пакеты: Специфика компиляции

    Если исходный код программы написан на компилируемых языках (C, C++, Rust, Go, Pascal), в процессе сборки пакета (на этапе %build в spec-файле) исходный текст превращается в машинно-зависимый бинарный код.

    Такие пакеты обязаны иметь архитектурный тег, соответствующий платформе, на которой они были собраны. По умолчанию утилита rpmbuild автоматически присваивает пакету архитектуру сборочного сервера.

    Математика разрядности и ограничения памяти

    Исторически архитектура x86 была 32-битной (теги i386, i586, i686). Главное математическое ограничение 32-битной системы заключается в размере адресного пространства. Процессор может адресовать максимум байт оперативной памяти, что равно ровно 4 гигабайтам (4 294 967 296 байт).

    Современная 64-битная архитектура (x86_64 или aarch64) использует 64-битные указатели, что позволяет адресовать байт (16 эксабайт) памяти. Это фундаментальное различие требует разных типов данных на уровне языка C, разных размеров регистров процессора и, как следствие, абсолютно разных бинарных файлов.

    Концепция Multilib: Сосуществование миров

    Переход от 32-битных систем к 64-битным занял более десятилетия. Чтобы обеспечить плавную миграцию, инженеры Linux разработали концепцию Multilib (множественные библиотеки).

    Multilib позволяет 64-битной операционной системе запускать старые 32-битные приложения. Для этого ядро ОС должно поддерживать выполнение 32-битных инструкций, а в файловой системе должны одновременно присутствовать две версии одних и тех же системных библиотек (например, glibc или libz).

    RPM реализует поддержку Multilib через строгое разделение путей в стандарте FHS (Filesystem Hierarchy Standard):

    * /usr/lib — директория для 32-битных библиотек (архитектура i686). * /usr/lib64 — директория для 64-битных библиотек (архитектура x86_64).

    > Если вы собираете библиотеку на C++ для Rosa Linux x86_64, макрос %_libdir в вашем spec-файле автоматически раскроется в /usr/lib64. Если вы попытаетесь жестко прописать путь /usr/lib в 64-битной сборке, это будет грубым нарушением архитектуры дистрибутива.

    Благодаря этому разделению, пакетный менеджер может установить пакеты zlib-1.2.11-1.x86_64.rpm и zlib-1.2.11-1.i686.rpm одновременно на один сервер. Их файлы не пересекутся, так как один положит libz.so в /usr/lib64, а другой — в /usr/lib.

    Пакеты noarch: Универсальность и экономия

    Далеко не все компоненты операционной системы зависят от архитектуры процессора. Огромный пласт программного обеспечения является кроссплатформенным по своей природе.

    Для таких компонентов в RPM существует специальный архитектурный тег — noarch (от англ. no architecture — без архитектуры).

    Что должно упаковываться в noarch?

  • Скриптовые языки: Программы на Python, Ruby, Perl, Bash, PHP. Эти языки не компилируются в машинный код. Они распространяются в виде обычного текста. Интерпретатор (например, /usr/bin/python3), который читает этот текст, является платформозависимым бинарником (x86_64), но сам текстовый скрипт одинаково хорошо прочитается на любой архитектуре.
  • Байт-код виртуальных машин: Скомпилированные классы Java (.jar файлы). Java использует принцип Write Once, Run Anywhere. Байт-код исполняется виртуальной машиной Java (JVM), которая берет на себя трансляцию в машинные инструкции конкретного процессора.
  • Данные и медиа: Шрифты (TTF, OTF), иконки (PNG, SVG), обои рабочего стола, звуковые темы.
  • Документация: Страницы руководств (man pages), файлы HTML, PDF, текстовые лицензии.
  • Конфигурационные файлы: Базовые настройки системы, правила udev, юниты systemd (если они поставляются отдельным пакетом).
  • Экономика noarch-пакетов

    Использование noarch — это не просто вопрос эстетики, это вопрос экономии колоссальных ресурсов инфраструктуры дистрибутива.

    Представьте, что вы мейнтейнер пакета с набором иконок (размером 50 МБ). Rosa Linux поддерживает архитектуры x86_64, i686, aarch64 и e2k.

    Если вы не укажете тег noarch, сборочная ферма ABF запустит процесс сборки 4 раза на разных серверах. В результате получится 4 идентичных RPM-пакета с разными именами (icons.x86_64.rpm, icons.aarch64.rpm и т.д.). На зеркалах дистрибутива эти пакеты займут 200 МБ вместо 50 МБ. При синхронизации зеркал по всему миру будут передаваться лишние гигабайты данных.

    Объявление пакета как noarch говорит сборочной системе: «Собери этот пакет ровно один раз на любой доступной машине. Результат будет гарантированно работать везде».

    Как создать noarch-пакет

    Чтобы указать RPM, что пакет не зависит от архитектуры, в заголовочной части spec-файла необходимо добавить одну директиву:

    При наличии этой директивы rpmbuild проигнорирует архитектуру хоста и создаст пакет с суффиксом .noarch.rpm.

    !Схема маршрутизации файлов при сборке: исходный код разделяется на платформозависимые бинарники и универсальные данные

    Ловушка мейнтейнера: Гибридные пакеты

    Самая частая ошибка начинающих мейнтейнеров возникает при работе с экосистемами Python, Ruby или Node.js.

    Допустим, вы упаковываете библиотеку на Python. Вы автоматически пишете BuildArch: noarch, так как Python — интерпретируемый язык. Пакет успешно собирается, но при установке на другой архитектуре программа падает с ошибкой ImportError: wrong ELF class.

    Причина: Многие библиотеки скриптовых языков для повышения производительности включают в себя модули, написанные на C (так называемые C-extensions).

    Например, библиотека numpy для Python содержит тысячи строк кода на C и Fortran для быстрых математических вычислений. В процессе сборки пакета этот код компилируется в разделяемые библиотеки (файлы .so).

    Файл .so (Shared Object) — это платформозависимый бинарный файл (ELF). Если в вашем пакете есть хотя бы один файл .so, пакет категорически запрещено маркировать как noarch.

    Правило чистоты noarch

    Утилита rpmlint (которую мы подробно разберем в будущих статьях) строго следит за этим правилом. Если она обнаружит ELF-бинарник внутри пакета с тегом noarch, она выдаст критическую ошибку arch-dependent-file-in-noarch и заблокирует отправку пакета в репозиторий.

    Если ваш пакет содержит и скрипты, и бинарные расширения, он должен собираться как платформозависимый (без BuildArch: noarch). В этом случае скрипты лягут в /usr/lib/python3.X/site-packages/, а бинарные модули — в /usr/lib64/python3.X/site-packages/.

    !Интерактивный симулятор анализатора архитектуры: распределение файлов по типам пакетов

    Условная компиляция: Макросы %ifarch и %ifnarch

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

    Например, программа отлично компилируется на x86_64, но на aarch64 компилятор GCC выдает ошибку из-за специфики работы с памятью на ARM. Разработчик программы выпустил патч, который исправляет эту ошибку только для ARM.

    Вместо того чтобы создавать два разных spec-файла, RPM предоставляет механизм условной логики на этапе препроцессинга — макросы %ifarch (если архитектура равна) и %ifnarch (если архитектура не равна).

    Пример использования в spec-файле:

    Эти макросы вычисляются утилитой rpmbuild до начала выполнения bash-скриптов. Если сборка идет на сервере x86_64, блок внутри %ifarch aarch64 будет просто удален из итогового скрипта, как будто его там никогда не было.

    Кросс-компиляция и флаг --target

    В классическом сценарии пакет для архитектуры aarch64 собирается на физическом сервере с процессором ARM. Однако инфраструктура не всегда позволяет иметь мощные сборочные фермы для каждой экзотической архитектуры.

    RPM поддерживает механизм кросс-компиляции — процесс, при котором мощный сервер на базе x86_64 собирает бинарный код, предназначенный для выполнения на ARM или MIPS.

    Для этого при запуске сборки используется флаг --target:

    При использовании этого флага RPM переопределяет внутренние макросы архитектуры. Макрос %{_target_cpu} примет значение aarch64, а макрос %{_libdir} укажет на правильную директорию для целевой платформы.

    Однако кросс-компиляция требует наличия в системе кросс-компилятора (например, aarch64-linux-gnu-gcc) и кросс-сборки всех библиотек-зависимостей (sysroot), что делает этот процесс сложным в настройке. В современной инфраструктуре Rosa Linux (ABF) предпочтение отдается нативной сборке на реальном или эмулируемом (через QEMU) оборудовании целевой архитектуры.

    Понимание архитектурных тегов позволяет мейнтейнеру создавать чистые, оптимизированные и безопасные пакеты, которые корректно интегрируются в сложную многоплатформенную среду современного дистрибутива Linux.

    14. Обслуживание базы данных RPM: Инструменты проверки целостности и восстановления

    Обслуживание базы данных RPM: Инструменты проверки целостности и восстановления

    Любая операционная система — это живой организм, находящийся в состоянии постоянного изменения. Установка обновлений безопасности, инсталляция новых утилит, удаление устаревших библиотек — все эти процессы непрерывно модифицируют файловую систему. Единственным источником истины, который знает, какому пакету принадлежит каждый файл на диске, является база данных RPM (RPMDB).

    В предыдущих материалах мы разобрали логическую архитектуру RPMDB и выяснили, что она состоит из первичного хранилища (таблицы пакетов) и вторичных индексов, обеспечивающих быстрый поиск. Однако любая база данных уязвима перед физическими сбоями оборудования и человеческим фактором. Понимание механизмов диагностики и восстановления RPMDB — это критический навык, отделяющий рядового пользователя от квалифицированного мейнтейнера и системного администратора Rosa Linux.

    Анатомия сбоя: Почему ломается RPMDB

    База данных RPM, расположенная в директории /var/lib/rpm, представляет собой набор файлов на жестком диске. Процесс записи данных в эти файлы не является мгновенным.

    Когда пакетный менеджер (например, DNF) фиксирует транзакцию, он должен обновить первичную таблицу и перестроить несколько вторичных индексов (по именам, файлам, предоставляемым зависимостям). Если в этот момент происходит прерывание процесса, база данных остается в промежуточном, неконсистентном состоянии.

    Основные причины повреждения RPMDB:

  • Внезапное отключение питания (Power Loss): Сервер теряет питание до того, как дисковый контроллер успевает сбросить данные из кэша оперативной памяти на физический магнитный диск или чипы NAND.
  • Принудительное завершение процессов: Использование сигнала SIGKILL (команда kill -9) для остановки зависшего процесса dnf или rpm. Этот сигнал не позволяет программе корректно закрыть дескрипторы файлов и снять блокировки.
  • Исчерпание дискового пространства: Ошибка No space left on device во время записи транзакции приводит к усечению файлов базы данных.
  • Аппаратные ошибки накопителя: Появление битых секторов (bad blocks) в области диска, где хранятся файлы /var/lib/rpm.
  • !Магнитные пластины и считывающая головка жесткого диска — физическое прерывание записи на этом уровне ведет к логическому повреждению файловой системы и баз данных

    Симптомы повреждения

    Система с поврежденной базой данных RPM начинает вести себя непредсказуемо. Наиболее типичные симптомы:

    * Команда rpm -qa (вывод всех установленных пакетов) зависает навсегда и не реагирует на Ctrl+C. При попытке установить любой пакет через dnf install процесс останавливается на этапе Running transaction test*. * В терминале появляются явные ошибки: error: rpmdb: damaged header #1043 retrieved или sqlite3: database disk image is malformed. * Утилита RPM завершается с ошибкой сегментации (Segmentation fault).

    Проблема зависших блокировок (Stale Locks)

    Прежде чем приступать к радикальным методам восстановления, необходимо исключить проблему «зависших блокировок». В 80% случаев то, что выглядит как фатальное повреждение базы данных, на самом деле является конфликтом доступа.

    Чтобы предотвратить одновременную запись в базу данных двумя разными программами (например, фоновым процессом автоматического обновления и администратором, запустившим dnf вручную), RPM использует механизм блокировок (locks).

    Современные версии Rosa Linux используют SQLite в качестве движка RPMDB. SQLite реализует транзакционность через механизм Write-Ahead Logging (WAL). В директории /var/lib/rpm помимо основного файла rpmdb.sqlite во время работы создаются временные файлы:

    * rpmdb.sqlite-wal — журнал упреждающей записи. * rpmdb.sqlite-shm — файл разделяемой памяти для координации блокировок между процессами.

    Если процесс rpm был убит некорректно, эти файлы остаются на диске. При следующем запуске RPM видит файл блокировки, считает, что другая транзакция все еще выполняется, и переходит в режим бесконечного ожидания.

    Диагностика и снятие блокировок

    Первый шаг при зависании RPM — проверить, какие процессы удерживают файлы базы данных. Для этого используется утилита lsof (List Open Files):

    Если команда показывает, что файлы удерживаются зависшим процессом dnf или packagekitd, этот процесс необходимо завершить штатным сигналом SIGTERM (команда kill <PID>).

    Если процессов нет, но база все равно висит, необходимо очистить временные файлы SQLite.

    > Золотое правило администратора: Никогда не модифицируйте файлы в /var/lib/rpm без предварительного создания резервной копии.

    Шаг 3. Создание новой базы данных Теперь мы переименовываем поврежденную базу и создаем новую, импортируя в нее наш текстовый SQL-дамп.

    Шаг 4. Финальное перестроение После того как мы спасли первичную таблицу средствами SQLite, необходимо перестроить индексы средствами самого RPM, чтобы гарантировать 100% консистентность форматов.

    Этот метод позволяет спасти 99% информации об установленных пакетах даже при тяжелых аппаратных сбоях диска.

    Восстановление из внешних источников

    В катастрофических случаях, когда файл rpmdb.sqlite уничтожен полностью (например, случайно выполнена команда rm -rf /var/lib/rpm), восстановить базу из самой себя невозможно.

    В такой ситуации система продолжает работать (так как бинарные файлы программ находятся в /usr/bin, а библиотеки в /usr/lib64), но пакетный менеджер считает, что в системе не установлено ни одного пакета. Любое обновление сломает систему.

    Единственный выход — восстановить список пакетов из логов пакетного менеджера DNF.

    DNF ведет подробный журнал всех своих действий в файле /var/log/dnf.rpm.log. Анализируя этот текстовый файл, можно составить список пакетов, которые были установлены в системе.

    Пример строки из лога: 2023-10-25T14:32:11+0300 SUBDEBUG Installed: nginx-1.24.0-1.x86_64

    Имея такой список, администратор может написать bash-скрипт, который скачает эти пакеты с зеркал Rosa Linux и установит их поверх существующих файлов с флагом --justdb.

    Флаг --justdb — это специальная команда для RPM: «Не распаковывай файлы из архива на диск (они там уже есть), а только добавь запись об этом пакете в базу данных RPMDB».

    Процесс полного восстановления через парсинг логов и --justdb является крайне трудоемким, поэтому регулярное резервное копирование директории /var/lib/rpm (например, с помощью утилиты rsync или tar на удаленный сервер) должно быть стандартом для любой production-системы.

    Понимание механизмов работы SQLite, принципов блокировок и алгоритмов перестроения индексов позволяет мейнтейнеру не только создавать качественные пакеты, но и обеспечивать надежную эксплуатацию всей экосистемы дистрибутива в условиях реальных аппаратных сбоев.

    15. Низкоуровневые запросы к RPM: Использование CLI для анализа метаданных

    Низкоуровневые запросы к RPM: Использование CLI для анализа метаданных

    Пакетный менеджер RPM часто воспринимается исключительно как инструмент для установки и удаления программ. Однако для мейнтейнера и системного инженера Rosa Linux утилита rpm — это в первую очередь мощный язык запросов к локальной базе данных и бинарным файлам. Способность быстро извлекать, фильтровать и форматировать метаданные отличает профессионала от рядового пользователя.

    В предыдущих материалах мы изучили физическую структуру пакета (секции Header и Payload) и устройство базы данных RPMDB. Теперь мы рассмотрим, как взаимодействовать с этими структурами на практике, используя интерфейс командной строки (CLI).

    Режимы работы: База данных против файла

    Архитектура запросов RPM строго разделяет источники данных. Прежде чем задать вопрос, необходимо указать утилите, где искать ответ. Для этого используются два базовых флага:

  • Запрос к установленным пакетам (-q или --query): RPM обращается к локальной базе данных (RPMDB), расположенной в /var/lib/rpm. В этом режиме утилита ищет совпадения по индексам (Name, Providename и т.д.).
  • Запрос к файлу пакета (-qp или --querypackage): RPM игнорирует базу данных и читает метаданные непосредственно из секции Header указанного .rpm файла на диске.
  • > Разница между -q и -qp аналогична разнице между поиском книги в электронном каталоге библиотеки и чтением аннотации на обложке книги, которую вы держите в руках.

    Пример базового запроса к базе данных:

    Пример запроса к скачанному, но еще не установленному пакету:

    Если забыть флаг p при обращении к файлу, RPM попытается найти в базе данных пакет с именем, совпадающим с длинным путем к файлу, и закономерно выдаст ошибку package is not installed.

    Векторы поиска: Как найти нужный пакет

    Знать точное имя пакета удается не всегда. RPM предоставляет набор селекторов для поиска пакетов по косвенным признакам.

    Поиск по принадлежности файла (-qf)

    Один из самых частых вопросов при отладке системы: «Какая программа создала этот конфигурационный файл или библиотеку?». Флаг -qf (--file) позволяет выполнить обратный поиск — от абсолютного пути файла к пакету-владельцу.

    Этот механизм работает благодаря вторичному индексу Basenames в RPMDB. Важное ограничение: файл должен быть официально зарегистрирован в секции Payload пакета. Если файл был создан динамически (например, лог-файл или кэш, не помеченный макросом %ghost), RPM его не распознает.

    Поиск по предоставляемым возможностям (--whatprovides)

    В RPM существует концепция виртуальных зависимостей. Пакет может заявлять, что он предоставляет определенную функциональность, не привязываясь к своему реальному имени. Например, несколько разных веб-серверов могут предоставлять виртуальную сущность webserver.

    Поиск по зависимостям (--whatrequires)

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

    Извлечение стандартных метаданных

    Найдя нужный пакет, мы можем запросить у него конкретную информацию. RPM предлагает набор стандартных флагов-модификаторов, которые комбинируются с базовым -q.

    | Флаг | Полное имя | Описание извлекаемых данных | | :--- | :--- | :--- | | -i | --info | Общая информация: версия, релиз, архитектура, дата сборки, лицензия, URL проекта и текстовое описание (Summary/Description). | | -l | --list | Полный список файлов, которые пакет устанавливает в файловую систему. | | -c | --configfiles | Выборка только конфигурационных файлов (помеченных макросом %config в spec-файле). | | -d | --docfiles | Выборка только файлов документации (man-страницы, README, лицензии). | | -R | --requires | Список зависимостей пакета (библиотеки, другие пакеты, версии интерпретаторов). | | --provides | - | Список сущностей, которые пакет предоставляет системе (свое имя, библиотеки, виртуальные зависимости). | | --scripts | - | Вывод содержимого исполняемых скриптлетов (%pre, %post и т.д.), которые выполняются при транзакции. |

    Эти флаги можно комбинировать с запросом к файлу. Например, чтобы посмотреть скрипты в скачанном пакете перед его установкой (базовая мера безопасности), используется комбинация rpm -qp --scripts package.rpm.

    !Схема извлечения метаданных

    Анатомия --queryformat: Низкоуровневый парсинг

    Стандартные флаги (вроде -qi или -ql) удобны для чтения человеком, но совершенно не подходят для машинной обработки в bash-скриптах. Вывод -qi содержит пробелы, переносы строк и локализованные заголовки, которые сложно парсить с помощью awk или sed.

    Для точной экстракции данных используется флаг --queryformat (или его короткая версия --qf). Он позволяет напрямую обращаться к тегам в секции Header пакета и формировать вывод по строго заданному шаблону.

    Базовый синтаксис тегов

    Синтаксис --queryformat напоминает функцию printf в языке C. Теги метаданных заключаются в конструкцию %{TAGNAME}.

    Обратите внимание на обязательный символ переноса строки \n в конце шаблона. В отличие от стандартных команд, --queryformat не добавляет перенос строки автоматически.

    Чтобы узнать все доступные теги (а их более 200), используется команда:

    Модификаторы форматирования

    Внутри конструкции тега можно использовать модификаторы ширины и выравнивания. Синтаксис выглядит так: %[флаги][ширина]TAGNAME.

    * %20{NAME} — выделить под имя ровно 20 символов. Если имя короче, оно будет выровнено по правому краю (дополнено пробелами слева). * %-20{NAME} — знак минус означает выравнивание по левому краю (дополнение пробелами справа). Это идеально подходит для создания ровных колонок в терминале.

    Функции форматирования данных

    Некоторые данные хранятся в RPMDB в сыром виде и требуют преобразования для чтения. Например, дата сборки (BUILDTIME) хранится как Unix timestamp (количество секунд с 1 января 1970 года).

    Для преобразования используются встроенные функции форматирования, которые добавляются после имени тега через двоеточие: %{TAG:function}.

    * :date — преобразует Unix timestamp в читаемую дату. * :day — выводит только день, месяц и год. * :bytes — форматирует размер, добавляя суффиксы (b, K, M). * :shescape — экранирует спецсимволы для безопасной передачи в shell.

    !Интерактивный конструктор --queryformat

    Работа с массивами в метаданных

    Самая сложная часть работы с --queryformat — это понимание массивов.

    Такие теги, как NAME, VERSION или SIZE, являются скалярами — они содержат только одно значение. Но теги вроде FILENAMES (список файлов), REQUIRENAME (список зависимостей) или CHANGELOGTEXT (записи истории изменений) являются массивами.

    Если просто запросить массив через %{FILENAMES}, RPM выведет только первый элемент массива. Чтобы перебрать все элементы, необходимо использовать синтаксис итерации: [%{TAG}\n] (квадратные скобки вокруг шаблона).

    Слияние скаляров и массивов

    Магия RPM заключается в том, как он обрабатывает комбинацию скаляров и массивов внутри одного блока итерации. Если поместить скаляр (например, имя пакета) внутрь квадратных скобок вместе с массивом, RPM автоматически продублирует скаляр для каждого элемента массива.

    Это позволяет создавать плоские таблицы связей, которые легко анализировать утилитой grep.

    Параллельные массивы

    Метаданные о файлах хранятся в RPMDB не в виде единого объекта, а в виде нескольких параллельных массивов одинаковой длины: DIRNAMES (директории), BASENAMES (имена файлов), FILESIZES (размеры файлов), FILEMODES (права доступа).

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

    Практические сценарии мейнтейнера

    Знание --queryformat позволяет решать задачи, для которых иначе потребовалось бы писать сложные скрипты на Python.

    Сценарий 1: Аудит дискового пространства

    Задача: найти 10 самых больших установленных пакетов в системе.

    Тег SIZE хранит размер пакета в байтах. Мы можем извлечь имена и размеры, отсортировать их числовой сортировкой (sort -n) и взять последние 10 строк.

    Если мы хотим получить размер в мегабайтах для дальнейших расчетов в bash, мы используем математическую формулу перевода байтов в мегабайты:

    Где — это (количество байт в одном мегабайте).

    Сценарий 2: Поиск пакетов без криптографической подписи

    Задача: проверить систему на наличие пакетов, установленных в обход официальных репозиториев (без GPG-подписи Rosa Linux).

    Тег SIGGPG содержит бинарные данные подписи. Если подписи нет, тег возвращает строку (none). Мы можем использовать это для фильтрации.

    Сценарий 3: Анализ архитектуры

    Задача: подсчитать соотношение платформозависимых пакетов (x86_64) и универсальных скриптов/данных (noarch).

    Сценарий 4: Извлечение Changelog

    При обновлении пакета мейнтейнер обязан вести историю изменений. Эти данные хранятся в массивах CHANGELOGTIME, CHANGELOGNAME (автор) и CHANGELOGTEXT (описание).

    Глубокий дамп состояния: --dump

    Иногда требуется получить исчерпывающую информацию о физическом состоянии файлов пакета на диске, чтобы сравнить их с эталонными значениями из базы данных (например, при подозрении на взлом системы или повреждение диска).

    Для этого используется модификатор --dump в сочетании с -ql (список файлов). Он выводит 11 параметров для каждого файла, разделенных пробелами:

  • Абсолютный путь
  • Размер в байтах
  • Время последней модификации (mtime)
  • Криптографический хеш (MD5 или SHA256)
  • Права доступа (mode)
  • Владелец (owner)
  • Группа (group)
  • Флаг конфигурационного файла (0 или 1)
  • Флаг документации (0 или 1)
  • Старший номер устройства (rdev major)
  • Младший номер устройства (rdev minor)
  • Анализируя этот вывод, системы обнаружения вторжений (IDS) могут выявлять несанкционированные изменения в бинарных файлах системных утилит.

    Умение конструировать низкоуровневые запросы к RPM превращает пакетный менеджер из «черного ящика» в прозрачную и предсказуемую систему. Это фундаментальный навык, необходимый для написания качественных spec-файлов, разрешения конфликтов зависимостей и автоматизации процессов сборки в инфраструктуре Rosa Linux.

    2. Формат архива cpio: Хранение и извлечение файлов внутри RPM

    Формат архива cpio: Хранение и извлечение файлов внутри RPM

    Изучая физическую структуру RPM-пакета, мы выяснили, что метаданные и зависимости хранятся в легковесном заголовке, а сами устанавливаемые файлы — в секции полезной нагрузки (Payload). Эта секция представляет собой сжатый архив формата cpio (copy in, copy out).

    Для начинающего пользователя Linux формат cpio часто остаётся в тени более популярных tar или zip. Однако для мейнтейнера Rosa Linux понимание внутреннего устройства этого формата критически важно. Именно от него зависит, как файлы будут развёрнуты в системе, как сохранятся права доступа и почему извлечение одного файла из гигантского пакета может занять неожиданно много времени.

    Историческое наследие: От магнитных лент к пакетным менеджерам

    Формат cpio был создан в 1977 году для операционной системы UNIX. Его изначальной задачей было резервное копирование данных на магнитные ленты.

    Магнитная лента — это устройство последовательного доступа. Вы не можете мгновенно переместить читающую головку в конец ленты, чтобы прочитать оглавление, а затем вернуться в середину за нужным файлом. Ленту нужно мотать последовательно, байт за байтом.

    !Накопитель на магнитной ленте IBM 729 — физический прародитель потоковых форматов архивирования

    Именно это физическое ограничение сформировало архитектуру cpio. В отличие от формата zip, который хранит центральную директорию (оглавление) в конце файла, cpio вообще не имеет единого индекса.

    Архив cpio представляет собой непрерывный поток данных, состоящий из повторяющихся пар: Заголовок файла + Содержимое файла.

    > Ожидайте, что программы будут читать текстовый поток от начала до конца. Избегайте строгих форматов данных и бинарных структур, требующих произвольного доступа. > > Философия UNIX, Дуг Макилрой

    Почему создатели RPM (а вслед за ними и разработчики Rosa Linux) выбрали формат из 70-х годов для современных дистрибутивов? Ответ кроется в конвейерной обработке данных.

    Архитектура потокового извлечения

    Когда вы устанавливаете RPM-пакет, пакетный менеджер не распаковывает архив на жёсткий диск целиком. Процесс идёт в оперативной памяти через систему конвейеров (pipes).

    Полезная нагрузка в RPM сжата современными алгоритмами (в Rosa Linux это zstd). Алгоритм сжатия выдаёт распакованные байты последовательно. Если бы RPM использовал zip, пакетному менеджеру пришлось бы сначала полностью распаковать архив во временную директорию, чтобы прочитать индекс в конце файла, и только потом начать установку.

    С cpio процесс идёт иначе:

  • Декомпрессор zstd читает сжатые байты и выдаёт сырой поток.
  • Утилита cpio подхватывает этот поток на лету.
  • Как только cpio считывает заголовок первого файла, она тут же создаёт его на диске и начинает записывать в него следующие за заголовком данные.
  • !Конвейер распаковки полезной нагрузки RPM-пакета в файловую систему

    Такая архитектура позволяет устанавливать пакеты размером в гигабайты на системах с минимальным объёмом оперативной памяти и без создания огромных временных файлов.

    !Сравнение извлечения файлов: Индексированный архив (ZIP) против Потокового (cpio)

    Анатомия заголовка cpio (формат SVR4)

    Современные RPM-пакеты используют вариант формата cpio, который называется SVR4 (или newc).

    Каждый файл в архиве предваряется текстовым заголовком фиксированного размера — 110 байт. Заголовок состоит из шестнадцатеричных символов (ASCII), что делает его читаемым даже при частичном повреждении архива.

    Ключевые поля заголовка SVR4:

  • Magic Number: Всегда равен 070701 (или 070702 для версии с контрольной суммой). Это маркер, по которому утилита понимает, что начался новый файл.
  • Ino (Inode): Уникальный номер индексного дескриптора. Критически важен для восстановления жёстких ссылок (hardlinks).
  • Mode: Права доступа и тип файла (обычный файл, директория, символическая ссылка, блочное устройство).
  • Uid / Gid: Идентификаторы владельца и группы.
  • Mtime: Время последней модификации файла (в формате UNIX-времени).
  • Filesize: Точный размер полезных данных файла в байтах.
  • Namesize: Длина имени файла (включая нулевой байт в конце).
  • Сразу после 110 байт заголовка идёт имя файла, а затем — само содержимое.

    Математика выравнивания (Padding)

    Для оптимизации работы процессора и оперативной памяти, формат SVR4 требует, чтобы данные каждого файла начинались с адреса, кратного 4 байтам. Если длина имени файла или размер данных не кратны 4, cpio добавляет пустые нулевые байты (padding).

    Формула расчёта количества добавляемых пустых байтов выглядит так:

    Где — это текущее смещение в байтах от начала архива (или от начала блока данных), а оператор означает остаток от деления.

    Пример: Если длина имени файла составляет 13 байт, то . Подставляем в формулу: . Значит, после имени файла cpio вставит 3 нулевых байта, чтобы само содержимое файла началось ровно с 16-го байта (что кратно 4).

    Практика: Хирургическая работа с cpio

    Как мейнтейнеру, вам регулярно придётся извлекать файлы из RPM-пакетов без их установки в систему. Для этого используется связка команд rpm2cpio и cpio.

    Базовая команда для полной распаковки пакета в текущую директорию:

    Разберём флаги утилиты cpio детально:

  • -i (extract): Переключает утилиту в режим извлечения (по умолчанию она работает в режиме создания архива).
  • -d (make directories): Разрешает создавать вложенные директории. Без этого флага cpio выдаст ошибку, если попытается извлечь файл etc/config.conf, а папки etc не существует.
  • -m (preserve modification time): Сохраняет оригинальное время изменения файлов, записанное в заголовке SVR4. Если флаг не указать, файлам будет присвоено текущее время.
  • -v (verbose): Выводит список извлекаемых файлов на экран.
  • Извлечение конкретного файла

    Представьте, что у вас есть пакет kernel-modules.rpm размером 150 МБ, и вам нужно достать из него только один конфигурационный файл.

    Поскольку cpio — потоковый формат без центрального индекса, вы не можете сказать ему "перейди к файлу X". Вы должны пропустить через cpio весь поток, указав шаблон поиска.

    Важный нюанс: Обратите внимание на звёздочку в начале пути. Внутри архива cpio пути обычно записываются как относительные (например, ./etc/modprobe.d/custom.conf). Если вы укажете точный абсолютный путь /etc/..., cpio не найдёт совпадения строк и ничего не извлечёт. Использование решает проблему несовпадения префиксов.

    Подводные камни потокового поиска

    Из-за отсутствия индекса поиск одного файла в конце большого архива займёт ровно столько же времени, сколько полная распаковка всего архива.

    Если файл custom.conf находится в самом конце потока, cpio придётся прочитать, декомпрессировать и отбросить все 149 МБ предыдущих данных, прежде чем он доберётся до нужного заголовка 070701. Это плата за простоту и надёжность формата.

    Управление метаданными и специальными файлами

    Главная причина, по которой RPM не использует формат tar (который тоже является потоковым) — это исторически более строгая и предсказуемая работа cpio со специальными файлами UNIX.

    Жёсткие ссылки (Hardlinks)

    Если в пакете есть два файла с идентичным содержимым, мейнтейнеры часто делают один файл жёсткой ссылкой на другой, чтобы сэкономить место на диске.

    В заголовке cpio есть поле Inode. Когда cpio упаковывает файлы, он запоминает их индексные дескрипторы. Если он встречает второй файл с таким же Inode, он не копирует его содержимое в архив второй раз. Вместо этого он записывает заголовок с размером данных байт.

    При распаковке cpio видит нулевой размер и совпадающий Inode, и автоматически создаёт жёсткую ссылку в файловой системе Rosa Linux. Это происходит абсолютно прозрачно для пользователя.

    Файлы устройств (Device Nodes)

    Пакеты, содержащие базовую файловую систему (например, setup или filesystem), должны создавать специальные символьные и блочные устройства в директории /dev (например, /dev/null или /dev/urandom).

    Формат cpio нативно поддерживает сохранение мажорных и минорных номеров устройств в своём заголовке. При распаковке (если утилита запущена от имени root), cpio выполнит системный вызов mknod и воссоздаст устройство с правильными характеристиками.

    Диагностика повреждений

    Понимание структуры cpio помогает при отладке повреждённых пакетов. Если при установке пакета вы получаете ошибку:

    cpio: premature end of archive

    Это означает, что поток данных оборвался до того, как cpio встретил специальный маркер конца архива. В формате SVR4 концом архива считается файл с зарезервированным именем TRAILER!!!.

    Если cpio прочитал заголовок, начал читать содержимое файла (опираясь на поле Filesize), но поток иссяк — файл на диске останется усечённым (truncated). В отличие от zip, где повреждение центрального индекса может сделать весь архив нечитаемым, cpio гарантированно извлечёт все файлы, которые успел прочитать до места обрыва.

    Умение работать с cpio напрямую — это навык, который отличает продвинутого мейнтейнера от обычного пользователя. Он позволяет инспектировать содержимое пакетов, проверять корректность путей и прав доступа ещё до того, как пакет попадёт в официальный репозиторий Rosa Linux.

    3. Архитектура базы данных RPM: Внутреннее устройство и форматы хранения

    Изучив физическую структуру RPM-пакета и потоковый формат cpio, мы понимаем, как отдельный пакет устроен внутри и как его файлы попадают на жёсткий диск. Однако операционная система — это не просто набор разрозненных файлов. Это сложная экосистема, где тысячи пакетов зависят друг от друга, обновляются, удаляются и конфликтуют.

    Чтобы управлять этим хаосом, пакетному менеджеру нужен единый центр знаний. Этим центром выступает база данных RPM (RPMDB). Для мейнтейнера Rosa Linux понимание архитектуры RPMDB критически важно: именно здесь происходят процессы разрешения зависимостей, именно эта база отвечает на ваши запросы в консоли, и именно её повреждение может сделать систему неработоспособной.

    Роль базы данных RPM в системе

    База данных RPM — это локальный реестр, хранящий метаданные обо всех установленных в системе пакетах.

    Важно понимать фундаментальное правило: RPMDB не хранит сами файлы программ. Файлы (исполняемые бинарники, конфигурации, библиотеки) распаковываются из архива cpio напрямую в файловую систему (например, в /usr/bin/ или /etc/). База данных хранит только информацию об этих файлах и пакетах.

    Каждый раз, когда вы устанавливаете пакет, происходит транзакция, состоящая из двух параллельных процессов:

  • Полезная нагрузка (Payload) распаковывается на диск.
  • Заголовок пакета (Header), содержащий метаданные, копируется и сохраняется в RPMDB.
  • Благодаря этому разделению система может мгновенно ответить на вопрос: «Какому пакету принадлежит файл /etc/passwd?» или «Установлена ли библиотека libcurl версии не ниже 7.60?».

    Эволюция форматов хранения: От BDB к SQLite

    Исторически база данных RPM располагается в директории /var/lib/rpm/. Однако то, как именно данные хранятся внутри этой директории, претерпело радикальные изменения.

    Эпоха Berkeley DB (BDB)

    На протяжении более 20 лет стандартом де-факто для RPMDB была Berkeley DB (BDB). Это высокопроизводительная встраиваемая база данных типа «ключ-значение».

    BDB обеспечивала невероятную скорость чтения, но имела фатальный архитектурный недостаток, связанный с управлением блокировками. Для обеспечения целостности данных при одновременном доступе (например, когда вы одновременно запускаете установку пакета и запрос rpm -qa), BDB использовала файлы окружения (environment files), такие как __db.001, __db.002.

    Если процесс установки прерывался нештатно (отключение питания, принудительное завершение процесса через kill -9 или сбой ядра), файлы блокировок оставались в системе навсегда. База данных переходила в состояние stale lock (зависшая блокировка). Любая последующая попытка использовать rpm или dnf приводила к бесконечному зависанию терминала. Мейнтейнерам и системным администраторам приходилось вручную удалять файлы __db.* и восстанавливать базу.

    Переход на SQLite

    Начиная с версии RPM 4.16 (которая легла в основу современных версий Rosa Linux), сообщество приняло решение отказаться от устаревшей и неподдерживаемой BDB в пользу SQLite.

    SQLite — это полноценная реляционная база данных, хранящаяся в одном файле (/var/lib/rpm/rpmdb.sqlite). Её главное преимущество — строгая поддержка принципов ACID (Атомарность, Согласованность, Изолированность, Долговечность).

    В SQLite транзакция либо выполняется целиком, либо не выполняется вообще. Если во время обновления пакета пропадёт электричество, при следующей загрузке SQLite автоматически откатит незавершённую транзакцию, используя журнал упреждающей записи (WAL — Write-Ahead Log). Проблема «зависших блокировок» исчезла как класс.

    Внутренняя архитектура RPMDB

    Независимо от того, какой движок используется под капотом, логическая структура базы данных RPM остаётся неизменной. Она строится на концепции первичного хранилища и вторичных индексов.

    !Схема архитектуры базы данных RPM: Центральная таблица Packages и указывающие на неё вторичные индексы

    Первичное хранилище: Таблица Packages

    Сердцем RPMDB является таблица Packages. Это единственное место, где хранятся реальные данные.

    Когда пакет устанавливается, RPM берёт его бинарную секцию Header (ту самую, которую мы разбирали в первой статье), присваивает ей уникальный целочисленный идентификатор (Package ID) и записывает в таблицу Packages как единый бинарный объект (BLOB).

    > Таблица Packages — это абсолютный источник истины. Если данные есть в этой таблице, пакет считается установленным. Если данных там нет — пакета в системе нет, даже если его файлы физически присутствуют на диске.

    Вторичные индексы: Математика быстрого поиска

    Если бы в RPMDB была только таблица Packages, любой поиск был бы мучительно долгим.

    Представьте, что в системе установлено 3000 пакетов, содержащих в сумме 200 000 файлов. Вы вводите команду rpm -qf /usr/bin/bash (найти пакет, владеющий этим файлом). Без индексов пакетному менеджеру пришлось бы извлекать каждый из 3000 бинарных заголовков, распаковывать их в памяти и проверять массивы путей. Это линейный поиск, сложность которого составляет , где — количество пакетов.

    Для решения этой проблемы RPMDB создаёт вторичные индексы на основе структур B-Tree (B-деревьев).

    Сложность поиска в сбалансированном B-дереве логарифмическая: .

    Сравним эффективность математически. Допустим, у нас файлов. При линейном поиске в худшем случае потребуется операций сравнения. При поиске по B-дереву количество операций составит примерно . Так как , а , поиск займёт не более 18 операций. Разница в производительности колоссальна.

    !Интерактивная визуализация поиска по B-дереву во вторичном индексе RPMDB

    Ключевые вторичные индексы в RPMDB:

  • Name: Поиск по имени пакета (например, kernel).
  • Basenames: Поиск по имени файла. Связывает имя файла с Package ID.
  • Dirnames: Поиск по директориям.
  • Providename: Индекс того, что пакет предоставляет (виртуальные зависимости, библиотеки).
  • Requirename: Индекс того, что пакет требует для своей работы.
  • Каждый вторичный индекс хранит только пару: [Искомое значение] -> [Package ID]. Найдя нужный ID в индексе, RPM мгновенно извлекает полный заголовок из таблицы Packages.

    Практика: Восстановление базы данных

    Знание архитектуры «Первичное хранилище + Вторичные индексы» объясняет механику работы самой важной команды обслуживания RPM — rpm --rebuilddb.

    Иногда вторичные индексы могут рассинхронизироваться с таблицей Packages (например, из-за аппаратного сбоя диска). В таком случае команда rpm -qa может показывать, что пакет установлен, но попытка удалить его выдаст ошибку «пакет не найден».

    Как работает rpm --rebuilddb под капотом:

  • Утилита создаёт временную директорию.
  • Она полностью игнорирует и удаляет все существующие вторичные индексы (Name, Basenames и т.д.).
  • Она открывает таблицу Packages и начинает последовательно читать каждый бинарный заголовок.
  • На основе прочитанных заголовков она заново генерирует все вторичные индексы с нуля.
  • Эта операция абсолютно безопасна, потому что вторичные индексы не содержат уникальной информации — они являются лишь производными от таблицы Packages.

    Типичная ошибка новичка: попытка сделать резервную копию RPMDB путём копирования файлов во время работы пакетного менеджера. В случае с SQLite это приведёт к копированию базы в промежуточном состоянии транзакции. Правильный способ резервного копирования — использование встроенных средств экспорта SQLite или утилиты rpmdb_dump.

    Взаимодействие с базой через CLI

    Мейнтейнеру Rosa Linux регулярно приходится обращаться к RPMDB напрямую, минуя высокоуровневые инструменты вроде dnf.

    Рассмотрим анатомию сложного запроса:

    Что происходит на уровне базы данных при выполнении этой команды:

  • RPM парсит аргумент и понимает, что нужно искать в индексе Providename.
  • Выполняется поиск по B-дереву индекса Providename для строки libcrypto.so.1.1()(64bit).
  • Индекс возвращает массив Package ID (например, ID 1045).
  • RPM обращается к таблице Packages и запрашивает BLOB для ID 1045.
  • Бинарный заголовок распаковывается в оперативной памяти.
  • Из заголовка извлекается тег RPMTAG_NAME (имя пакета, например, openssl-libs).
  • Имя выводится в консоль.
  • Всё это происходит за миллисекунды благодаря строгой реляционной структуре и логарифмической сложности поиска.

    Понимание того, что RPMDB — это просто структурированное хранилище бинарных заголовков, связанных быстрыми индексами, снимает «магию» с работы пакетного менеджера. В следующем шаге мы перейдём от изучения готовых пакетов к их созданию и разберём анатомию spec-файла — инструкции, по которой исходный код превращается в структурированный RPM-архив, готовый к записи в базу данных.

    4. Взаимодействие RPM с файловой системой: Размещение файлов и права доступа

    Изучив внутреннее устройство архива cpio и архитектуру базы данных RPMDB, мы подошли к моменту, когда абстрактные данные превращаются в реальную рабочую систему. Пакетный менеджер извлекает файлы из архива и размещает их на жёстком диске.

    Этот процесс далёк от простого копирования. Операционная система Linux строго регламентирует, где должны лежать исполняемые файлы, а где — настройки. Более того, RPM должен гарантировать, что при обновлении пакета ваши личные конфигурации не будут затёрты, а в случае внезапного отключения питания система не останется с наполовину записанным системным бинарником.

    Понимание механизмов взаимодействия RPM с файловой системой — это граница, отделяющая начинающего сборщика от профессионального мейнтейнера Rosa Linux.

    Стандарт иерархии файловой системы (FHS)

    RPM не принимает самостоятельных решений о том, куда распаковывать файлы. Он строго следует путям, прописанным внутри пакета. Однако эти пути не берутся с потолка. Все современные дистрибутивы Linux, включая Rosa Linux, подчиняются Filesystem Hierarchy Standard (FHS) — стандарту, определяющему структуру каталогов.

    Если каждый мейнтейнер будет устанавливать программы в произвольные папки (например, в /opt/my_app/ или /usr/local/), система быстро превратится в хаос. Утилиты не смогут найти нужные библиотеки, а администраторы не будут знать, где искать логи.

    Основные директории FHS, с которыми работает мейнтейнер:

    * /usr/bin/ — основные исполняемые файлы программ, доступные всем пользователям. * /usr/sbin/ — системные утилиты, требующие прав суперпользователя (например, инструменты разметки диска). * /usr/lib64/ (или /usr/lib/ для 32-битных систем) — разделяемые библиотеки (shared libraries), необходимые для работы программ. * /etc/ — глобальные конфигурационные файлы. Здесь не должно быть исполняемых бинарников. * /var/ — изменяемые данные: логи (/var/log/), кэши (/var/cache/), базы данных. * /usr/share/ — архитектурно-независимые данные: документация, иконки, локализации (переводы интерфейса).

    > Исторически в Linux существовало разделение между /bin и /usr/bin. Первоначально /bin содержал минимальный набор утилит для загрузки системы и монтирования остальных дисков. В современных дистрибутивах (концепция UsrMerge) директории /bin, /sbin и /lib являются лишь символическими ссылками на соответствующие папки внутри /usr/.

    Управление правами и владельцами файлов

    В Linux каждый файл имеет владельца (User), группу (Group) и матрицу прав доступа (Permissions — чтение, запись, выполнение).

    Когда RPM-пакет собирается на машине мейнтейнера (сборочном сервере), файлы принадлежат тому пользователю, который запустил сборку (например, пользователю builder с UID 1000). Если бы RPM просто сохранял числовые идентификаторы (UID/GID) в архив cpio и слепо применял их при установке, возникла бы критическая уязвимость. На сервере пользователя UID 1000 может принадлежать совершенно другому человеку, который внезапно получил бы полный доступ к системным файлам пакета.

    Чтобы решить эту проблему, RPM использует метаданные заголовка (Header), а не полагается исключительно на данные внутри cpio.

    Механизм трансляции имен

    В базе данных RPM и в заголовке пакета сохраняются строковые имена владельцев и групп, а не их числовые ID.

    При установке пакета RPM выполняет следующие шаги для каждого файла:

  • Читает из метаданных пакета имя владельца (например, nginx).
  • Обращается к системе (через вызовы getpwnam и getgrnam), чтобы узнать, какой UID и GID назначен пользователю nginx на текущей машине.
  • Распаковывает файл и применяет к нему локальные UID/GID.
  • Если пользователь, указанный в пакете, не существует в системе, RPM по умолчанию назначит владельцем файла пользователя root. Именно поэтому мейнтейнеры используют специальные скрипты (pre-install), которые создают нужных системных пользователей до того, как файлы начнут распаковываться.

    Атомарность операций: Как RPM защищает систему от сбоев

    Представьте ситуацию: вы обновляете системную библиотеку glibc, от которой зависят почти все программы в Linux. Пакетный менеджер начинает перезаписывать файл /usr/lib64/libc.so.6. В этот момент, когда записана только половина файла, происходит сбой питания.

    Если бы RPM перезаписывал файлы напрямую, после перезагрузки система бы не включилась — повреждённая glibc сделала бы невозможным запуск даже базовой оболочки bash.

    Для предотвращения таких катастроф RPM использует механизм атомарного обновления на основе системного вызова rename().

    !Схема атомарного обновления файла пакетным менеджером

    Процесс записи файла выглядит так:

  • RPM извлекает новый файл из архива cpio и сохраняет его в ту же директорию, но с временным именем, добавляя суффикс (например, /usr/lib64/libc.so.6;60a1b2c3).
  • RPM устанавливает для этого временного файла правильные права доступа, владельца и контекст безопасности SELinux.
  • Выполняется системный вызов rename("/usr/lib64/libc.so.6;60a1b2c3", "/usr/lib64/libc.so.6").
  • Согласно стандарту POSIX, переименование файла в пределах одной файловой системы является атомарной операцией. Это значит, что для операционной системы и всех запущенных программ файл либо существует в старой версии, либо мгновенно заменяется на новую. Состояния «наполовину записанный файл» не существует в принципе. Старый файл (inode) удаляется с диска только после того, как все программы, которые держали его открытым, завершат работу.

    Специальные типы файлов в RPM

    Помимо обычных бинарников и библиотек, RPM оперирует специальными категориями файлов, поведение которых при установке и удалении кардинально отличается.

    1. Конфигурационные файлы и защита от перезаписи

    Самая сложная логика в RPM связана с конфигурационными файлами (обычно лежащими в /etc/). Если администратор сервера потратил часы на настройку веб-сервера, обновление пакета не должно уничтожить его труд.

    В RPM конфигурационные файлы помечаются специальными макросами. Наиболее важный из них — %config(noreplace).

    Чтобы понять, нужно ли перезаписывать файл, RPM использует криптографические хеши (обычно SHA256). Пакетный менеджер анализирует три состояния: * X — хеш файла из старого пакета (каким он был при первоначальной установке). * Y — хеш файла, который сейчас физически лежит на диске. * Z — хеш файла из нового (обновляемого) пакета.

    RPM сравнивает эти хеши и принимает решение по строгой математической логике:

  • Если , значит администратор не менял файл. RPM спокойно перезаписывает его новым файлом . Никаких конфликтов.
  • Если , значит администратор внёс ручные правки.
  • * Если при этом (администратор случайно привёл файл к тому же виду, что и в новом пакете), RPM ничего не делает. * Если (самый частый случай), срабатывает защита %config(noreplace). RPM оставляет изменённый файл администратора на месте, а новый файл из пакета сохраняет рядом с суффиксом .rpmnew (например, nginx.conf.rpmnew).

    Администратор системы получает предупреждение в консоли: «warning: /etc/nginx/nginx.conf created as /etc/nginx/nginx.conf.rpmnew». Теперь его задача — вручную сравнить эти два файла и перенести новые параметры из .rpmnew в свой рабочий конфиг.

    !Интерактивный симулятор обновления конфигурационных файлов

    Существует и обратная ситуация. Если файл был помечен просто как %config (без noreplace), RPM отдаст приоритет пакету. Он перезапишет рабочий конфиг новым файлом, а труд администратора спасёт, переименовав старый файл, добавив суффикс .rpmsave.

    2. Призрачные файлы (Ghost files)

    Вторая важная категория — файлы-призраки, помечаемые макросом %ghost.

    Представьте лог-файл /var/log/nginx/access.log. Этот файл не создаётся мейнтейнером на этапе сборки пакета. Он генерируется самим веб-сервером в процессе работы.

    Если RPM ничего не знает об этом файле, то при выполнении команды rpm -e nginx (удаление пакета) лог-файл останется на диске. Со временем такие «осиротевшие» файлы могут забить всё свободное место на сервере.

    Объявляя файл как %ghost, мейнтейнер говорит пакетному менеджеру: «В архиве cpio этого файла нет, и при установке его распаковывать не нужно. Но запиши его в базу данных RPMDB как принадлежащий этому пакету. Если пакет будут удалять, и этот файл будет существовать на диске — удали его вместе с остальными».

    Файлы-призраки используются для логов, PID-файлов (хранящих номер процесса) и динамически генерируемых кэшей.

    Разрешение конфликтов на уровне файловой системы

    RPM строго следит за тем, чтобы один физический файл на диске принадлежал только одному пакету. Это фундаментальное правило целостности системы.

    Если вы попытаетесь установить пакет App-B, который содержит файл /usr/bin/tool, но этот файл уже был установлен ранее пакетом App-A, транзакция будет немедленно прервана с ошибкой:

    > file /usr/bin/tool from install of App-B conflicts with file from package App-A

    RPM не позволяет пакетам молча перезаписывать файлы друг друга. Если мейнтейнеру действительно нужно, чтобы два пакета предоставляли одну и ту же утилиту (например, vim и neovim оба хотят предоставлять команду vi), используется механизм Alternatives (символические ссылки, управляемые отдельной системной утилитой), а физические бинарники размещаются под разными именами (/usr/bin/vim.basic и /usr/bin/nvim).

    Интеграция с SELinux: Расширенные атрибуты

    В современных дистрибутивах, ориентированных на безопасность (к которым относится Rosa Linux), классической матрицы прав (чтение/запись/выполнение) недостаточно. Используется система принудительного контроля доступа — SELinux.

    SELinux работает на основе меток (контекстов безопасности), которые прикрепляются к каждому файлу в виде расширенных атрибутов файловой системы (Extended Attributes, xattr).

    Например, файл паролей /etc/shadow имеет контекст shadow_t. Даже если процесс веб-сервера каким-то образом получит права root, SELinux заблокирует ему чтение этого файла, так как веб-серверу (с контекстом httpd_t) запрещено читать файлы с контекстом shadow_t.

    RPM тесно интегрирован с SELinux. При распаковке файла из архива cpio, RPM обращается к локальной политике SELinux на целевой машине, вычисляет правильный контекст для данного пути (например, для /var/www/html/index.html это будет httpd_sys_content_t) и применяет его через системный вызов setxattr ещё до того, как файл станет доступен остальной системе.

    Если мейнтейнер разместит файлы в нестандартных директориях, нарушив FHS, RPM не сможет подобрать правильный контекст SELinux. В результате программа установится, но при попытке запуска будет заблокирована ядром операционной системы.

    Понимание того, как RPM взаимодействует с файловой системой, объясняет, почему строгие правила упаковки — это не бюрократия, а техническая необходимость. В следующем шаге мы перейдём к практике и разберём анатомию spec-файла, где научимся управлять всеми этими процессами с помощью макросов и директив.

    5. Жизненный цикл транзакции RPM: Этапы установки и обновления пакета

    Любое изменение состояния операционной системы — будь то установка крошечной утилиты или масштабное обновление ядра — представляет собой риск. В процессе записи новых файлов, изменения конфигураций и перезапуска системных служб может произойти сбой питания, нехватка места на диске или ошибка в скрипте мейнтейнера.

    Чтобы система не осталась в неработоспособном, промежуточном состоянии, пакетный менеджер RPM объединяет все действия в единую логическую единицу — транзакцию.

    Транзакция в RPM концептуально похожа на транзакцию в реляционных базах данных. Она стремится к соблюдению принципов атомарности и консистентности: набор изменений должен быть применён либо целиком, либо система должна остаться в исходном состоянии (насколько это технически возможно в рамках файловой системы POSIX).

    Понимание того, как RPM планирует, проверяет и выполняет транзакцию, позволяет мейнтейнерам Rosa Linux писать безопасные пакеты, которые корректно устанавливаются, обновляются и удаляются, не разрушая систему пользователя.

    Анатомия транзакции: От планирования до фиксации

    Процесс работы с пакетами строго разделен на два этапа: работу высокоуровневого менеджера (например, dnf в Rosa Linux) и работу низкоуровневого бэкенда (rpm).

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

    Фаза 1: Инициализация и проверка (Transaction Check)

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

  • Проверка криптографических подписей: RPM извлекает секцию Signature из каждого пакета и сверяет её с публичными GPG-ключами, установленными в системе. Если подпись недействительна, транзакция немедленно прерывается.
  • Проверка целостности зависимостей: Хотя dnf уже разрешил зависимости, RPM делает финальную проверку на уровне своей базы данных (RPMDB). Он убеждается, что все библиотеки (например, libc.so.6), требуемые новыми пакетами, действительно присутствуют в системе или находятся в текущем наборе транзакции.
  • Поиск конфликтов файлов: RPM анализирует метаданные всех пакетов в наборе и сравнивает их с RPMDB. Если два разных пакета пытаются установить файл по одному и тому же пути (например, /usr/bin/server), и это не было явно разрешено через механизм альтернатив, RPM блокирует операцию.
  • > Конфликт файлов на этапе проверки — частая ошибка начинающих мейнтейнеров. Если ваш пакет предоставляет утилиту, которая уже есть в другом пакете, транзакция упадёт с ошибкой file conflicts between attempted installs. RPM гарантирует, что один файл на диске всегда принадлежит только одному пакету.

    Фаза 2: Предтранзакционные скрипты

    Если проверки пройдены, RPM запускает скриптлеты %pretrans (Pre-transaction).

    Это особый вид скриптов, который выполняется до того, как RPM начнёт распаковывать файлы или изменять базу данных. Они используются в крайне редких и сложных случаях, когда для успешной установки пакета нужно подготовить файловую систему нестандартным образом.

    Например, если в старой версии пакета /var/lib/app был символической ссылкой, а в новой версии это должна быть директория, RPM не сможет автоматически разрешить этот конфликт при распаковке архива cpio. Мейнтейнер использует %pretrans, чтобы удалить симлинк до начала основной фазы установки.

    Фаза 3: Выполнение транзакции (Transaction Test & Run)

    Это ядро процесса. RPM начинает физически изменять систему. Самый сложный сценарий здесь — это обновление пакета (Upgrade), так как оно объединяет в себе установку новой версии и удаление старой.

    Многие полагают, что при обновлении RPM сначала удаляет старый пакет, а затем устанавливает новый. Это логичная, но в корне неверная гипотеза.

    Если бы RPM сначала удалял старый пакет, то в случае сбоя на этапе установки нового пакета система осталась бы вообще без программы. Для критичных компонентов (например, системной оболочки bash или демона сети) это означало бы смерть операционной системы.

    Поэтому RPM использует перекрывающийся подход: сначала устанавливается новое, затем удаляется старое.

    !Схема порядка выполнения скриптлетов при обновлении пакета

    Порядок выполнения при обновлении пакета версии 1 (v1) на версию 2 (v2) выглядит так:

  • %pre (v2): Выполняется скрипт подготовки нового пакета. Обычно здесь создаются системные пользователи и группы, необходимые для новых файлов.
  • Распаковка файлов (v2): RPM извлекает файлы из архива cpio пакета v2. Файлы записываются на диск с использованием механизма атомарного переименования. Если файл существовал в v1, он перезаписывается (с учётом правил защиты конфигурационных файлов %config(noreplace)).
  • %post (v2): Выполняется скрипт настройки нового пакета. Здесь регистрируются системные службы (systemctl daemon-reload), обновляются кэши шрифтов или иконок.
  • %preun (v1): Начинается фаза удаления старого пакета. Выполняется скрипт перед удалением. Здесь обычно останавливаются службы старой версии, если они больше не нужны.
  • Удаление файлов (v1): RPM удаляет с диска те файлы, которые принадлежали пакету v1, но отсутствуют в пакете v2. Те файлы, которые есть в обеих версиях, не трогаются (они уже были перезаписаны на шаге 2).
  • %postun (v1): Выполняется скрипт после удаления старого пакета. Обычно используется для очистки временных файлов или перезапуска служб.
  • !Интерактивный симулятор транзакции обновления RPM

    Фаза 4: Посттранзакционные скрипты и фиксация

    После того как все пакеты из набора транзакции были обработаны, RPM запускает скриптлеты %posttrans (Post-transaction).

    Они нужны для действий, которые имеют смысл только после того, как вся транзакция завершена. Например, если вы устанавливаете 50 шрифтов разными пакетами, нет смысла обновлять системный кэш шрифтов 50 раз в секциях %post каждого пакета. Логичнее сделать это один раз в %posttrans последнего пакета.

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

    Математика состояний: Аргумент

    Поскольку одни и те же скрипты (%pre, %post, %preun, %postun) вызываются и при чистой установке, и при обновлении, и при удалении, скрипту нужно понимать, в каком контексте он сейчас запущен.

    Для этого RPM передаёт в каждый скриптлет специальный числовой аргумент — NNN = 1N = 2N = 1N = 01 -ge 1 ]; then systemctl try-restart my_daemon.service >/dev/null 2>&1 || : fi

    Если N == 0, значит это полное удаление. Ничего перезапускать не нужно.

    bash %pre useradd -r -s /sbin/nologin myapp_user bash %pre getent passwd myapp_user >/dev/null || useradd -r -s /sbin/nologin myapp_user ` Здесь мы сначала проверяем наличие пользователя. Если он есть, команда useradd просто не вызывается. Скрипт всегда завершается успешно, сколько бы раз его ни запускали.

    Триггеры: Реакция на чужие транзакции

    Обычные скриптлеты реагируют на установку или удаление своего собственного пакета. Но архитектура RPM предоставляет более мощный инструмент — триггеры (Triggers).

    Триггеры позволяют пакету A выполнить код, когда устанавливается или удаляется пакет B.

    Классический пример — пакет man-db, который отвечает за индексацию справочных страниц (man pages). В системе могут быть тысячи пакетов, и каждый из них может принести с собой новые страницы руководства в директорию /usr/share/man/.

    Если бы триггеров не существовало, каждому мейнтейнеру пришлось бы добавлять в свой пакет команду обновления индекса mandb. Это нарушило бы принцип DRY (Don't Repeat Yourself) и привело бы к хаосу.

    Вместо этого пакет man-db содержит файловый триггер %transfiletriggerin. RPM отслеживает все файлы, распаковываемые во время транзакции. Если какой-либо пакет (неважно какой) кладёт файл в /usr/share/man/, RPM ставит галочку. В конце транзакции RPM вызывает триггер пакета man-db ровно один раз. Индекс обновляется централизованно.

    Триггеры делают дистрибутив Rosa Linux модульным. Пакеты не обязаны знать друг о друге напрямую, они взаимодействуют через события файловой системы и транзакции RPM.

    Заключительный этап транзакции

    После того как все файлы распакованы, старые удалены, а скрипты и триггеры отработали, RPM обновляет свою базу данных (SQLite).

    Записи о старых версиях пакетов удаляются из таблицы Packages`, записи о новых — добавляются. Перестраиваются B-деревья индексов (Name, Basenames, Providename), о которых мы говорили в предыдущих статьях.

    Только в этот момент транзакция считается успешно завершённой. Блокировка с базы данных снимается, и система готова к следующей операции.

    Понимание жизненного цикла транзакции завершает теоретический фундамент RPM. Вы знаете, как пакет устроен внутри (cpio), как он хранится в системе (RPMDB), как взаимодействует с файловой системой (FHS, SELinux) и как безопасно изменяет состояние ОС (Транзакции).

    Этот багаж знаний — ваш пропуск к практической работе. На следующем этапе мы откроем текстовый редактор и начнём писать свой первый spec-файл, превращая исходный код в полноценный RPM-пакет для Rosa Linux.

    6. Исполняемые скрипты (Scriptlets): Порядок выполнения pre, post, preun и postun

    Исполняемые скрипты (Scriptlets): Порядок выполнения pre, post, preun и postun

    Распаковка файлов из архива cpio на жёсткий диск — это лишь половина задачи при установке программного обеспечения. Сами по себе бинарные файлы и конфигурации статичны. Чтобы программа стала полноценной частью операционной системы, её нужно интегрировать в системное окружение.

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

    Для выполнения этих динамических задач архитектура RPM предоставляет механизм скриптлетов (scriptlets) — встроенных в пакет исполняемых сценариев, которые запускаются на строго определённых этапах транзакции.

    Ограничения статической распаковки

    Представьте, что вы собираете пакет с веб-сервером Nginx. Внутри архива cpio лежат исполняемый файл /usr/sbin/nginx и конфигурация /etc/nginx/nginx.conf.

    Согласно правилам безопасности, веб-сервер не должен работать от имени суперпользователя root. Ему требуется выделенный системный пользователь, например, nginx. Если пакетный менеджер просто распакует файлы, система ничего не будет знать об этом пользователе. При попытке запуска служба выдаст ошибку и завершит работу.

    RPM не умеет автоматически угадывать, какие пользователи нужны программе, или какие службы нужно перезапустить. Эта ответственность полностью лежит на мейнтейнере пакета, который должен написать соответствующие инструкции на языке командной оболочки (обычно bash).

    Базовая четвёрка скриптлетов

    Жизненный цикл любого пакета опирается на четыре основных скриптлета. Они делятся на две группы: выполняемые при установке (Install) и выполняемые при удалении (Erase).

    | Директива | Название | Момент запуска | Типичные задачи | | :--- | :--- | :--- | :--- | | %pre | Pre-install | До распаковки файлов пакета на диск | Создание системных пользователей и групп. Подготовка нестандартных директорий. | | %post | Post-install | После распаковки файлов пакета на диск | Регистрация служб (systemctl daemon-reload), обновление кэшей (шрифты, иконки, ldconfig), вывод информационных сообщений. | | %preun | Pre-uninstall | До удаления файлов пакета с диска | Остановка работающих служб (systemctl stop), удаление пакета из альтернатив. | | %postun | Post-uninstall | После удаления файлов пакета с диска | Перезапуск зависящих служб, очистка временных файлов, которые программа создала во время работы (логи, кэши), обновление системных кэшей. |

    Важно понимать контекст выполнения: когда запускается %pre, файлов вашего пакета на диске ещё нет. Вы не можете вызвать утилиту, которую сами же и устанавливаете. Напротив, когда запускается %postun, файлов вашего пакета на диске уже нет.

    Анатомия обновления: Перекрытие состояний

    Самый сложный и критически важный процесс в RPM — это обновление пакета (Upgrade). Как мы выяснили при изучении транзакций, обновление не является последовательным удалением старой версии и установкой новой. Это перекрывающийся процесс.

    Представим, что в системе установлена версия 1.0 (v1), и мы обновляем её до версии 2.0 (v2). Порядок выполнения скриптлетов будет следующим:

  • Выполняется %pre от новой версии (v2).
  • Распаковываются файлы новой версии (v2).
  • Выполняется %post от новой версии (v2).
  • Выполняется %preun от старой версии (v1).
  • Удаляются файлы старой версии (v1), которых нет в новой.
  • Выполняется %postun от старой версии (v1).
  • !Схема перекрытия скриптлетов при обновлении пакета

    Этот порядок порождает ловушку обновления, в которую часто попадают начинающие мейнтейнеры.

    Допустим, ваш пакет содержит системную службу. Логично предположить, что при удалении пакета службу нужно остановить. Мейнтейнер пишет в старом пакете (v1):

    А при установке пакета службу нужно запустить. Мейнтейнер пишет в новом пакете (v2):

    Что произойдёт при обновлении с v1 на v2 согласно порядку выполнения RPM?

  • Распакуются файлы v2.
  • Отработает %post (v2) — служба запустится.
  • Отработает %preun (v1) — служба остановится.
  • Итог: после успешного обновления пакета служба окажется выключенной. Это критическая ошибка, которая в масштабах дистрибутива Rosa Linux может привести к потере сетевого доступа к серверу или остановке баз данных.

    Чтобы избежать этой проблемы, скриптам нужен механизм понимания контекста: происходит ли сейчас чистая установка, полное удаление или промежуточный этап обновления.

    Математика состояний: Аргумент 1.

    Значение NNN = 1N = 2N = 1N = 01 -eq 0 ]; then # Выполняется ТОЛЬКО при полном удалении пакета systemctl stop mydaemon.service systemctl disable mydaemon.service fi bash %postun if [ 1 вручную для стандартных задач (таких как управление службами systemd или пользователями).

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

    Например, для управления службой достаточно написать:

    Использование макросов гарантирует, что пакет будет вести себя единообразно с остальной системой и корректно обрабатывать все краевые случаи (например, работу в изолированном окружении chroot при сборке образов ОС).

    Альтернативные интерпретаторы (Lua)

    По умолчанию все скриптлеты выполняются интерпретатором /bin/sh. Однако это создаёт проблему зависимостей: чтобы установить пакет, в системе уже должен быть установлен bash и базовые утилиты (coreutils).

    Для большинства пакетов это не проблема. Но как установить самый первый пакет в абсолютно пустой системе (например, при создании минимального Docker-контейнера)?

    RPM имеет встроенный интерпретатор легковесного языка Lua. Скриптлеты, написанные на Lua, выполняются самим процессом RPM, без вызова внешних оболочек и без создания новых процессов (fork).

    Чтобы указать альтернативный интерпретатор, используется флаг -p:

    Скриптлеты на Lua часто используются в базовых системных пакетах (например, glibc или setup), чтобы разорвать циклические зависимости на этапе начальной загрузки системы (bootstrap).

    Инструменты отладки

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

    Вытащить скриптлеты из собранного RPM-файла можно с помощью команды:

    Эта команда выведет полный текст всех секций %pre, %post, %preun и %postun, включая уже развёрнутые макросы. Это незаменимый инструмент для аудита безопасности перед установкой пакетов из недоверенных источников.

    Если вы хотите установить пакет, но подозреваете, что его скрипты могут повредить систему, RPM позволяет отключить их выполнение с помощью флагов:

    * --noscripts — отключает выполнение вообще всех скриптлетов. * --nopre, --nopost, --nopreun, --nopostun — отключают выполнение конкретных этапов.

    > Использование флагов отключения скриптов почти всегда приводит к тому, что установленная программа будет работать некорректно (не создадутся пользователи, не запустятся службы). Этот механизм предназначен исключительно для восстановления сломанной системы или исследовательских целей.

    Понимание порядка выполнения скриптлетов и математики состояний $1` — это водораздел между новичком, который просто копирует файлы в архив, и профессиональным мейнтейнером, который создаёт надёжные, отказоустойчивые пакеты, органично встраивающиеся в экосистему Rosa Linux.

    7. Механизм триггеров в RPM: Реакция на изменение состояния других пакетов

    Механизм триггеров в RPM: Реакция на изменение состояния других пакетов

    В предыдущих материалах мы детально разобрали жизненный цикл пакета и работу базовых скриптлетов (%pre, %post, %preun, %postun). Они отлично справляются с настройкой окружения для самого устанавливаемого пакета. Однако операционная система — это не набор изолированных программ, а сложная экосистема, где компоненты постоянно взаимодействуют друг с другом.

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

    Для решения этой задачи в архитектуре RPM реализован механизм триггеров (triggers) — реактивная система выполнения скриптов, построенная на паттерне «Наблюдатель» (Observer). Триггеры позволяют одному пакету пассивно ожидать изменений в состоянии других пакетов или файловой системы и выполнять код только при наступлении определённых событий.

    Проблема жёстких связей и инверсия контроля

    Представьте, что вы собираете пакет текстового редактора Vim. В архиве пакета есть файл документации vim.1.gz, который должен быть проиндексирован системной утилитой man-db, чтобы пользователь мог найти его через команду man -k editor.

    Если использовать стандартный подход со скриптлетами, в %post пакета Vim нужно добавить команду обновления базы данных руководств:

    Этот подход имеет три критических недостатка:

  • Избыточность кода: Мейнтейнерам тысяч пакетов (от Vim до Nginx) придётся копировать эту команду в свои spec-файлы.
  • Жёсткая зависимость: Если в минимальной серверной сборке Rosa Linux пакет man-db не установлен (для экономии места), скриптлет Vim завершится с ошибкой, так как не найдёт исполняемый файл /usr/bin/mandb.
  • Катастрофическое падение производительности: При обновлении системы из 500 пакетов, каждый из которых содержит man-страницы, тяжёлая команда mandb будет запущена 500 раз подряд.
  • Архитектура RPM решает это через инверсию контроля. Не Vim должен сообщать системе о своей документации, а утилита man-db должна наблюдать за появлением новых файлов документации и реагировать на них.

    Именно для этого созданы триггеры. Они перекладывают ответственность за интеграцию с устанавливаемого пакета на тот пакет, который предоставляет инфраструктуру.

    Классические триггеры пакетов

    Исторически первым механизмом реактивности в RPM стали классические триггеры, реагирующие на изменение состояния конкретного пакета по его имени.

    Пакет-наблюдатель объявляет в своём spec-файле специальные директивы, указывая имя целевого пакета. Как только целевой пакет устанавливается или удаляется, RPM приостанавливает текущую транзакцию, находит в базе данных RPMDB все связанные триггеры и выполняет их.

    Базовая тройка директив

    | Директива | Условие срабатывания | Типичное применение | | :--- | :--- | :--- | | %triggerin -- target_pkg | Выполняется, когда устанавливается target_pkg (при условии, что пакет с триггером уже установлен), ИЛИ когда устанавливается пакет с триггером (при условии, что target_pkg уже установлен). | Регистрация плагинов, добавление конфигураций для появившейся службы. | | %triggerun -- target_pkg | Выполняется непосредственно перед удалением target_pkg (если пакет с триггером установлен), ИЛИ перед удалением пакета с триггером (если target_pkg установлен). | Безопасное отключение интеграции до того, как файлы целевого пакета исчезнут. | | %triggerpostun -- target_pkg | Выполняется после того, как target_pkg был удалён с диска. | Очистка кэшей, удаление символических ссылок на уже несуществующие файлы. |

    > Важное правило симметрии: триггер %triggerin гарантированно сработает независимо от порядка установки пакетов. Если сначала установить веб-сервер Apache, а затем модуль PHP, сработает триггер внутри PHP. Если сначала установить PHP, а затем Apache, триггер внутри PHP сработает в момент завершения установки Apache.

    Математика состояний в триггерах: 2

    Как и в обычных скриптлетах, триггерам необходимо понимать контекст транзакции (чистая установка, обновление, удаление). Однако ситуация усложняется тем, что в событии участвуют два независимых пакета.

    Поэтому интерпретатор bash получает два числовых аргумента: * — количество экземпляров пакета, содержащего триггер, которое останется в системе после текущего шага. * — количество экземпляров целевого пакета, которое останется в системе после текущего шага.

    Рассмотрим сложный сценарий: в системе установлен пакет plugin-v1 (содержащий триггер на app). Мы обновляем целевую программу app-v1 до app-v2.

    В процессе обновления app RPM выполнит следующие шаги, вызывая триггеры из plugin-v1:

  • Устанавливается app-v2. В системе временно два экземпляра app. Срабатывает %triggerin из плагина. Значения: (плагин один), (две версии app).
  • Удаляется app-v1. Срабатывает %triggerun из плагина. Значения: , (останется только новая версия app).
  • Завершается удаление app-v1. Срабатывает %triggerpostun из плагина. Значения: , .
  • Эта сложная матрица состояний позволяет мейнтейнерам писать идемпотентные скрипты, которые не ломают систему при перекрывающихся обновлениях.

    !Интерактивный симулятор состояний триггеров RPM

    Эволюция: Файловые триггеры (File Triggers)

    Классические триггеры решали проблему инверсии контроля, но имели фатальный архитектурный изъян: необходимость знать имя целевого пакета.

    Возвращаясь к примеру с man-db: чтобы классический триггер работал, мейнтейнер man-db должен был бы перечислить в spec-файле имена всех пакетов в дистрибутиве, содержащих документацию:

    Это физически невозможно поддерживать. Дистрибутивы пытались обходить это через виртуальные зависимости (Provides), но система оставалась хрупкой.

    Революцией в RPM стало появление файловых триггеров (%filetriggerin, %filetriggerun, %filetriggerpostun).

    Вместо наблюдения за именами пакетов, файловые триггеры наблюдают за путями в файловой системе. Если любой устанавливаемый пакет содержит хотя бы один файл, путь к которому совпадает с маской триггера, RPM выполнит скрипт.

    Синтаксис и механика работы

    Файловый триггер объявляется с указанием директории или префикса пути:

    В этом примере пакет desktop-file-utils (отвечающий за меню приложений в графических средах) устанавливает триггер на директорию /usr/share/applications. Теперь, если вы установите Firefox, GIMP или любую другую программу, содержащую файл .desktop в этой директории, RPM автоматически вызовет update-desktop-database.

    Передача списка файлов через stdin

    Файловому триггеру часто нужно знать не просто факт изменения директории, а точный список добавленных или удалённых файлов. RPM передаёт этот список скрипту через стандартный поток ввода (stdin), по одному файлу на строку.

    Это позволяет писать высокооптимизированные скрипты. Пример триггера, который компилирует схемы GSettings (настройки GNOME) только для тех директорий, где файлы реально изменились:

    Механизм триггеров — это вершина эволюции управления зависимостями в RPM. Переход от жёстких связей к реактивной модели файловых триггеров позволил современным дистрибутивам, таким как Rosa Linux, стать модульными, быстрыми и устойчивыми к изменениям конфигурации. Понимание этой архитектуры позволяет мейнтейнерам создавать пакеты, которые органично и незаметно встраиваются в систему, не требуя ручного вмешательства администратора.

    8. Система макросов RPM: Принципы работы, синтаксис и порядок вычисления

    Система макросов RPM: Принципы работы, синтаксис и порядок вычисления

    В предыдущих материалах мы изучили, как RPM-пакет устроен физически, как он взаимодействует с файловой системой и базой данных, а также как работают скриптлеты и триггеры в момент установки пакета на целевую систему. Однако перед тем как пакет будет установлен, его нужно собрать.

    Сборка пакета управляется специальным текстовым файлом — spec-файлом (спеком). Чтобы spec-файлы тысяч пакетов в дистрибутиве Rosa Linux оставались читаемыми, стандартизированными и легко обновляемыми, архитектура RPM использует мощный механизм абстракции — систему макросов.

    Макросы в RPM — это не переменные в классическом понимании языков программирования. Это интеллектуальный движок текстовой подстановки (препроцессор), который обрабатывает spec-файл до того, как начнется фактическая сборка пакета.

    Зачем нужны макросы: Проблема жесткого кодирования

    Представьте, что вы пишете инструкцию по сборке программы. Программа должна установить свой исполняемый файл в директорию /usr/bin. Вы можете написать в spec-файле прямой путь:

    Этот подход называется жестким кодированием (hardcoding), и в масштабах дистрибутива он приводит к катастрофе.

    Допустим, архитектурный комитет Rosa Linux принимает решение: в новой версии ОС все бинарные файлы 64-битных систем должны лежать не в /usr/bin, а в /usr/bin64 (гипотетический пример для наглядности). Если пути жестко закодированы, мейнтейнерам придется вручную найти и исправить тысячи spec-файлов. Это займет месяцы и неизбежно приведет к ошибкам.

    Система макросов решает эту проблему через абстракцию. Вместо прямого пути мейнтейнер использует макрос:

    Макрос %{_bindir} определяется на уровне конфигурации самого дистрибутива. При сборке пакета RPM автоматически заменит %{_bindir} на актуальный путь для текущей системы. Если стандарты изменятся, достаточно будет обновить значение макроса в одном конфигурационном файле дистрибутива, и при следующей пересборке все тысячи пакетов автоматически получат новые правильные пути.

    Анатомия макроса: Синтаксис и изоляция

    Вызов макроса в RPM всегда начинается с символа процента %. Существует два основных способа записи:

  • Простой вызов: %name
  • Изолированный вызов: %{name}
  • Хотя оба варианта технически работают, стандарты разработки пакетов (в том числе в Rosa Linux) строго предписывают использовать изолированный вызов с фигурными скобками везде, где макрос сливается с обычным текстом.

    Рассмотрим пример формирования имени архивного файла. Нам нужно получить строку nginx-1.24.tar.gz, используя макросы имени (name) и версии (version).

    Если написать без скобок: %name-%version.tar.gz

    RPM может запутаться, пытаясь найти макрос с именем %version.tar, так как точка является допустимым символом в некоторых контекстах.

    Правильная запись с изоляцией четко указывает парсеру границы имени макроса: %{name}-%{version}.tar.gz

    Иерархия конфигурации: Где живут макросы?

    Когда RPM встречает в тексте конструкцию %{_bindir}, он должен найти её значение. Поиск происходит по строго определенной иерархии конфигурационных файлов. Эта многоуровневая система позволяет переопределять поведение сборщика на разных уровнях — от базового стандарта до конкретного пользователя.

    !Иерархия конфигурационных файлов макросов RPM

    Порядок загрузки и переопределения макросов (от низшего приоритета к высшему):

  • Базовые макросы RPM: /usr/lib/rpm/macros
  • Здесь хранятся фундаментальные определения, поставляемые разработчиками самого пакетного менеджера RPM. Этот файл редактировать запрещено.

  • Макросы дистрибутива: /usr/lib/rpm/macros.d/ и /etc/rpm/macros.
  • Здесь Rosa Linux задает свои стандарты: флаги компилятора по умолчанию, пути к системным директориям, специфичные для дистрибутива функции (например, макросы для интеграции с systemd).

  • Пользовательские макросы: ~/.rpmmacros
  • Файл в домашней директории мейнтейнера. Здесь разработчик может указать свое имя, email для подписи пакетов или переопределить директорию сборки (чтобы не собирать пакеты от имени root, что является грубейшим нарушением безопасности).

  • Определения внутри spec-файла:
  • Макросы, заданные непосредственно в тексте спека с помощью %define или %global, имеют наивысший приоритет и переопределяют любые системные настройки для конкретной сборки.

    > Чтобы посмотреть, какое значение примет макрос на вашей текущей системе с учетом всей иерархии, используйте команду: > rpm --eval "%{_bindir}" > Это главный инструмент отладки для любого мейнтейнера.

    Механизм вычисления: Рекурсия и ленивая загрузка

    Движок макросов RPM работает по принципу рекурсивной подстановки. Это означает, что значение одного макроса может содержать другие макросы. RPM будет продолжать процесс подстановки до тех пор, пока в строке не останется ни одного символа % (или пока не упрется в лимит рекурсии, защищающий от бесконечных циклов).

    Рассмотрим, как RPM вычисляет путь к директории с документацией %{_docdir}:

  • RPM видит %{_docdir}.
  • В системных настройках _docdir определен как %{_datadir}/doc.
  • Строка превращается в %{_datadir}/doc.
  • RPM ищет _datadir. Он определен как %{_prefix}/share.
  • Строка превращается в %{_prefix}/share/doc.
  • RPM ищет _prefix. Он определен как /usr.
  • Строка превращается в /usr/share/doc. Больше макросов нет, вычисление завершено.
  • !Интерактивный визуализатор рекурсивного раскрытия макросов

    %define против %global: Главная ловушка мейнтейнера

    Внутри spec-файла мейнтейнер может создавать собственные макросы. Для этого исторически использовалась директива %define. Однако позже была введена директива %global. Понимание разницы между ними — критический навык, отличающий новичка от профессионала.

    Разница заключается в моменте вычисления (eager vs lazy evaluation) и области видимости (scope).

    %define (Ленивое вычисление и локальная область)

    Когда вы используете %define, RPM просто запоминает строку текста «как есть». Вычисление вложенных макросов происходит только в тот момент, когда макрос вызывается.

    В этом примере Source0 получит значение myapp-2.0.tar.gz. Макрос %{my_archive} вычислился в момент вызова, подхватив последнее актуальное значение my_version.

    Кроме того, %define, объявленный внутри блока подпакета (например, %package devel), действует только внутри этого блока. Как только парсер выходит из блока, макрос исчезает.

    %global (Жадное вычисление и глобальная область)

    Когда вы используете %global, RPM вычисляет все вложенные макросы немедленно, в момент объявления, и фиксирует результат. Область видимости такого макроса распространяется на весь spec-файл до самого конца.

    В этом случае Source0 получит значение myapp-1.0.tar.gz. В момент выполнения второй строки %{my_version} был равен 1.0, и это значение намертво «запеклось» в my_archive.

    Золотое правило Rosa Linux: Всегда используйте %global для определения пользовательских переменных в начале spec-файла. Использование %define оправдано только в сложных конструкциях с многократным переопределением внутри специфичных секций, что требуется крайне редко.

    Условные конструкции и проверка существования

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

    Базовый синтаксис выглядит так:

    Проверка существования макроса: Конструкция %{?}

    Самая частая логическая операция в RPM — проверка, задан ли макрос вообще. Для этого используется знак вопроса ? внутри фигурных скобок.

    Конструкция %{?macro_name} работает следующим образом:

  • Если макрос macro_name существует, она возвращает его значение.
  • Если макроса не существует, она не возвращает ничего (пустую строку), и при этом не вызывает ошибку парсера.
  • Это критически важно для флагов компиляции. Например, в Rosa Linux есть макрос %{?_smp_mflags}, который содержит флаги для многопоточной сборки (например, -j8).

    Если пакет собирается на мощном сервере Rosa, макрос раскроется в make -j8. Если спек перенесут на старую систему, где этот макрос не определен, строка превратится просто в make, и сборка не упадет с ошибкой.

    Инверсия и тернарный оператор

    Восклицательный знак ! инвертирует логику. Конструкция %{!?macro_name} вернет истину (выполнит следующий за ней код), только если макрос не определен.

    RPM также поддерживает подобие тернарного оператора для вывода альтернативного текста: %{?with_debug: --enable-debug}

    Эта строка читается так: «Если макрос with_debug существует (независимо от его значения), выведи текст --enable-debug». Это элегантный способ передавать опциональные флаги скрипту конфигурации configure.

    Выполнение shell-команд во время парсинга: %()

    Иногда статического текста недостаточно. Мейнтейнеру может понадобиться получить динамические данные из операционной системы в момент сборки пакета. Для этого используется конструкция %(команда).

    Всё, что находится внутри скобок, передается системному командному интерпретатору (bash). Результат выполнения команды (stdout) возвращается в spec-файл как текст.

    Пример получения текущей даты для формирования версии ночной сборки (nightly build):

    Важнейшее концептуальное отличие: Не путайте %(команда) со скриптлетами %post или %pre, которые мы изучали в предыдущих статьях.

  • Команда внутри %() выполняется на сборочном сервере (в сборочной среде ABF для Rosa Linux) в момент чтения spec-файла.
  • Команды внутри %post выполняются на компьютере конечного пользователя в момент установки готового RPM-пакета.
  • Если вы напишете %(rm -rf /tmp/cache), кэш будет удален на сервере сборки. Пользователь, устанавливающий пакет, этого не почувствует.

    Параметризованные макросы (Макросы-функции)

    Макросы могут принимать аргументы, работая как функции в программировании. Внутри тела такого макроса аргументы доступны через переменные %1, %2 и так далее. Количество переданных аргументов хранится в %#.

    Самый известный параметризованный макрос, с которым сталкивается каждый мейнтейнер — это %setup. Он используется в секции %prep для распаковки исходных кодов.

    Когда вы пишете: %setup -q -n %{name}-%{version}

    Вы вызываете сложный встроенный макрос, передавая ему флаги -q (quiet, тихая распаковка) и -n (указание нестандартного имени директории внутри архива). Движок RPM парсит эти аргументы и генерирует на их основе длинный bash-скрипт, который выполняет команды tar, cd и chmod.

    Создание собственных параметризованных макросов выглядит так:

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

    Встроенный интерпретатор Lua

    В современных версиях RPM (используемых в актуальных релизах Rosa Linux) встроен полноценный интерпретатор языка программирования Lua. Это позволяет писать макросы со сложной логикой, циклами и обработкой строк, не вызывая внешние shell-процессы, что значительно ускоряет парсинг spec-файлов.

    Вызов Lua-кода осуществляется через макрос %{lua: ... }.

    В этом примере Lua вычисляет длину имени пакета. Использование Lua — это продвинутый уровень мейнтейнерства, который применяется в базовых системных пакетах дистрибутива для сложной маршрутизации зависимостей.

    Резюме архитектурной роли макросов

    Система макросов RPM — это мост между абстрактным намерением разработчика («я хочу установить этот файл в системную директорию для бинарников») и конкретной реализацией дистрибутива («в Rosa Linux эта директория находится по пути /usr/bin»).

    Понимание того, как макросы раскрываются, в каком порядке они читаются из конфигурационных файлов и как правильно использовать %global вместо %define, позволяет создавать надежные, переносимые и легко поддерживаемые пакеты. В следующей статье мы применим эти знания на практике, разобрав анатомию spec-файла от первой до последней строчки.

    9. Конфигурационные файлы RPM: Настройка поведения через rpmrc и rpmmacros

    Любая сложная программная система требует разделения логики работы и параметров среды, в которой она выполняется. В контексте сборки пакетов для Rosa Linux это означает, что один и тот же spec-файл (инструкция по сборке) должен без изменений работать на сервере с архитектурой x86_64, на ARM-плате и на локальном ноутбуке разработчика.

    Чтобы достичь такой универсальности, пакетный менеджер RPM опирается на двухуровневую систему конфигурации. За адаптацию к аппаратному обеспечению и операционной системе отвечает подсистема rpmrc (RPM Run Control), а за настройку путей, флагов компилятора и пользовательских предпочтений — подсистема rpmmacros. Понимание того, как эти файлы взаимодействуют друг с другом, является границей, отделяющей начинающего сборщика от профессионального мейнтейнера.

    Историческое разделение: Почему файлов два?

    На первый взгляд может показаться нелогичным наличие двух разных систем конфигурации. Почему нельзя хранить всё в одном файле?

    Ответ кроется в эволюции RPM. Изначально (в середине 1990-х годов) существовал только файл rpmrc. Он отвечал за всё: от определения архитектуры процессора до путей на диске. Однако по мере роста сложности дистрибутивов стало ясно, что синтаксис rpmrc, основанный на жестко заданных директивах, недостаточно гибок.

    Был разработан мощный движок текстовых подстановок — система макросов, которую мы подробно разобрали в предыдущей статье. Макросы взяли на себя 90% задач по конфигурации среды. Тем не менее, rpmrc не был удален. Он был оставлен для выполнения самой низкоуровневой задачи: определения физической реальности, в которой запущен RPM.

    Аналогия из мира компьютеров: * rpmrc — это BIOS/UEFI. Он опрашивает железо, понимает, какой процессор установлен, и задает базовые правила совместимости. * rpmmacros — это операционная система и переменные окружения. Они определяют, в каких папках лежат файлы, как зовут пользователя и с какими параметрами запускать программы.

    Архитектура rpmrc: Железо и совместимость

    Файлы rpmrc используют простой линейный синтаксис: ключ: значение. Они загружаются в строгом порядке, где каждый следующий файл может переопределить или дополнить значения предыдущего.

    Иерархия загрузки rpmrc

    | Уровень | Путь к файлу | Назначение | Редактирование | | :--- | :--- | :--- | :--- | | Системный базовый | /usr/lib/rpm/rpmrc | Фундаментальные правила RPM. Поставляется вместе с самим пакетным менеджером. | Запрещено | | Уровень дистрибутива | /etc/rpmrc | Специфичные настройки Rosa Linux для всей системы. | Только администратором (root) | | Пользовательский | ~/.rpmrc | Локальные переопределения конкретного мейнтейнера. | Свободное |

    Магия трансляции архитектур

    Главная задача rpmrc — управление зоопарком аппаратных архитектур. В мире Linux существует множество процессоров, которые технически отличаются, но программно совместимы.

    Например, процессоры Intel Pentium (i586) и Intel Core (x86_64) — это разные архитектуры. Но 64-битный процессор может выполнять 32-битный код. Как RPM узнает об этом? Через директиву arch_compat (совместимость архитектур).

    В системном файле rpmrc можно найти следующие строки:

    Эта запись читается справа налево и означает: «Если пакет собран для архитектуры i386, его можно установить на i486, i586, i686, amd64 и x86_64». Именно благодаря этой строке вы можете установить старую 32-битную библиотеку на современную 64-битную систему Rosa Linux. Без rpmrc пакетный менеджер выдал бы ошибку несовместимости.

    !Интерактивное дерево совместимости архитектур RPM

    Вторая важнейшая директива — buildarchtranslate (трансляция архитектуры при сборке).

    Допустим, вы собираете пакет на процессоре i686. Если вы не укажете иное, RPM соберет пакет с пометкой i686. Но что, если вы хотите, чтобы пакет был максимально универсальным и работал даже на древних i386?

    Эта директива принудительно указывает RPM: «Даже если мы физически находимся на i686, помечай собранные бинарные пакеты как i386».

    Оптимизация компилятора: optflags

    Третья ключевая функция rpmrc — связывание архитектуры с флагами компилятора (GCC/Clang). Директива optflags задает базовые параметры оптимизации кода для конкретного процессора.

    Когда вы запускаете сборку пакета, RPM сначала через rpmrc определяет текущую архитектуру (например, x86_64), затем находит соответствующую строку optflags и передает эти флаги в систему макросов (в макрос %{optflags}), который затем подставляется в spec-файл при вызове make.

    Архитектура rpmmacros: Среда сборки и пути

    Если rpmrc настраивает фундамент, то макросы настраивают рабочее пространство. Иерархия загрузки макросов (от /usr/lib/rpm/macros до ~/.rpmmacros) была подробно разобрана в предыдущей статье. Теперь мы сфокусируемся на практическом применении пользовательского файла ~/.rpmmacros.

    Главный грех мейнтейнера: Сборка от имени root

    Исторически сложилось так, что по умолчанию RPM ожидает найти директории для сборки пакетов по пути /usr/src/rpm (или /usr/src/packages). Запись в эту директорию разрешена только суперпользователю (root).

    Сборка пакетов от имени root — это грубейшее нарушение безопасности и стандартов разработки Rosa Linux.

    Процесс сборки пакета включает выполнение произвольных shell-скриптов из исходных кодов программы (например, скрипта configure или Makefile). Если в этих скриптах есть ошибка (или вредоносный код), запущенная от имени root команда может уничтожить операционную систему.

    > Классический пример катастрофы: > В Makefile программы есть строка очистки временных файлов: rm -rf HOME)/rpmbuild text %packager Иван Иванов <ivan.ivanov@example.com> %vendor Rosa Linux Community text %_smp_mflags -j4 bash rpm --showrc | grep -A 2 "optflags: x86_64" bash rpm --eval "%_topdir" `

    Если команда вернет /home/ivan/rpmbuild, значит ваша конфигурация ~/.rpmmacros применена успешно, и вы готовы к безопасной сборке пакетов.

    Резюме взаимодействия

    Процесс инициализации среды сборки RPM можно описать как строгую последовательность:

  • Запускается процесс rpmbuild.
  • Читаются файлы rpmrc (от системных к пользовательским). Определяется физическая архитектура процессора.
  • На основе архитектуры из rpmrc извлекаются базовые флаги компилятора (optflags).
  • Читаются файлы макросов (от системных к пользовательским). Флаги компилятора помещаются в макрос %{optflags}.
  • Пользовательский ~/.rpmmacros переопределяет пути сборки (%_topdir`) и данные мейнтейнера.
  • Только после формирования этой среды RPM приступает к чтению и парсингу самого spec-файла.
  • Понимание этой цепочки позволяет мейнтейнеру Rosa Linux гибко управлять процессом сборки, адаптируя его под любые аппаратные платформы и требования безопасности, не внося при этом изменений в исходный код самих пакетов.