目录
扫盲
rpm其实就是一个压缩包,包含了需要安装的软件目录树内的各种文件和目录,也包括了一些脚本,这些脚本是安装软件的时候执行的,所以在安装rpm包的时候,有时候会看到,不仅仅是把软件复制到了安装目录下,还会创建一些用户,注册systemctl服务等等,这些都是rpm中的脚本做的事情,而rpm中的脚本就是shell脚本。
rpmdev-setuptree 在用户的home目录生成rpm工作目录,工作目录默认名rpmbuild,里面默认生成几个子目录:BUILD、BUILDROOT、RPMS、SOURCES、SPECS、SRPMS
制作用rpm包使用二进制程序rpmbuild(不要与前面的rpmbuild目录混淆),它以SPECS目录下的xxx.spec文件为输入,这个spec文件是说明rpm构建过程及安装过程的文本文件,生成rpm包的过程中,会把中间结果放到BUILD、BUILDROOT等目录下,spec文件会自动到与SPEC平级的位置寻找这些目录。
在很久很久以前,一个很遥远的地方,rpmbuild并不仅仅是用来打个包,人们还用它解压源码的压缩包,然后编译源码,把编译的结果放到一个目录下,把这个目录下的目录结构,打包成rpm包,还会往里加一些shell脚本,这就是为什么spec文件里会有%prep,%install这些阶段。
但是我的需求并不是这样,我自己已经编译并打包好了软件的二进制版本,并不需要rpmbuild 的编译安装过程,只需要把我自己的二进制软件目录打包成rpm包,然后用rpm安装时再执行一些依赖检查和脚本,现在大多数人的需求可能也是这样。
rpm包的安装
众所周知,rpm包需要root用户来安装,这是因为rpm包的安装目标路径,在打包的时候就已经设置好了(在spec中设置),虽然也可以在安装时用--relocate修改,不过这种场景非常少,因此,安装路径通常只有root用户才有权限读写,另外执行rpm中的脚本,也需要root权限。
在构建rpm包时,可以在spec文件中设置安装前和安装后执行的shell命令(写在%pre和%post阶段下),也可以设置卸载前和卸载后的shell命令(写在%preun和%postun)。
除此以外rpm包安装时,还会查看依赖包是否存在,依赖包的名称和版本号,也是在spec里设置。rpm是一个操作系统中安装和卸载软件的管理系统,它有数据库会记录已经安装的软件的名称和版本,安装时,rpm查询这个数据库,判断所需版本的依赖包是否已经安装。
可以用 rpm -qa |grep mysql 查询某个rpm包是否已经安装了。
查看rpm包的内容(不解压也不安装):
rpm -ql abc.rpm
rpm -ql 也可以用来查看已经安装的包的安装路径,只要指定rpm包的名称部分即可,也可以用于yum安装的包,例如对于一个已安装的krb5-1.18.2-5.oe1.x86_64.rpm:
rpm -ql krb5 就可以看到这个rpm安装后的路径。
spec文件说明
下面是一个spec文件的例子:
Name: opengauss-hadb
Version: 5.0.0
Release: 1
Group: Development/Libraries
License: ASL 2.0
URL: http://www.newland.com.cn/
Source0: opengauss-hadb-5.0.0-2.tar.gz
Summary: opengauss & patroni package
#BuildRequires:
Requires: pam >= 1.4.0
Requires: libffi >= 3.3
Requires: libaio >= 0.3.112
Requires: ncurses >= 6.2
Requires: python3 >= 3.7.9
Requires: openssl >= 1.1.1t
Requires: glibc-common >= 2.28
Requires: glibc-devel >= 2.28
Requires: glibc >= 2.28
%description
opengauss & patroni package
%prep
%setup -q
tar zcf opengauss.tar.gz opengauss
tar zcf python3.tar.gz python3
tar zcf netdata.template.tar.gz netdata.template
rm -rf opengauss python3 netdata.template
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}/usr/lib/opengauss-hadb-5.0.0
cp -fr ./* %{buildroot}/usr/lib/opengauss-hadb-5.0.0
%files
/usr/lib/opengauss-hadb-5.0.0
%defattr(-,root,root,-)
%pre
useradd postgres
%post
#!/bin/sh
tar xf /usr/lib/opengauss-hadb-5.0.0/opengauss.tar.gz -C /usr/lib/opengauss-hadb-5.0.0/
tar xf /usr/lib/opengauss-hadb-5.0.0/python3.tar.gz -C /usr/lib/opengauss-hadb-5.0.0/
tar xf /usr/lib/opengauss-hadb-5.0.0/netdata.template.tar.gz -C /usr/lib/opengauss-hadb-5.0.0/
chown root:root -R /usr/lib/opengauss-hadb-5.0.0/
rm -rf /usr/lib/opengauss-hadb-5.0.0/{opengauss.tar.gz,python3.tar.gz,netdata.template.tar.gz}
%preun
touch /usr/lib/opengauss-hadb-5.0.0/{opengauss.tar.gz,python3.tar.gz,netdata.template.tar.gz}
%postun
#!/bin/sh
#userdel -r postgres
rm -rf /usr/lib/opengauss-hadb-5.0.0
echo "Uninstall opengauss-hadb-5.0.0 successfully."
Name: opengauss-hadb
Version: 5.0.0
Release: 1
Group: Development/Libraries
License: ASL 2.0
URL: http://www.newland.com.cn/
Source0: opengauss-hadb-5.0.0-2.tar.gz
Summary: opengauss & patroni package
这些变量是rpm包的信息,其中Name、Version、Release会用来构造rpm包的文件名,卸载rpm包的时候需要指定Name,如果这个包被别的rpm包依赖,版本是否符合,就根据Version判断,Version的格式可以是x.x.x或x.x,也可以有字母例如 1.1.1t,判断版本大小是根据版本号和字母的ascii码,实际上就是比较版本字符串大小。(参考 rpm: Dependencies)
Source0 是源码或者我的二进制文件目录的tar包,要事先放到SOURCE目录下,执行构建的命令 rpmbuild -bb SPEC/my.spec 时,他会吧SOURCE目录下Source0指向的包,解压到BUILD目录下。这里有个隐含的要求:这个tar包解压后,应该生成一个名称由 Name-Version 组成目录,也就是说 opengauss-hadb-5.0.0-2.tar.gz 解压后应该是名称为 opengauss-hadb-5.0.0的目录,其中压缩包的名字是啥无所谓,只要和Source0指的是一样就可以了,但是解压缩后的目录名,必须由Name-Version组成。
Release 是必要的,但是没有限制,Group、License、URL、Summary可以根据需要随便填。
其它变量可以根据需要随便写,对rpm打包和安装没有影响。
#BuildRequires:
Requires: pam >= 1.4.0
Requires: libffi >= 3.3
Requires: libaio >= 0.3.112
Requires: ncurses >= 6.2
Requires: python3 >= 3.7.9
Requires: openssl >= 1.1.1t
Requires: glibc-common >= 2.28
Requires: glibc-devel >= 2.28
Requires: glibc >= 2.28%description
opengauss & patroni package
Requires指定依赖的包名和版本,这些包在rpm安装时会检查安装机器上是否有符合条件的包。
%description 可以随便写
%prep
%setup -q
tar zcf opengauss.tar.gz opengauss
tar zcf python3.tar.gz python3
tar zcf netdata.template.tar.gz netdata.template
rm -rf opengauss python3 netdata.template
%prep #是预处理阶段,是复制 SOURCE 下的 Source0指向的压缩包,到BUILD目录下
%setup -q #是解压BUILD目录下的压缩包,并cd 到 Name-Version目录下
之后的shell脚本都是在BUILD/Name-Version目录下执行的。
这里有个细节要注意,SOURCE目录下Source0指向的压缩包,压缩包的名字没有限制,但是,解压后目录名必须是Name-Version,否则会报错找不到目录。
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}/usr/lib/opengauss-hadb-5.0.0
cp -fr ./* %{buildroot}/usr/lib/opengauss-hadb-5.0.0
%install很有迷惑性,它其实指的是源码解压到BUILD目录下后,再make & make install,编译结果复制到BUILDROOT目录下(准确的说是复制到 [name]-[version]-[release].[arch] 目录下)这个过程。BUILDROOT/[name]-[version]-[release].[arch]这个目录又称作“假根”,意思是这个目录的下的目录结构,就是rpm安装时软件相对于根目录存放的位置,理解这一点非常重要!其中%{buildroot}表示当前这个构建过程的假根,即BUILDROOT/[name]-[version]-[release].[arch]
由于我们没有make install的过程,所以我们直接在%{buildroot}下创建目录结构,并且把我们的软件复制到目录结构中,这里我们复制到了 %{buildroot}/usr/lib/opengauss-hadb-5.0.0 下,这意味着,安装这个rpm时,软件将安装到目标机器的 /usr/lib/opengauss-hadb-5.0.0 目录下。
%files
/usr/lib/opengauss-hadb-5.0.0
%defattr(-,root,root,-)
%file 是指将BUILDROOT/[name]-[version]-[release].[arch]下哪些目录及其下面的文件、子目录加入到rpm包。
%defattr(-,root,root,-) 是文件及子目录的owner和权限,使用这种方法,虽然不是以root用户执行rpmbuild的,打包时也可以指定为root所有者和权限,否则rpm安装后,这些目录和文件的owner和权限是执行rpmbuild用户。
%pre
useradd postgres
%post
#!/bin/sh
tar xf /usr/lib/opengauss-hadb-5.0.0/opengauss.tar.gz -C /usr/lib/opengauss-hadb-5.0.0/
tar xf /usr/lib/opengauss-hadb-5.0.0/python3.tar.gz -C /usr/lib/opengauss-hadb-5.0.0/
tar xf /usr/lib/opengauss-hadb-5.0.0/netdata.template.tar.gz -C /usr/lib/opengauss-hadb-5.0.0/
chown root:root -R /usr/lib/opengauss-hadb-5.0.0/
rm -rf /usr/lib/opengauss-hadb-5.0.0/{opengauss.tar.gz,python3.tar.gz,netdata.template.tar.gz}
%pre 是指rpm安装前执行的shell 脚本
%post 是指rpm安装后执行的shell 脚本
注意:以root用户安装rpm,因此执行这些命令的用户都是root
%preun
touch /usr/lib/opengauss-hadb-5.0.0/{opengauss.tar.gz,python3.tar.gz,netdata.template.tar.gz}%postun
#!/bin/sh
#userdel -r postgres
rm -rf /usr/lib/opengauss-hadb-5.0.0
echo "Uninstall opengauss-hadb-5.0.0 successfully."
%preun 卸载前执行的命令
%postun 卸载后执行的命令
注意,userdel -r postgres 删除用户时会删除它的home目录。
rpm包构建过程
可能前面已经说过了,构建的命令是:
rpmbuild -bb SPEC/abc.spec
当执行这个命令时,它会自动寻找与SPEC同一层目录下的,BUILD,BUILDROOT,RPMS,SOURCES,SPECS,SRPMS这些目录,把中间结果或最终结果放在里面。在%prep阶段(使用%setup -q)它会首先解压SOURCES目录下Source0指向的tar包到BUILD目录下,在%install阶段,在BUILRROOT目录下构建与安装时相同的目录结构,只不过根目录是“假根”,然后将BUILD下的文件目录等,复制到假根下面的目录结构,然后通过%file指定BUILDROOT/[name]-[version]-[release].[arch]下哪些目录的内容包含到rpm包中,最后这些目录打包,生成的rpm包放在RPMS目录下,rpm安装前后执行的脚本,和卸载前后执行的脚本,如果有的话,也会集成到rpm包里。
tips
构建rpm包时,也可以不用rpmdev-setuptree在用户家目录下创建rpmbuild,而是在任意目录下创建工作目录结构。这需要修改宏变量_topdir,这个_topdir的路径就是rpmbuild的路径,例如:
rpmbuild -bb --define "_topdir /opt/my-rpmbuild" /opt/my-rpmbuild/SPECS/opengauss.spec
其中 "_topdir /opt/my-rpmbuild"表示,以/opt/my-rpmbuild为_topdir。
rpm安装的时候也可以指定非默认目录,但是这样做的前提是,制作rpm时要能relocate,但是我发现,这也有它的局限性,例如rpm安装后的脚本不能获得改变后的安装目录,无法做相应设置,因此可以先不考虑这个特性。
Prefix : /usr/lib
%install
rm -rf %{buildroot}
mkdir -p %{buildroot}/usr/lib/opengauss-client-5.0.0
cp -fr ./* %{buildroot}/usr/lib/opengauss-client-5.0.0
%files
%defattr(-,root,root,-)
%attr(0755,root,root) /usr/lib/opengauss-client-5.0.0
rpm -ivh abc.rpm --prefix=/opt # 就会把spec里的/usr/lib,在安装时替换成/opt,但是有个问题是在%post的安装后脚本里不知如何获取新的prefix。
还有一种安装的配置,不需要为安装的软件创建用户,可以给所有用户使用。方法就是以root用户安装,可执行文件权限设置为 755,PATH和LD_LIBRARY_PATH环境变量设置到 /etc/profile,这样对所有用户就可用了。
设置和删除环境变量的命令可以用这个:
%post
echo "export LD_LIBRARY_PATH=/usr/lib/opengauss-client-5.0.0/lib64:\$LD_LIBRARY_PATH" >> /etc/profile
echo "export PATH=/usr/lib/opengauss-client-5.0.0/bin:\$PATH" >> /etc/profile
%postun
# delete the line with pattern
sed '/^export .*opengauss-client-5.0.0.*/d' -i /etc/profile
rpmbuild过程中有个步骤是CHECK_RPATHS,就是检查可执行文件或者so文件中的rpath,rpath是寻找依赖的so文件的路径,一般是编译时确定的,在编译时可以通过-rpath选项控制,一般设置为空。rpmbuild发现找不到可执行文件或者so内嵌的rpath就会报错,解决办法就是将~/.rpmmacros中的下面注释掉:
关于rpath,参考 RPATH是什么 - 邱明成 - 博客园
参考:
这是一个关于RPM的很好的教程:Maximum RPM
rpmbuild打包遇到问题汇总_rpmbuild contains an invalid rpath-优快云博客
linux下如何完全删除用户_linux删除用户-优快云博客
RPM打包原理、示例、详解及备查 - yipianchuyun - 博客园