简介RPM与DEB软件包打包基础教程文档面向Linux系统管理员、运维工程师与软件发布人员系统梳理基于RHEL/Fedora/CentOS和Debian/Ubuntu两大主流包管理体系的打包流程帮助读者快速掌握从目录规划、元数据编写到打包安装的完整方法。文档逐一讲解DEB包中DEBIAN目录、control文件及preinst/postinst/prerm/postrm四个脚本的作用并以dpkg -b示例演示生成deb包同时针对RPM包说明BUILD、SPECS、RPMS等目录结构、spec文件配置项和%pre、%post等各阶段脚本配合rpmbuild命令完成构建。此外还给出安装、升级、卸载常用命令对照便于直接套用。资源为1个doc文档约267KB内容以文字配命令示例为主结构清晰适合需要快速上手打包的读者查阅。目前已有416人学习下载可作为Linux软件打包入门的实操参考。1. 软件包不只是文件集合RPM 与 DEB 的元数据与脚本契约把编译好的二进制丢进/usr/lib、写个start.sh看起来程序就算“装”上了。可真到了交付环节问题全冒出来老版本升级时旧文件没人清理用户卸载后/usr/share/applications里还躺着快捷方式换一台基于 Debian 衍生发行版的机器又得重新适配。RPM 与 DEB 这两种包格式本质上不是把文件压成一个归档而是约定了一份“软件包契约”——元数据描述名称、版本、架构与依赖关系preinst、postinst、%post、%postun这些维护脚本定义安装前后的动作文件清单决定哪些路径归属于这个包。对需要交付 Linux 安装包、做内网离线部署、给不同发行版适配的工程师来说弄清这套契约比单纯会敲rpm -ivh要重要得多。本文沿着 DEB 与 RPM 两条线把目录结构、元数据字段、维护脚本到打包验证的完整流程拆开讲。2. DEB 打包从 dpkg -b 到 control 元数据与维护脚本2.1 DEBIAN 控制段与真实目录的边界先看一个完整的 DEB 打包目录长什么样。假设客户端程序叫aktd程序文件最终要装到/usr/lib/aktd-client下打包目录结构如下aktd/ ├── DEBIAN/ │ ├── control │ ├── preinst │ ├── postinst │ ├── prerm │ └── postrm └── usr/ └── lib/ └── aktd-client/DEBIAN目录下面只有五样东西control元数据文件和四个维护脚本。除此之外打包目录里其余所有路径都是“真实目录”dpkg -b打包时会把usr/lib/aktd-client原样映射到安装后的系统根路径下。换句话说aktd/usr/lib/aktd-client在安装后就是/usr/lib/aktd-client目录层级不能多也不能少。常见错误是有人把usr写成USR或者多包一层嵌套目录结果装完发现程序被放到了奇怪的位置。DEBIAN目录名是固定保留字大小写不能改。维护脚本不是必须全部存在但内网交付时强烈建议四个都写上哪怕某个阶段什么都不做也要让脚本存在并返回 0否则后续排查问题时很难分清是脚本缺失还是脚本执行报错。2.2 control 字段包名、架构和依赖是三个最容易错的地方control是纯文本文件每行一个字段最终会被 dpkg 解析成包的元数据。字段含义如下表字段含义示例Package软件包名称卸载时用这个名字aktd-clientVersion软件版本号1.0.0Architecture平台架构必须与dpkg --print-architecture输出一致amd64Installed-Size安装后占用空间单位 KB32768Maintainer维护者和联系方式ops opsexample.comDescription程序功能说明首行短描述后续行是长描述aktd client serviceSection软件类别utilsPriority优先级optionalDepends依赖的软件包多个用逗号分隔libc6 ( 2.14), openjdk-8-jre注意Package字段才是包的真实身份dpkg -r卸载时用的是它而不是文件名。文件名里的版本号如果和control里的Version不一致虽然 dpkg 不会直接拒绝但dpkg -I查包信息时会出现版本对不上的情况内网管理系统做版本比对时容易出问题。架构字段写错是另一个高频事故。dpkg --print-architecture返回什么就写什么在 x86 机器上硬写arm64安装时直接报“软件包架构不匹配”。如果是给麒麟这类基于 Debian 衍生发行的系统适配更要先确认目标机器架构再决定下载amd64、arm64还是loongarch64的包不能想当然。2.3 安装与卸载脚本dpkg 会在什么时机执行什么四个维护脚本都是 Shell 脚本dpkg 安装和卸载过程中会按生命周期调用。preinst在包文件解包之前执行常用于停止待升级版本的服务postinst在安装完成后执行负责解压 JDK、创建日志目录、调整权限prerm在删除关联文件之前执行用于停止服务postrm在文件删除之后执行用来清理残余配置。这里最容易忽略的是 deb 维护脚本其实会收到参数install、upgrade、remove、purge脚本内部用$1判断当前处于哪个阶段。一个常见的postinst写法#!/bin/bash set -e clientPath/usr/lib/aktd-client if [ ! -d $clientPath/logs ]; then mkdir -p $clientPath/logs fi chmod 775 $clientPath chmod 755 $clientPath/start.sh tar -xzvf $clientPath/jdk.tar.gz -C $clientPathset -e在这里很关键。dpkg 执行维护脚本时会捕获退出码任何一条命令失败都会让整个安装流程回滚避免留下半安装状态。脚本里对start.sh、jdk.tar.gz这类文件逐个赋权限而不是对整个目录一把chmod -R 777是为了防止把可执行权限错误扩散到配置文件上。postrm的典型写法#!/bin/bash if [ $1 purge ]; then rm -rf /var/lib/aktd-client fi rm -f /usr/share/applications/aktd-client.desktop rm -rf /usr/lib/aktd-client测试时最常见的坑是postrm里无条件删整个安装目录导致升级时旧版本文件先被删掉新版本还没解包中间出现短暂的空窗。稳妥的做法是在postrm里用$1区分remove和purge升级场景下有些用户数据目录要保留。2.4 dpkg -b 打包与本地安装验证目录结构和文件准备好之后执行打包命令chmod x aktd/DEBIAN/* dpkg -b aktd aktd_1.0.0_amd64.debdpkg -b第一个参数是待打包的目录名第二个参数是输出文件名。打包前给DEBIAN下的脚本赋可执行权限否则安装时 dpkg 会报脚本无法执行。验证安装用dpkg -i卸载用dpkg -rsudo dpkg -i aktd_1.0.0_amd64.deb sudo dpkg -r aktd-clientdpkg -i是本地安装不依赖 apt 软件源所以不会出现“无法定位软件包”的报错——那个报错是apt install在软件源里找不到包名时出现的和本地 deb 安装是两条路。如果dpkg -i报“软件包似乎无效”优先检查文件是不是完整下载、后缀是不是被改过名很多第三方下载站会把 tar.gz 直接改名成 deb。安装输出的每一行都要看特别是每行末尾的退出状态码日常测试里前 99 次安装失败的原因都藏在最后几行输出里。3. RPM 打包rpmbuild 目录骨架与 spec 文件脚本段拆解3.1 固定目录骨架BUILDROOT 是理解 RPM 的关键RPM 的打包目录结构是固定的rpmbuild会按约定从根目录查找各个子目录rpm/ ├── BUILD ├── BUILDROOT ├── RPMS ├── SOURCES/ │ └── aktd-client/ ├── SPECS/ │ └── demo-1.0.0.spec └── SRPMSSOURCES放程序源文件SPECS放 spec 文件RPMS存放构建产物SRPMS存放源码包BUILD是编译暂存目录BUILDROOT是虚拟安装根目录。理解BUILDROOT就能理解整个 RPM 构建流程%install阶段把文件从SOURCES拷贝到BUILDROOT下的对应目录rpmbuild再以BUILDROOT为根目录制作 RPM 包。也就是说BUILDROOT里出现的内容才是最终进入 RPM 包的内容写%install脚本时任何路径都要以这个虚拟根为基准。SPECS目录下只有一个 spec 文件这与 DEB 的控制信息分散在DEBIAN目录多个文件中不同。RPM 把元数据、文件清单、生命周期脚本全部塞进一个 spec 文件里所以 spec 文件同时承担了control、preinst、postinst、prerm、postrm的职能。3.2 spec 文件配置区元数据字段逐个说spec 文件先是配置区从Name到URL是 RPM 包的元数据。Name: aktd-client Version: 1.0.0 Release: beta Summary: swing demo client Group: Applications/System License: GPL Vendor: HAB URL: hab.com基本字段对应关系如下表字段含义需要留意的地方Name包名卸载时用rpm -e加这个名字Version版本号不能带横线Release发布序列号建议用数字beta这类字符串部分仓库工具不识别Summary一句话说明不要写太长Group软件分组旧式字段部分发行版已弱化License授权方式必填项缺失会导致构建告警URL项目主页可选Version里不能出现-RPM 解析器会把横线当成版本和 Release 的分隔符。之前遇到过有人把Version: 1.0.0-beta写进字段结果rpm -q查询时包名解析异常最后改成Version: 1.0.0、Release: 0.1.beta才正常。3.3 脚本段%install、%files、%post、%postun 的职责划分配置区下面是%开头的脚本段rpmbuild 会按固定顺序执行%description aktd client for internal deployment %pre # 安装前执行常用于停止旧服务 %install rm -rf %{buildroot} mkdir -p %{buildroot}/usr/lib/demo cp -rp %{_sourcedir}/aktd-client/ %{buildroot}/usr/lib %files %defattr(-,root,root,0755) /usr/lib %post sourcePath/usr/lib/aktd-client if [ ! -d $sourcePath ]; then mkdir -p $sourcePath fi chmod 775 $sourcePath chmod 755 $sourcePath/start.sh tar -xzvf $sourcePath/jdk.tar.gz -C $sourcePath %postun clientPath/usr/lib/aktd-client rm -f $clientPath/aktd-client.jar rm -rf $clientPath/logs rm -f /usr/share/applications/aktd-client.desktop %clean rm -rf %{buildroot}各段执行语义如下脚本段执行时机与 DEB 对应关系%pre安装前preinst%install构建期把文件放入 BUILDROOT无对应%files声明哪些文件属于本包并设权限无对应%post安装到真实系统后postinst%preun卸载前prerm%postun卸载后postrm%clean构建完成后清理 BUILDROOT无对应%install里的%{buildroot}和%{_sourcedir}是 rpmbuild 预置宏前者指向当前构建的 BUILDROOT 路径后者指向 SOURCES 目录。%files段%defattr(-,root,root,0755)表示默认属主是 root:root目录权限 0755文件权限保持源文件属性。常见错误是%install里cp -rp复制了整个目录但%files里只写了/usr/lib没有写具体子路径导致包内容不完整安装后运行找不到 jar 包。%post里需要留意的是此时 rpm 已经把文件部署到真实系统路径了所以脚本里操作的是/usr/lib/aktd-client而不是%{buildroot}。而%install阶段文件还在 BUILDROOT 里操作的是%{buildroot}下的路径两者写反是新手最容易犯的错。3.4 rpmbuild 构建产物与安装升级卸载打包命令需要显式指定 spec 文件和顶层目录rpmbuild -ba /opt/rh/rpm/SPECS/demo-1.0.0.spec --define _topdir /opt/rh/rpm-ba表示构建二进制包和源码包如果只需要二进制包可以改用-bb。--define _topdir指定之后可以省略前提是当前用户家目录下存在~/rpmbuild标准结构否则必须显式指定。构建产物在RPMS/x86_64/下文件名类似aktd-client-1.0.0-beta.x86_64.rpm。安装、升级、卸载命令sudo rpm -ivh --replacefiles aktd-client-1.0.0-beta.x86_64.rpm sudo rpm -Uvh --replacefiles aktd-client-1.0.0-beta.x86_64.rpm sudo rpm -e aktd-client--replacefiles在目标路径已存在同名文件时允许覆盖。但要注意它只跳过文件冲突不解决依赖问题。yum 或 dnf 安装时提示“没有可用软件包 nginx”说明当前软件源里没有这个包要么换源要么用 rpm 本地包。网上搜“rpm 安装 mysql”走的就是这条路下载官网 rpm 包后rpm -ivh遇到依赖报错别直接加--nodeps硬装那样装完服务大概率起不来。从镜像站下载的openssh、vsftpd这类 rpm 包名里往往带系统代号比如oe2203sp1先确认包名里的系统标识和当前发行版匹配再执行安装。4. 安装验证与桌面集成desktop 文件、维护脚本顺序和权限排错4.1 desktop 文件决定程序能否出现在应用菜单程序装完系统菜单栏没有图标这是客户端软件交付时最常被吐槽的问题。菜单栏的显示逻辑不读安装目录而是读/usr/share/applications/下的.desktop文件。客户端包里通常会带一个 desktop 文件安装后需要复制到应用目录并赋 644 权限[Desktop Entry] Nameaktd-client GenericNameaktd client Commentdesktop quick launch Keywordsaktd;client Exec/usr/lib/aktd-client/start.sh Icon/usr/share/icons/aktd-client/app.png Terminalfalse TypeApplication CategoriesDevelopment;Utility;字段含义如下表字段含义易错点Name应用名别写中文部分老桌面环境乱码Exec执行的命令路径带空格需加引号建议写绝对路径Icon图标路径写绝对路径最稳写图标名依赖主题目录Terminal是否使用终端客户端程序通常为falseType启动器类型固定ApplicationCategories应用分类分号结尾是标准写法Icon字段如果写成Iconaktd-client系统会去/usr/share/icons/hicolor/下按主题查找对应文件找不到就显示空白图标。Icon/usr/share/icons/aktd-client/app.png这种绝对路径写法虽然不够优雅但兼容性最好。安装后若菜单不显示排查顺序固定是desktop 文件是否存在于/usr/share/applications/、权限是否 644、Exec指向的可执行文件是否存在且带执行权限。这三项里 90% 的问题出在最后一项。4.2 DEB 与 RPM 维护脚本的执行顺序对照DEB 的四个脚本和 RPM 的四个脚本段不是简单的一一对应触发时机有细微差别。先看对照表生命周期阶段DEB 脚本RPM 脚本段安装前preinst%pre安装后postinst%post卸载前prerm%preun卸载后postrm%postun除了时机对应两者接收的参数也不同。DEB 脚本收到的$1取值范围是install、upgrade、remove、purgeRPM 脚本段收到的$1是数字1 表示安装或升级0 表示卸载。这个差异直接决定脚本怎么写# DEB postrm 中区分卸载和彻底清除 if [ $1 purge ]; then rm -rf /var/lib/aktd-client fi # RPM %postun 中区分升级和真实卸载 %postun if [ $1 -eq 0 ]; then rm -rf /usr/lib/aktd-client/logs fiRPM 的%postun在升级时也会执行一次此时$1大于 0如果脚本里无条件删掉整个安装目录升级就会把新版本的运行数据一起干掉。所以涉及用户数据、日志目录这类文件一律用参数判断后再删。这个细节在 DEB 和 RPM 双格式交付时尤其容易踩两边脚本往往是从一套逻辑改过来的参数判断忘了换卸载时就把日志和配置全清了。4.3 权限与编码打包前最后一道检查维护脚本执行失败dpkg -i和rpm -ivh都会报错并回滚根源通常是两类问题没有执行权限、编码格式不对。DEBIAN 目录下的脚本打包前必须chmod xRPM 那边%post等脚本段由 rpmbuild 内部处理但 spec 文件本身如果是从 Windows 编辑后传上来的行尾会是 CRLFrpmbuild 执行到脚本段时会报/bin/sh: /var/tmp/...: bad interpreter: /bin/sh^M。这类文件统一用dos2unix转换或者执行一次sed -i s/\r$//批量清理。之后再把打包目录里所有脚本用如下命令做一次语法检查bash -n aktd/DEBIAN/postinst bash -n aktd/DEBIAN/postrmbash -n只做语法解析不执行内容遇到缺失的右引号、写错的 if 分支会直接报错。这一步能过滤掉大概三成“安装后无响应”的案例因为 postinst 脚本里可能藏着解压失败后未处理的退出码导致整个安装流程回滚但用户只看到“安装失败”四个字。另一个高频问题是用uname -m或dpkg --print-architecture查看了架构却不比对包文件名结果在麒麟这类基于 Debian 衍生构建的发行版上装了 x86 的包运行时才暴露兼容性问题。5. 用 fpm 快速产出 deb/rpm目录转包、依赖声明与报错排查手动搭建 DEB 目录、逐字敲 spec 文件一套流程至少半小时。内部工具类程序经常要在一个版本里同时交付 deb 和 rpm这时候用 fpm 能把半小时压缩到一分钟。fpm 是一个目录转包的命令行工具支持把指定目录转换成 deb、rpm 等多种格式很多 Electron 应用分发 Linux 版时也是先用 electron-builder 产出可执行目录再交给 fpm 补上 deb/rpm 外壳处理方式和 Python 系用 pyinstaller 先出目录再套包是同一个套路。安装 fpm 需要 Ruby 环境gem install fpm如果 gem 源慢先切换镜像源再安装。装完后执行目录转包命令fpm -s dir -t deb \ -n aktd-client -v 1.0.0 \ -C aktd/ --prefix /usr/lib/aktd-client \ --after-install scripts/postinst.sh \ --before-remove scripts/prerm.sh \ -p aktd-client_1.0.0_amd64.deb .对应 RPM 版本fpm -s dir -t rpm \ -n aktd-client -v 1.0.0 --iteration beta \ -C aktd/ --prefix /usr/lib/aktd-client \ --after-install scripts/postinst.sh \ -p aktd-client-1.0.0-beta.x86_64.rpm .参数说明-s dir声明输入是目录-t deb或-t rpm声明输出格式-C指定进入哪个目录取文件--prefix是安装到系统后的目标路径--after-install对应 postinst 脚本--before-remove对应 prerm 脚本。deb 和 rpm 的文件名分隔符规则不同deb 用下划线连接版本rpm 用横线连接 Releasefpm 不会自动替你纠正输出文件名最好手动对齐这个约定。依赖声明用--depends参数deb 写包名和版本约束rpm 同理fpm 会转换成对应格式的元数据字段。注意 deb 的依赖名和 rpm 不一定一致比如 JDK 在 deb 里可能叫openjdk-8-jre在 rpm 里是java-1.8.0-openjdk跨格式交付时依赖名要分别确认。fpm 报错最常见的是两个一个是环境提示平台不匹配多半是 fpm 版本和 Ruby 版本冲突升级 fpm 或锁定 gem 版本可以解决另一个是打包后安装发现目录结构不对根因是-C和--prefix配合失误——-C决定了从哪个目录里取文件--prefix决定文件在安装目标上的绝对位置前者写错会把整台机器的目录都打进去。生成的包先验证再安装dpkg -I aktd-client_1.0.0_amd64.deb rpm -qip aktd-client-1.0.0-beta.x86_64.rpmdpkg -I和rpm -qip分别查看 deb 与 rpm 的元数据确认 Name、Version、Depends 字段无误后在干净容器里完整执行一遍dpkg -i或rpm -ivh看过退出码再交付。本文还有配套的精品资源点击获取
