RPM spec编写心得

最近,研究用nginx取代Apache作为Git服务器。该服务器要提供gitolite, cgit的web访问。nginx蛮怪的,大概出于性能考虑,不能原生支持CGI,只支持FastCGI。结果gitolite, cgit这些纯CGI应用都歇菜了,没法像在Apache上那样用ScriptAlias即可直接跑了。幸好有一个叫fcgiwrap的工具,它把传统的CGI程序包装成FastCGI,这样就可以和nginx集成了。

但这个工具没有现成的RPM包,在Debian上倒是有deb包。从源码安装先要用autoconf生成configure文件,然后是大家所熟知的./configure, make, make install。另外,这个工具已经支持systemd的socket activation。使用功能的最大好处是它不再需要用额外的程序(比如spawn-fcgi)来启动fcgiwrap。fcgiwrap不用预先启动,只有当真正的请求由前端的web服务器通过unix domain socket传递过来时才会由systemd启动fcgwrap进程。不幸的是fcgiwrap的源码包里虽然提供了systemd unit文件,但没有提到如何安装并启用systemd socket activation功能。如果把这些功能做到RPM包里,那对于系统管理员而言会省很多事情。甚至可以降低系统管理员的门槛,一般的屌丝管理员也能胜任。

另外一个问题是,虽然systemd是很好的技术,但还离主流技术尚需时日。就像selinux出来这么久了,很多管理员根本用不起来,直接disable掉算数。在可预见的相当长一段时间里,SysV init还会是主流的技术。那么怎么让我只写一个版本的RPM spec文件但却能在systemd系统上生成支持socket activation的RPM,而在SysV init系统上生成传统的RPM。解决这个问题需要在spec文件中侦测当前系统是否使用systemd。这个侦测用pkg-config命令测试libsystemd-daemon这个库文件是否安装。根据侦测的结果定义相应的RPM宏以表示systemd的存在。

归纳起来做fcgiwrap的RPM包有这么几个重要的需求:

  1. 正确声明RPM构建阶段和安装阶段的软件包依赖
  2. 确保执行用autoconf生成configure文件的编译步骤
  3. 支持用同一个spec文件产生支持systemd和SysV init的RPM包
  4. 在systemd系统上安装时要自动注册fcgiwrap的socket和service的单元文件,启用并使fcgiwrap服务处于监听状态。

需求3没有现成的功能可用,但可以用调用pkg-config命名然后把结果加以转化最后保存到RPM宏里面去。具体代码如下:

%define systemd_test_str %(
if [[ -z $(pkg-config --print-errors libsystemd-daemon 2>&1) ]]; then
  echo "yes"
else
  echo "no"
fi
)
%if "%{systemd_test_str}" == "yes"
    %define with_systemd 1
%endif

 

需求1用标准的spec功能基本能实现,但是一些systemd系统上才用到的软件包如systemd-devel需要动态声明。着可用利用%{?macro: xxx}表达式来实现,具体写法:

%{?with_systemd:BuildRequires:  systemd-devel systemd}

 需求4则要在%post和%preun中使用shell脚本判断目标系统是否启用了systemd然后做相应的服务注册,启动等操作。具体例子如下:

%post
# enable socket activation for fcgiwrap
if [[ -z $(pkg-config --print-errors libsystemd-daemon 2>&1) ]]; then
    /usr/bin/systemctl enable fcgiwrap.socket
    /usr/bin/systemctl start fcgiwrap.socket
    
    cat <<BANNER
==================================================
FCGI service fcgiwrap is ready!!!
==================================================
BANNER
fi

%preun
# stop and disable socket activation for fcgiwrap
if [[ -z $(pkg-config --print-errors libsystemd-daemon 2>&1) ]]; then
    /usr/bin/systemctl stop fcgiwrap.socket
    /usr/bin/systemctl disable fcgiwrap.socket
fi  

 这些shell脚本会封装到RPM包中去,对于现成的RPM可用rpm -qp --scripts <pkg.rpm>命令列出。

 

初次使用RPM宏,对于如何根据外部命令的结果来定义相应的宏并有点陌生,走了不少弯路。主要的教训是:

  • spec文件中调用多行shell脚本,%()并不限定括号内的表达式必须在一行上
  • 条件表达式%if %macro %endif和%{?macro:xxx}在macro未定义时完全不等价,前者会报错,后者则相对于没有指定过该表达式,如果要是两者等同,应该把%if中的宏之前加上0。
  • %post %preun等处不能用RPM的条件语句来控制脚本的执行分支。因为,这些脚本在安装时才运行,而那些RPM宏只在RPM包创建时才本展开。

这个spec文件完整代码在我的github上能找到。为了把它集成到上游,相应的pull request已经发给fcgiwrap的原作者。

你可能感兴趣的:(rpm)