自从 20 多年前我开始使用 Linux 以来,我就一直使用基于 rpm 的软件包管理器在 Red Hat 和 Fedora Linux 上安装软件。 我使用 rpm 程序本身、yum 和 DNF(yum 的近亲)在我的 Linux 主机上安装和更新软件包。 yum 和 DNF 工具是 rpm 实用程序的包装器,提供了额外的功能,例如查找和安装软件包依赖项的能力。
多年来,我创建了许多 Bash 脚本,其中一些脚本有单独的配置文件,我喜欢将它们安装在我的大多数新计算机和虚拟机上。 安装所有这些软件包花费了大量时间,所以我决定通过创建一个 rpm 软件包来自动化该过程,我可以将该软件包复制到目标主机并在其正确的位置安装所有这些文件。 虽然 rpm 工具以前用于构建 rpm 软件包,但该功能已被删除,并创建了一个新工具 rpmbuild 来构建新的 rpm。
当我开始这个项目时,我发现关于创建 rpm 软件包的信息很少,但我设法找到了一本书 Maximum RPM,它帮助我解决了这个问题。 这本书现在有些过时了,就像我发现的大部分信息一样。 它也已绝版,二手拷贝售价数百美元。 Maximum RPM 的在线版本免费提供,并且会保持更新。 RPM 网站 还链接到其他包含大量关于 rpm 文档的网站。 其他信息往往很简短,并且显然假设你已经对该过程有很好的了解。
此外,我找到的每一份文档都假设代码需要从源代码编译,就像在开发环境中一样。 我不是开发人员。 我是一名系统管理员,我们系统管理员有不同的需求,因为我们不会(或者我们不应该)编译代码来用于管理任务;我们应该使用 shell 脚本。 所以我们没有源代码,因为它是需要编译成二进制可执行文件的东西。 我们拥有的是一个也是可执行文件的源。
在大多数情况下,这个项目应该以非 root 用户 student 的身份执行。 Rpms 永远不应该由 root 构建,而只能由非特权用户构建。 我将指示哪些部分应作为 root 执行,哪些部分应由非 root、非特权用户执行。
准备
首先,打开一个终端会话并 su
到 root。 务必使用 -
选项以确保启用完整的 root 环境。 我不相信系统管理员应该使用 sudo
来执行任何管理任务。 在我的个人博客文章中了解原因:真正的系统管理员不用 sudo。
[student@testvm1 ~]$ su -
Password:
[root@testvm1 ~]#
创建一个可以用于此项目的 student 用户,并为该用户设置密码。
[root@testvm1 ~]# useradd -c "Student User" student
[root@testvm1 ~]# passwd student
Changing password for user student.
New password: <Enter the password>
Retype new password: <Enter the password>
passwd: all authentication tokens updated successfully.
[root@testvm1 ~]#
构建 rpm 软件包需要 rpm-build
软件包,该软件包可能尚未安装。 现在以 root 身份安装它。 请注意,此命令还会安装几个依赖项。 数量可能会有所不同,具体取决于主机上已安装的软件包;它在我的测试 VM 上安装了总共 17 个软件包,这非常少。
dnf install -y rpm-build
除非另有明确指示,否则此项目的其余部分应以用户 student 的身份执行。 打开另一个终端会话并使用 su
切换到该用户以执行其余步骤。 使用以下命令从 GitHub 下载我准备的开发目录结构的 tarball,utils.tar
wget https://github.com/opensourceway/how-to-rpm/raw/master/utils.tar
此 tarball 包含最终 rpm 将安装的所有文件和 Bash 脚本。 还有一个完整的 spec 文件,你可以使用它来构建 rpm。 我们将详细介绍 spec 文件的每个部分。
作为 student 用户,以你的主目录作为你的当前工作目录 (pwd),解压 tarball。
[student@testvm1 ~]$ cd ; tar -xvf utils.tar
使用 tree
命令验证 ~/development 的目录结构和包含的文件是否如下图所示
[student@testvm1 ~]$ tree development/
development/
├── license
│ ├── Copyright.and.GPL.Notice.txt
│ └── GPL_LICENSE.txt
├── scripts
│ ├── create_motd
│ ├── die
│ ├── mymotd
│ └── sysdata
└── spec
└── utils.spec
3 directories, 7 files
[student@testvm1 ~]$
mymotd
脚本创建一个“每日消息”数据流,该数据流发送到 stdout。 create_motd
脚本运行 mymotd
脚本并将输出重定向到 /etc/motd 文件。 此文件用于向使用 SSH 远程登录的用户显示每日消息。
die
脚本是我自己的脚本,它用一段代码包装了 kill
命令,该代码可以查找与指定字符串匹配的正在运行的程序并终止它们。 它使用 kill -9
以确保它们不能忽略 kill 消息。
sysdata
脚本可以输出数万行关于你的计算机硬件、已安装的 Linux 版本、所有已安装的软件包以及硬盘驱动器的元数据的数据。 我使用它来记录主机在某个时间点的状态。 我以后可以使用它作为参考。 我过去常常这样做来维护我为客户安装的主机的记录。
你可能需要将这些文件和目录的所有权更改为 student.student。 如果需要,可以使用以下命令执行此操作
chown -R student.student development
此树中的大多数文件和目录将由你在此项目中创建的 rpm 安装在 Fedora 系统上。
创建构建目录结构
rpmbuild
命令需要非常特定的目录结构。 你必须自己创建此目录结构,因为没有提供自动方式。 在你的主目录中创建以下目录结构
~ ─ rpmbuild
├── RPMS
│ └── noarch
├── SOURCES
├── SPECS
└── SRPMS
我们将不创建 rpmbuild/RPMS/X86_64 目录,因为这将是 64 位编译二进制文件的特定于架构的目录。 我们有不特定于架构的 shell 脚本。 实际上,我们也不会使用 SRPMS 目录,该目录将包含编译器的源文件。
检查 spec 文件
每个 spec 文件都有许多部分,其中一些部分可能会被忽略或省略,具体取决于 rpm 构建的特定情况。 这个特定的 spec 文件不是一个最小的工作所需文件的示例,但它是一个中等复杂 spec 文件的很好的示例,该文件打包了不需要编译的文件。 如果需要编译,它将在 %build
部分中执行,该部分从此 spec 文件中省略,因为它不是必需的。
前言
这是 spec 文件中唯一没有标签的部分。 它由你运行命令 rpm -qi [软件包名称]
时看到的大部分信息组成。 每个数据都是单行,它由一个标记(用于标识它)和文本数据(用于标记的值)组成。
###############################################################################
# Spec file for utils
################################################################################
# Configured to be built by user student or other non-root user
################################################################################
#
Summary: Utility scripts for testing RPM creation
Name: utils
Version: 1.0.0
Release: 1
License: GPL
URL: http://www.both.org
Group: System
Packager: David Both
Requires: bash
Requires: screen
Requires: mc
Requires: dmidecode
BuildRoot: ~/rpmbuild/
# Build with the following syntax:
# rpmbuild --target noarch -bb utils.spec
注释行会被 rpmbuild
程序忽略。 我总是喜欢在此部分中添加一条注释,其中包含创建软件包所需的 rpmbuild
命令的确切语法。 Summary 标签是软件包的简短描述。 Name、Version 和 Release 标签用于创建 rpm 文件的名称,例如 utils-1.00-1.rpm。 递增发布和版本号可以让你创建可用于更新旧版本的 rpm。
License 标签定义了软件包发布的许可。 我总是使用 GPL 的变体。 指定许可对于明确软件包中包含的软件是开源的这一事实非常重要。 这也是我将许可和 GPL 声明包含在将要安装的文件中的原因。
URL 通常是项目或项目所有者的网页。 在这种情况下,这是我的个人网页。
Group 标签很有趣,通常用于 GUI 应用程序。 Group 标签的值决定了应用程序菜单中的哪个图标组将包含此软件包中的可执行文件的图标。 与 Icon 标签(我们在这里不使用)结合使用,Group 标签允许将图标和启动程序所需的信息添加到应用程序菜单结构中。
Packager 标签用于指定负责维护和创建软件包的个人或组织。
Requires 语句定义了此 rpm 的依赖项。 每一个都是一个软件包名称。 如果缺少指定的软件包之一,则 DNF 安装实用程序将尝试在 /etc/yum.repos.d 中定义的存储库中找到它,并在它存在的情况下安装它。 如果 DNF 找不到一个或多个必需的软件包,它将抛出一个错误,指示缺少哪些软件包并终止。
BuildRoot 行指定 rpmbuild
工具将在其中找到 spec 文件以及在构建软件包时在其中创建临时目录的顶级目录。 完成的软件包将存储在我们之前指定的 noarch 子目录中。 显示用于构建此软件包的命令语法的注释包括选项 –target noarch
,它定义了目标架构。 因为这些是 Bash 脚本,所以它们不与特定的 CPU 架构相关联。 如果省略此选项,则构建将面向执行构建的 CPU 的架构。
rpmbuild
程序可以面向许多不同的架构,并且使用 --target
选项允许我们在具有与执行构建的架构不同的架构的主机上构建特定于架构的软件包。 因此,我可以在 x86_64 主机上构建一个用于 i686 架构的软件包,反之亦然。
将打包者名称更改为你自己的名称,如果有自己的网站,请将 URL 更改为你自己的网站。
%description
spec 文件的 %description
部分包含 rpm 软件包的描述。 它可以很短,也可以包含很多行信息。 我们的 %description
部分相当简洁。
%description
A collection of utility scripts for testing RPM creation.
%prep
%prep
部分是构建过程中执行的第一个脚本。 此脚本在软件包安装期间不会执行。
此脚本只是一个 Bash shell 脚本。 它准备构建目录,根据需要创建用于构建的目录,并将相应的文件复制到各自的目录中。 这将包括作为构建的一部分的完整编译所需的源文件。
$RPM_BUILD_ROOT
目录代表已安装系统的根目录。在 $RPM_BUILD_ROOT
目录中创建的目录是完全限定路径,例如在实际文件系统中的 /user/local/share/utils、/usr/local/bin 等。
在我们的软件包中,由于我们所有的程序都是 Bash 脚本,因此我们没有预编译的源文件。 所以我们只需将这些脚本和其他文件复制到已安装系统中它们所属的目录即可。
%prep
################################################################################
# Create the build tree and copy the files from the development directories #
# into the build tree. #
################################################################################
echo "BUILDROOT = $RPM_BUILD_ROOT"
mkdir -p $RPM_BUILD_ROOT/usr/local/bin/
mkdir -p $RPM_BUILD_ROOT/usr/local/share/utils
cp /home/student/development/utils/scripts/* $RPM_BUILD_ROOT/usr/local/bin
cp /home/student/development/utils/license/* $RPM_BUILD_ROOT/usr/local/share/utils
cp /home/student/development/utils/spec/* $RPM_BUILD_ROOT/usr/local/share/utils
exit
请注意,此部分末尾的 exit 语句是必需的。
%files
spec 文件的这个部分定义了要安装的文件及其在目录树中的位置。它还指定了文件属性以及要安装的每个文件的所有者和组所有者。文件权限和所有权是可选的,但我建议显式设置它们,以消除安装时这些属性不正确或不明确的任何可能性。如果在安装过程中目录尚不存在,则会根据需要创建目录。
%files
%attr(0744, root, root) /usr/local/bin/*
%attr(0644, root, root) /usr/local/share/utils/*
%pre
在我们的实验项目的 spec 文件中,这部分是空的。这里可以放置需要在 rpm 安装期间但在安装文件之前运行的任何脚本。
%post
spec 文件的这部分是另一个 Bash 脚本。此脚本在文件安装后运行。这部分几乎可以是您需要的或想要的任何内容,包括创建文件、运行系统命令和重新启动服务以在进行配置更改后重新初始化它们。我们的 rpm 包的 %post
脚本执行其中一些任务。
%post
################################################################################
# Set up MOTD scripts #
################################################################################
cd /etc
# Save the old MOTD if it exists
if [ -e motd ]
then
cp motd motd.orig
fi
# If not there already, Add link to create_motd to cron.daily
cd /etc/cron.daily
if [ ! -e create_motd ]
then
ln -s /usr/local/bin/create_motd
fi
# create the MOTD for the first time
/usr/local/bin/mymotd > /etc/motd
此脚本中包含的注释应使其用途清晰明了。
%postun
这部分包含一个在卸载 rpm 包后运行的脚本。使用 rpm 或 DNF 删除包会删除 %files
部分中列出的所有文件,但它不会删除由 %post
部分创建的文件或链接,因此我们需要在这部分中处理该问题。
此脚本通常包含清理任务,而仅仅删除先前由 rpm 安装的文件无法完成这些任务。在我们的软件包中,它包括删除由 %post
脚本创建的链接,以及恢复已保存的原始 motd 文件。
%postun
# remove installed files and links
rm /etc/cron.daily/create_motd
# Restore the original MOTD if it was backed up
if [ -e /etc/motd.orig ]
then
mv -f /etc/motd.orig /etc/motd
fi
%clean
此 Bash 脚本在 rpm 构建过程后执行清理。下面的 %clean
部分中的两行代码删除了由 rpm-build
命令创建的构建目录。在许多情况下,可能还需要额外的清理。
%clean
rm -rf $RPM_BUILD_ROOT/usr/local/bin
rm -rf $RPM_BUILD_ROOT/usr/local/share/utils
%changelog
此可选文本部分包含 rpm 及其包含的文件更改的列表。最新的更改记录在此部分的顶部。
%changelog
* Wed Aug 29 2018 Your Name <Youremail@yourdomain.com>
- The original package includes several useful scripts. it is
primarily intended to be used to illustrate the process of
building an RPM.
将标题行中的数据替换为您自己的姓名和电子邮件地址。
构建 rpm
spec 文件必须位于 rpmbuild 树的 SPECS 目录中。我发现最简单的方法是在该目录中创建指向实际 spec 文件的链接,以便可以在开发目录中对其进行编辑,而无需将其复制到 SPECS 目录。将 SPECS 目录设置为您的 pwd,然后创建链接。
cd ~/rpmbuild/SPECS/
ln -s ~/development/spec/utils.spec
运行以下命令以构建 rpm。 如果没有发生错误,则只需片刻即可创建 rpm。
rpmbuild --target noarch -bb utils.spec
检查 ~/rpmbuild/RPMS/noarch 目录以验证新的 rpm 是否存在于该目录中。
[student@testvm1 ~]$ cd rpmbuild/RPMS/noarch/
[student@testvm1 noarch]$ ll
total 24
-rw-rw-r--. 1 student student 24364 Aug 30 10:00 utils-1.0.0-1.noarch.rpm
[student@testvm1 noarch]$
测试 rpm
以 root 用户身份安装 rpm 以验证它是否正确安装,以及文件是否安装在正确的目录中。rpm 的确切名称将取决于您在 Preamble 部分中使用的标签值,但是如果您使用了示例中的标签值,则 rpm 名称将如以下示例命令中所示
[root@testvm1 ~]# cd /home/student/rpmbuild/RPMS/noarch/
[root@testvm1 noarch]# ll
total 24
-rw-rw-r--. 1 student student 24364 Aug 30 10:00 utils-1.0.0-1.noarch.rpm
[root@testvm1 noarch]# rpm -ivh utils-1.0.0-1.noarch.rpm
Preparing... ################################# [100%]
Updating / installing...
1:utils-1.0.0-1 ################################# [100%]
检查 /usr/local/bin 以确保新文件在那里。您还应该验证是否已创建 /etc/cron.daily 中的 create_motd 链接。
使用 rpm -q --changelog utils
命令查看变更日志。使用 rpm -ql utils
命令查看软件包安装的文件(ql
中的 l 为小写)。
[root@testvm1 noarch]# rpm -q --changelog utils
* Wed Aug 29 2018 Your Name <Youremail@yourdomain.com>
- The original package includes several useful scripts. it is
primarily intended to be used to illustrate the process of
building an RPM.
[root@testvm1 noarch]# rpm -ql utils
/usr/local/bin/create_motd
/usr/local/bin/die
/usr/local/bin/mymotd
/usr/local/bin/sysdata
/usr/local/share/utils/Copyright.and.GPL.Notice.txt
/usr/local/share/utils/GPL_LICENSE.txt
/usr/local/share/utils/utils.spec
[root@testvm1 noarch]#
删除软件包。
rpm -e utils
实验
现在,您将更改 spec 文件以要求一个不存在的软件包。这将模拟一个无法满足的依赖项。在现有的 Requires 行下立即添加以下行
Requires: badrequire
构建软件包并尝试安装它。显示什么消息?
我们使用 rpm
命令来安装和删除 utils
软件包。尝试使用 yum 或 DNF 安装软件包。您必须与软件包位于同一目录中,或者指定软件包的完整路径才能使其工作。
结论
在本次 rpm 包创建基础知识的介绍中,我们没有涵盖许多标签和几个部分。下面列出的资源可以提供更多信息。构建 rpm 包并不困难;您只需要正确的信息。我希望这能对您有所帮助——我花了好几个月才自己弄清楚。
我们没有介绍从源代码构建,但如果您是开发人员,那么从这一点来看,这应该只是一个简单的步骤。
创建 rpm 包是成为一名“懒惰的”系统管理员并节省时间和精力的另一种好方法。它为分发和安装脚本以及其他我们需要在许多主机上安装的文件提供了一种简单的方法。
资源
-
Edward C. Baily, Maximum RPM, Sams Publishing, 2000, ISBN 0-672-31105-4
-
Edward C. Baily, Maximum RPM, 更新的在线版本
-
RPM 文档:此网页列出了 rpm 的大多数可用在线文档。它包括许多指向其他网站和有关 rpm 的信息的链接。
11 条评论