手把手教你制作deb安装包:从目录结构到dpkg打包实战

发布时间:2026/10/5 7:30:10
手把手教你制作deb安装包:从目录结构到dpkg打包实战 最近有朋友私信我说自己写了个小工具每次给别人都要远程指导解压、配环境变量特别累问我有没有办法让安装体验像apt install一样省心。我说很简单打成 deb 安装包放到 Ubuntu 上一条dpkg -i或者双击就完事。deb 这个格式对 Ubuntu、Debian 系用户来说就是“安装包”的标准语言系统自带的 dpkg 就是它的解释器。这篇文章就围绕怎么亲手制作一个能正常安装、正常卸载的 deb 包来写适合写过脚本或编译过程序、但对 Linux 打包还不够熟的朋友。看完你会发现deb 打包本质上就是把一堆文件按照系统目录结构摆好再补充一份“说明书”而已。1. 项目概述与核心需求拆解1.1 deb安装包的本质一个受控的压缩目录第一次接触 deb 包时我也以为是多复杂的东西直到用dpkg-deb -c解包看了一眼才恍然大悟。所谓 deb 包本质是一个ar归档文件里面通常包含三个部分debian-binary版本标记文件、control.tar.gz包管理和维护脚本信息、data.tar.xz需要安装到系统里的实际文件。你可以把它想象成快递包裹外层箱子是 ar 归档里面的快递单是 control 文件真正要送到你手里的商品是 data 数据。dpkg 在安装 deb 时做的事情很简单先读快递单把 control 信息记录到/var/lib/dpkg/status数据库里然后把 data 里的文件按目录结构解压到系统根目录。卸载时反过来dpkg 根据数据库记录逐一删除文件。所以只要目录结构摆对了、control 信息写对了打包这件事就成功了一大半。这里也解释了为什么 deb 包文件名里会包含“_amd64”或“_all”这种标识它不只是装饰而是直接告诉 dpkg 这个包装在什么架构上。后面我们做包时也会用到。1.2 为什么自己制作适用场景与可选方案有人可能会问网上那么多现成 deb 包为什么还要自己动手其实日常工作中自己打 deb 的需求非常常见我把主要场景列了几个内部开发的小工具、脚本集希望放在同事机器上能一键安装。自己编译的软件想发给别人试用又不想让对方经历源码编译的折磨。学习 Linux 包管理机制想把“安装”这个过程透明化。给闭源或半闭源项目做安装包让用户有正式的卸载入口。实现 deb 打包的方案不止一种我对比过主流几种方案原理适合场景缺点dpkg-deb手工整理目录直接打包学习、精细控制、少量包需要手动管理字段和脚本checkinstall拦截make install产生的文件变化从源码快速生成安装包对自定义路径、脚本支持偏弱equivs生成只含元信息的准包伪造依赖、占位包不适合分发真实软件fpm一套命令把目录/源码转成多种包格式批量打包、跨格式依赖 Ruby出错排查成本高我的建议是新手先从dpkg-deb开始它能让你真正理解包的结构。后面项目多了想提效再考虑fpm也不迟。本篇文章全程围绕dpkg-deb展开因为它是所有 deb 包的地基。2. 准备工作与环境搭建2.1 搭建打包工作环境制作 deb 包不需要 root 权限你甚至可以在普通用户家目录下完成所有操作只要最终做好的.deb文件不在系统目录里就行。我先检查一下环境是否具备基础工具dpkg-deb --versionUbuntu 默认自带dpkg-deb一般不需要额外安装。不过我强烈建议你再装两个辅助工具sudo apt install lintian shellchecklintian是 Debian 系打包规范的检查器专门帮你看包名、目录、脚本权限这些容易踩坑的点。shellcheck是 bash 脚本静态检查工具后面写维护脚本时会用到。实测下来这两个工具能帮你把 80% 的低级错误挡在安装之前。另外强烈建议准备一个干净的临时目录比如~/packaging/。打包过程中会生成中间目录如果直接放在系统目录或者混在源码目录里很容易把无关文件打进包里。我习惯用mkdir -p ~/packaging/hello-deb这种方式隔离得干净。2.2 设计一个最小示例hello-deb工具为了把流程讲透我造一个最小但完整的示例一个叫hello-deb的命令行小工具。它本质上是一个 bash 脚本安装后输出一句问候语和系统信息。这个示例虽然简单但能覆盖“可执行文件放哪里”“配置文件放哪里”“安装和卸载脚本怎么写”这几个核心问题。先规划好文件放置位置可执行脚本放到/usr/local/bin/hello-deb配置文件放到/etc/hello-deb/config.conf。为什么是这两个位置/usr/local/bin是用户自己安装的软件存放点不会被系统升级覆盖/etc是配置文件的标准位置。理解了这个规划后面目录搭建就不会迷糊。我们的目标是最终生成一个hello-deb_1.0.0-1_all.deb安装后就能在终端直接输入hello-deb使用。3. 核心实现手把手制作一个deb安装包3.1 搭建目录布局路径即安装位置deb 包内部目录和系统目录是一一对应的。你想让文件最终出现在系统的哪个路径就把它放在包内对应路径下。以我们刚才规划的内容为例cd ~/packaging mkdir -p hello-deb/DEBIAN mkdir -p hello-deb/usr/local/bin mkdir -p hello-deb/etc/hello-deb这里有个关键点DEBIAN目录必须大写它不会安装到系统里而是存储 control 文件和维护脚本剩下的usr、etc目录则会原样覆盖到系统根目录。如果你不小心把目录写成了小写debiandpkg-deb会直接报错后面安装时也找不到任何元信息。把工具脚本写进去cat hello-deb/usr/local/bin/hello-deb EOF #!/bin/bash echo Hello from deb package! if [ -f /etc/hello-deb/config.conf ]; then echo Config content: cat /etc/hello-deb/config.conf fi EOF chmod x hello-deb/usr/local/bin/hello-deb一定要执行chmod x否则打包出来安装后还是不可执行的脚本。这个权限是保存在 deb 包里的dpkg 解压时会完整还原。再写一个配置文件示例cat hello-deb/etc/hello-deb/config.conf EOF # hello-deb 配置文件 greetinghello print_system_infotrue EOF目前目录结构是这样hello-deb/ ├── DEBIAN/ ├── etc/ │ └── hello-deb/ │ └── config.conf └── usr/ └── local/ └── bin/ └── hello-deb3.2 编写control文件deb包的身份证整个 deb 包最重要的就是DEBIAN/control文件它记录了包名、版本、架构、依赖关系等元信息。dpkg 判断能否安装、能否卸载全靠这份文件。新建hello-deb/DEBIAN/controlcat hello-deb/DEBIAN/control EOF Package: hello-deb Version: 1.0.0-1 Section: utils Priority: optional Architecture: all Depends: bash ( 4.0) Maintainer: Your Name youexample.com Description: A simple demo deb package It just prints a greeting and shows the config file. Homepage: https://example.com Installed-Size: 10 EOF我只挑几个容易出错的字段重点解释Package必须是小写字母、数字、中划线或加号组成不能用下划线。例如hello_deb就是非法包名。Version的格式是“上游版本号-修订号”比如1.0.0-1中的-1表示第几次重新打包。系统会自动比较版本号升级时靠这个判断新旧。Architecture如果是纯脚本或纯文本填all如果包含编译好的 amd64 二进制文件填amd64。填错了会导致安装时被 dpkg 拒绝。Depends声明依赖包。我们示例脚本用 bash 执行所以写上bash ( 4.0)明确最低版本要求。Description第一行是短描述后面每一行必须以一个空格开头否则 dpkg 会解析失败。写完后检查一下文件编码和换行file DEBIAN/control cat -A DEBIAN/control | headfile应该显示UTF-8 Unicode textcat -A不该出现^M这样的行尾字符。如果你用 Windows 编辑器打开过这个文件很容易引入 CRLF 换行这样 dpkg 会认不出字段。Installed-Size表示安装后占用空间单位是 KB这个字段允许估算也可以提前用du -sk统计du -sk hello-deb/usr hello-deb/etc把结果填进去即可示例工具很小写 10 KB 没问题。3.3 用dpkg-deb构建deb安装包目录和 control 都准备好后打包命令非常简单cd ~/packaging dpkg-deb --build --root-owner-group hello-deb hello-deb_1.0.0-1_all.deb我特意加了--root-owner-group参数这是很多人容易忽略的细节。如果不加这个参数包内文件属主会是你当前系统里的用户 UID比如1000:1000安装到别人机器上后文件属主就变成那个机器上的 1000 用户这通常不是你想要的。加上这个参数后所有文件会归一成root:root这才是 deb 包的标准姿态。构建成功后立刻用两个命令验证包内容dpkg-deb -I hello-deb_1.0.0-1_all.deb # 查看控制信息 dpkg-deb -c hello-deb_1.0.0-1_all.deb # 查看文件清单-I输出里能看到我们写的 control 字段-c输出会列出包内所有文件路径和权限信息。检查一下usr/local/bin/hello-deb是否带*可执行标记确认无误再进入安装环节。3.4 安装、测试与卸载流程在手上这台 Ubuntu 上实际安装测试sudo dpkg -i hello-deb_1.0.0-1_all.deb安装结束后运行我们打包的命令hello-deb如果一切正常你会看到问候语和配置文件内容。再用包管理工具确认元信息已经记录dpkg -s hello-deb卸载时也有讲究。sudo dpkg -r hello-deb会移除程序文件但保留/etc下的配置文件这符合 Linux 的习惯系统不知道你之后会不会重新安装删除配置是不合理的。如果你确实想把配置也清掉使用sudo dpkg -P hello-deb-P表示 purge彻底清除。到这一步一个最简单的 deb 包已经完整跑通了。4. 进阶自定义安装/卸载脚本4.1 脚本的作用与触发时机如果 deb 包只是解压文件很多需求没法满足。比如安装后要生成默认配置、启动服务、创建用户卸载前要停止服务、删除临时文件这些都靠维护脚本实现。Debian 定义了四种维护脚本preinst安装前、postinst安装后、prerm卸载前、postrm卸载后。我把常用触发时机整理成了表格脚本触发时机典型场景preinst在解包 data 之前停止正在运行的旧版本服务备份即将被覆盖的文件postinst在解包并记录数据库之后启动服务、创建用户、设置权限、更新缓存prerm在删除文件之前停止服务、关闭进程、清理运行态文件postrm在删除文件之后删除临时文件彻底清理配置脚本如果缺失包依然能装上只会少一些自动化行为。但如果你写了脚本就必须保证它能正确执行否则 dpkg 会把包置为“半配置”状态麻烦得很。4.2 编写带参数的维护脚本维护脚本和普通脚本不一样它会被 dpkg 带上参数调用。以postinst为例最常见的参数是configure表示包安装完成后的常规配置阶段。为了安全我们一般写一个处理多个参数的结构cat hello-deb/DEBIAN/postinst EOF #!/bin/bash set -e case $1 in configure) echo hello-deb is now configured. # 这里可以放具体操作比如给配置文件设置默认权限 chmod 644 /etc/hello-deb/config.conf ;; abort-upgrade|abort-remove|abort-deconfigure) # 升级或安装回滚时的特殊处理 ;; *) echo postinst called with unknown argument \$1 2 exit 1 ;; esac exit 0 EOF接着写卸载前执行的prermcat hello-deb/DEBIAN/prerm EOF #!/bin/bash set -e case $1 in remove|upgrade) echo hello-deb is being removed. # 如果示例里有服务就在这里停止 ;; failed-upgrade) ;; *) echo prerm called with unknown argument \$1 2 exit 1 ;; esac exit 0 EOF写完之后别忘了两件事脚本要有可执行权限语法要正确。chmod x hello-deb/DEBIAN/postinst hello-deb/DEBIAN/prerm bash -n hello-deb/DEBIAN/postinst shellcheck hello-deb/DEBIAN/postinst为什么必须加执行权限因为 dpkg 在安装时会直接执行这些脚本权限不对就会报permission denied导致整个安装流程失败。重新打包后再安装一次你会看到 postinst 输出的配置信息。4.3 添加桌面快捷方式与图标可选刚才的 hello-deb 是纯命令行工具如果你打包的是 GUI 程序还需要考虑桌面菜单项和图标。方法是在包内加入一个.desktop文件例如放到usr/share/applications/hello-deb.desktop[Desktop Entry] NameHello Deb CommentA simple demo GUI Exechello-deb Iconhello-deb Terminalfalse TypeApplication CategoriesUtility;图标则放到usr/share/icons/hicolor/48x48/apps/hello-deb.png。安装后你可以用update-desktop-database刷新菜单缓存这条命令通常放在 postinst 里if command -v update-desktop-database /dev/null 21; then update-desktop-database -q /usr/share/applications fi注意Exec路径要写命令名而不是绝对路径因为系统会通过PATH查找如果程序不是放到/usr/bin或/usr/local/bin里建议写绝对路径避免菜单打不开。4.4 给包添加md5校验文件可选Debian 规范建议包内包含DEBIAN/md5sums用来校验安装后的文件是否被篡改。虽然dpkg-deb --build不会自动生成但我们自己补一个也容易cd hello-deb find usr etc -type f -exec md5sum {} \; DEBIAN/md5sums chmod 644 DEBIAN/md5sums注意find的目录要从包根开始写生成出来的路径就是usr/...、etc/...。然后把md5sums一并打进包里。安装后可以在/var/lib/dpkg/info/hello-deb.md5sums看到这个文件dpkg --verify hello-deb会参考它检查完整性。5. 常见问题与排查技巧实录5.1 安装后命令找不到优先检查路径和权限我最早打包时遇到最多的现象就是明明安装成功了运行命令却提示command not found。这种问题八成出在文件路径或执行权限上。先用这个命令看安装后文件到底去了哪里dpkg -L hello-deb如果输出显示/usr/local/bin/hello-deb再用ls -l /usr/local/bin/hello-deb检查权限。没有x权限的话直接重新打包前执行chmod x。如果输出显示/home/yourname/...说明包内路径搭错了需要调整目录层级而不是调整系统。还有一个容易被忽略的点如果你的脚本第一行是#!/bin/bash而系统中 bash 路径不是/bin/bash虽然 Ubuntu 几乎是标准路径也不会有问题但使用env解释器会更通用#!/usr/bin/env bash这样写能兼容更多发行版。5.2 control文件解析错误先看字符和缩进dpkg对 control 文件要求很严格常见的报错是Parse error: control file或者missing description。大多不是字段问题而是格式问题。我整理过一张自检表现象排查点Description多行显示混乱长描述每行必须用单个空格开头字段名无法识别检查是否用了全角冒号中文显示乱码文件带 BOM 或编码不是 UTF-8Windows 保存后解析失败换行符变成 CRLF用cat -A看版本号带下划线下划线只允许出现在包名? 不包名不能下划线出现这种问题优先跑一遍lintian hello-deb*.deb它会直接告诉你哪个字段有问题比如E: hello-deb: malformed-description-line。你也可以用dpkg-deb -I看 dpkg 实际读到的 control 内容是不是你写的。5.3 依赖关系与架构不匹配导致的安装失败如果安装时提示dpkg: dependency problems prevent configuration of hello-deb hello-deb depends on xxx; however:说明Depends里的某个依赖在当前系统缺失。临时处理可以安装依赖后重新配置sudo apt -f install sudo dpkg --configure -a但正确的做法是在 control 里声明清楚。对于编译型程序可以使用dpkg-shlibdeps自动分析dpkg-shlibdeps -O your-binary它会输出类似shlibs:Dependslibc6 ( 2.35)的内容拿这个结果填到 control 里最准确。另外注意架构如果包内是 64 位二进制control 里却写Architecture: all安装后可能能装但运行直接Exec format error这个问题更难排查。所以看到运行错误时先file /usr/local/bin/hello-deb看二进制格式再和dpkg --print-architecture对比。5.4 postinst执行失败导致半配置状态这是维护脚本最容易引发的坑。postinst 脚本里某个命令返回非零dpkg 会停止安装并报dpkg: error processing package hello-deb (--configure): installed hello-deb package post-installation script subprocess returned error exit status 1此时包处于“已解包但未配置”的状态手动重跑配置脚本可以继续sudo /var/lib/dpkg/info/hello-deb.postinst configure但如果脚本本身有 bug重跑还会失败。我建议的排查顺序是先bash -n查语法再用shellcheck查常见问题最后把脚本里的命令拆开手动执行看哪一步失败。脚本里加set -e是好习惯但要小心它会把“预期中会失败”的命令也变成致命错误。比如一个条件判断里用了可能不存在的命令却被当成致命异常反而掩盖了真正的问题。这种情况下可以改用if command -v xxx; then ... fi形式包裹。下面这张表总结了本节的排查手法场景快速定位命令常见根因命令找不到dpkg -L 包名路径错误、权限无 x安装报依赖错误dpkg -s 包名Depends 字段不全菜单没有图标ls -l .desktop 文件Exec 路径错误脚本执行失败sudo /var/lib/dpkg/info/包名.postinst configure脚本内部命令退出非零6. 个人经验与扩展建议做 deb 包这几年我最深的体会是工具链很简单真正的难点都在细节规范上。control 文件里一个缩进、一个换行符都能让安装向导瞬间崩溃维护脚本里一个没考虑到的参数也能让升级过程直接卡壳。所以我现在的习惯是不管包多简单打包完成后都要跑一遍lintian再拿到干净虚拟机实测安装和卸载一轮才敢拿去发别人。另外建议你做包的时候尽量把升级路径考虑进去。很多新手只测了“全新安装”没测“从旧版本升级”结果 postinst 在升级时把新配置覆盖了旧配置用户数据直接丢失。稳妥做法是在 postinst 脚本里判断文件是否已存在如果存在就不要覆盖或者备份为.bak。如果后续分发场景变多可以把这些 deb 包放到自己的 apt 仓库里这样团队成员只需要apt update apt install xxx就能安装连下载页都不用维护。做本地仓库其实就是用dpkg-scanpackages生成 Packages 索引再用 nginx 托管目录并不复杂。等这篇基础操作熟练之后再往这个方向延伸你会觉得整个 Linux 包管理生态其实非常有逻辑。