自从 20 多年前开始使用 Linux 以来,我一直使用基于 rpm 的软件包管理器在 Red Hat 和 Fedora Linux 上安装软件。我使用过 rpm 程序本身、yum 和 DNF(yum 的近亲)来安装和更新我的 Linux 主机上的软件包。yum 和 DNF 工具是 rpm 实用程序的封装,它们提供额外的功能,例如查找和安装软件包依赖项的能力。
多年来,我创建了许多 Bash 脚本,其中一些脚本有单独的配置文件,我想将它们安装到我的大多数新计算机和虚拟机上。安装所有这些软件包非常耗时,所以我决定通过创建一个 rpm 包来自动化这个过程,我可以将该 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 条评论