4、DEB格式详解:生成Debian包、依赖管理、适用于Ubuntu/Debian系统、配置与签名
DEB格式,说白了就是Debian系Linux的安装包标准。Ubuntu、Debian、Linux Mint这些系统都用它。我最早接触DEB包是在十年前,当时为了给公司内部部署一个监控工具,手动打了一个包,结果依赖关系没处理好,部署了三天才搞定。嗯,从那以后我就再也不敢轻视DEB的依赖管理了。
4.1 DEB包的核心结构
一个DEB包,本质上是一个ar归档文件。里面包含三个部分:
- debian-binary:版本标记文件,内容就一行"2.0"
- control.tar.gz:元数据压缩包,包含控制信息、脚本等
- data.tar.gz:实际安装的文件压缩包
你想想看,这结构其实挺简洁的。但真正让DEB强大的,是control.tar.gz里的那些文件。
核心文件清单:
- control:包名、版本、依赖、描述等元信息
- preinst:安装前执行的脚本
- postinst:安装后执行的脚本
- prerm:卸载前执行的脚本
- postrm:卸载后执行的脚本
- conffiles:标记哪些文件是配置文件
- md5sums:文件校验和列表
4.2 用CPack生成DEB包
我个人习惯用CPack来生成DEB包,省心。你只需要在CMakeLists.txt里配置好,剩下的交给工具链。
先看一个最小配置:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MyTool VERSION 1.2.3 LANGUAGES CXX)
add_executable(mytool main.cpp)
install(TARGETS mytool DESTINATION bin)
# CPack配置
set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_NAME "mytool")
set(CPACK_DEBIAN_PACKAGE_VERSION "1.2.3")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Your Name <your@email.com>")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "A useful tool for doing things")
set(CPACK_DEBIAN_PACKAGE_SECTION "utils")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64")
然后执行:
mkdir build && cd build
cmake ..
cpack -G DEB
搞定。你会得到一个 mytool_1.2.3_amd64.deb 文件。
小技巧: 我建议在CMakeLists.txt里加上 set(CPACK_PACKAGE_FILE_NAME "${CPACK_DEBIAN_PACKAGE_NAME}_${CPACK_DEBIAN_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}"),这样生成的文件名更规范,方便后续自动化处理。
4.3 依赖管理——DEB的灵魂
依赖管理是DEB包最核心的功能。没有它,你的包装上去可能跑不起来。我曾经在一个项目中,因为漏写了libssl的依赖,导致客户现场部署时程序崩溃,被运维同事追着骂了一周。
CPack里配置依赖很简单:
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl-dev (>= 1.1.0), libc6 (>= 2.28), zlib1g (>= 1:1.2.0)")
常用的依赖类型:
| 符号 | 含义 | 示例 |
|---|---|---|
| >= | 大于等于某版本 | libssl-dev (>= 1.1.0) |
| << | 小于某版本 | libfoo (<< 2.0) |
| = | 精确版本 | libbar (= 1.0.3) |
| | | 或关系 | liba | libb |
还有几个实用的CPack变量:
- CPACK_DEBIAN_PACKAGE_RECOMMENDS:推荐安装的包,不强制
- CPACK_DEBIAN_PACKAGE_SUGGESTS:建议安装的包,更弱的关系
- CPACK_DEBIAN_PACKAGE_CONFLICTS:冲突的包,不能同时安装
- CPACK_DEBIAN_PACKAGE_REPLACES:替换的包,安装时会覆盖
- CPACK_DEBIAN_PACKAGE_BREAKS:破坏的包,安装前需要卸载
注意: 依赖版本号一定要写对。我曾经遇到过因为版本号写成了 libssl-dev (> 1.1.0)(少了个等号),结果系统里装的是1.1.0版本,包管理器认为不满足条件,死活不让装。这种低级错误,排查起来特别费时间。
4.4 配置与脚本
DEB包支持在安装和卸载的不同阶段执行脚本。这些脚本放在 control.tar.gz 里,CPack可以帮你自动生成。
举个例子,安装后需要重启服务:
# postinst脚本
#!/bin/bash
set -e
case "$1" in
configure)
# 重新加载systemd服务
systemctl daemon-reload
systemctl enable mytool.service
systemctl start mytool.service
;;
abort-upgrade|abort-remove|abort-deconfigure)
exit 0
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
在CMakeLists.txt里指定脚本:
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_SOURCE_DIR}/debian/postinst;${CMAKE_SOURCE_DIR}/debian/prerm")
配置文件也需要特殊处理。DEB包管理器会智能地处理配置文件:如果用户修改过,升级时不会覆盖。用 conffiles 文件标记:
# conffiles
/etc/mytool/mytool.conf
/etc/mytool/mytool.env
CPack里配置:
set(CPACK_DEBIAN_PACKAGE_CONFFILES "/etc/mytool/mytool.conf;/etc/mytool/mytool.env")
4.5 包签名——安全性的最后一道防线
签名不是必须的,但如果你要发布到公共仓库,我强烈建议加上。没有签名的包,用户安装时会收到警告,体验很差。
签名流程分两步:
- 生成GPG密钥(如果还没有)
- 用
dpkg-sig或debsigs签名
生成密钥:
gpg --full-generate-key
# 选择RSA,密钥长度4096,有效期按需设置
签名DEB包:
dpkg-sig --sign builder mytool_1.2.3_amd64.deb
CPack本身不直接支持签名,但可以在CMakeLists.txt里加一个自定义目标:
add_custom_target(sign-deb
COMMAND dpkg-sig --sign builder ${CPACK_PACKAGE_FILE_NAME}.deb
DEPENDS package
COMMENT "Signing DEB package..."
)
然后执行:
cmake --build . --target sign-deb
提示: 在CI/CD流水线里,我习惯把GPG私钥存在密钥管理服务里,比如HashiCorp Vault或AWS KMS。构建时动态导入,用完立即销毁。这样既安全又自动化。
4.6 实战:一个完整的DEB包示例
最后,给你看一个我实际项目中用过的配置。这是一个Web服务工具,包含二进制、配置文件、systemd服务单元和日志轮转配置。
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(WebService VERSION 2.0.1 LANGUAGES CXX)
add_executable(webservice main.cpp)
install(TARGETS webservice DESTINATION /usr/bin)
install(FILES webservice.conf DESTINATION /etc/webservice)
install(FILES webservice.service DESTINATION /lib/systemd/system)
install(FILES webservice.logrotate DESTINATION /etc/logrotate.d)
# CPack DEB配置
set(CPACK_GENERATOR "DEB")
set(CPACK_DEBIAN_PACKAGE_NAME "webservice")
set(CPACK_DEBIAN_PACKAGE_VERSION "2.0.1")
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Dev Team <dev@example.com>")
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "High-performance web service tool")
set(CPACK_DEBIAN_PACKAGE_SECTION "web")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libssl1.1 (>= 1.1.1), libc6 (>= 2.31), systemd (>= 245)")
set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "nginx, certbot")
set(CPACK_DEBIAN_PACKAGE_CONFFILES "/etc/webservice/webservice.conf")
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
"${CMAKE_SOURCE_DIR}/debian/postinst"
"${CMAKE_SOURCE_DIR}/debian/prerm"
"${CMAKE_SOURCE_DIR}/debian/postrm"
)
set(CPACK_PACKAGE_FILE_NAME
"${CPACK_DEBIAN_PACKAGE_NAME}_${CPACK_DEBIAN_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}"
)
生成后,用 dpkg-deb --info 查看包信息:
dpkg-deb --info webservice_2.0.1_amd64.deb
输出应该包含你配置的所有元数据。如果发现依赖写错了,别慌,用 dpkg-deb --control 解压control文件,手动修改后重新打包也行。不过我个人建议还是改CMakeLists.txt重新生成,保持一致性。
嗯,DEB格式就讲到这里。说白了,它就是一个结构清晰、依赖管理强大的包格式。只要把依赖和脚本处理好,你的包就能在Ubuntu/Debian系统上平稳运行。下一章我们聊聊RPM格式,那又是另一套玩法了。