Linux制作deb格式安装包教程

9,766 阅读11分钟

我们通常在使用Debian系的Linux发行版时,常常会遇到一些deb格式安装包,使用dpkg命令即可安装。然而,有时候也会下载到一些软件是压缩包,就没那么方便了。我们能不能把它自己打包成deb安装包呢?当然是可以的。

1,认识deb安装包结构

在制作安装包之前,我们认识一下deb包的内容:

image.png

这是我要打包的内容,可见除了DEBIAN文件夹,其余都是应用程序文件,这里其余部分的目录模拟了安装目录(看起来和安装完成后根目录里面对应内容结构一致),这是因为deb安装包安装就是把内容释放到了根目录/

DEBIAN文件夹中必须有control文件,表示安装包基本信息。

此外,还可以有:

  • preinst 软件安装之前会执行的脚本
  • postinst 软件安装完成后执行的脚本
  • prerm 软件卸载之前会执行的脚本
  • postrm 软件卸载之后会执行的脚本

这四个脚本非必须,如果有,这四个脚本文件必须要有可执行权限。

2,开始制作安装包

以官方下载的Typora编辑器软件为例,该软件下载得到的是压缩包,我们将其制作为deb安装包。

(1) 创建一个目录存放所有文件

我创建了一个名为pack的文件夹,位于/home/maiqu/Downloads/pack,放置安装包内容和配置,下文也以此为例。

(2) 创建DEBIAN文件夹并编写control文件

我们在pack文件夹中创建DEBIAN文件夹,在其中新建名为control的文件并编辑,我这里内容如下:

Package: typora
Version: 13.6.1
Section: utils
Priority: optional
Architecture: amd64
Installed-Size: 218600
Maintainer: swsk33<example@163.com>
Provides: typora
Description: 自己打包的Typora安装包

上述字段意义如下:

  • Package 包名
  • Version 版本号
  • Section 软件的类别,可以是utilsnetmailtextdevel等等
  • Priority 软件对于系统的重要程度,如requiredstandardoptionalextra等等
  • Architecture 架构,32位软件填写i38664位软件填写amd64,如果是既能在32位系统运行又能在64为系统运行可以填写all
  • Installed-Size 安装后大小,单位为kb
  • Maintainer 维护者,为维护者名<邮箱>的形式
  • Provides 供应者
  • Description 描述

上面的字段是必须字段或者常用字段。此外,control文件还可以有以下字段,以下字段非必须,按需加入:

  • Essential 申明是否是系统最基本的软件包,值为yes或者no,如果是的话,这就表明该软件是维持系统稳定和正常运行的软件包,不允许任何形式的卸载
  • Depends 软件所依赖的其他软件包和库。如果是依赖多个软件包,彼此之间采用英文逗号隔开(例如该值为libc6, default-jre,说明该软件包依赖于libc6default-jre这两个软件包,缺一不可)
  • Pre-Depends 软件安装前必须安装、配置依赖性的软件包和库
  • Recommends 这个字段表明推荐的安装的其他软件包
  • Suggests 建议安装的其他软件包

注意,control文件最末尾必须保留一个空行,否则打包会报错。

此时,DEBIAN文件夹中的文件就准备完成了,我们还要确保其权限是正确的,否则可能后续出现问题。一般来说:

  • DEBIAN文件夹本身通常需要755权限位
  • control文件需要是644权限位
  • 其余脚本例如preinst等也需要是755权限位

我们可以在打包目录下执行下列命令确保权限正确:

# 确保权限正确
chmod -R 755 ./DEBIAN/
chmod 644 ./DEBIAN/control

(3) 将待打包内容放进来

上面我们知道了,其余内容即为被打包的软件内容,会被直接释放到根目录。那么我们这里也需要保持目录结构。

例如我想让Typora安装到/opt文件夹中,那么我们这里就在pack文件夹中创建opt文件夹,将Typora文件夹放进这个opt文件夹:

image.png

除了DEBIAN文件夹,其它内容都会被直接放到根目录,那么这时我们就要在这里模拟出安装目录,把应用程序文件进行组织,使其最终能安装到我们想要的位置。

3,进行打包

刚刚完成了安装包配置文件编写,现在就可以打包了。使用dpkg命令打包:

dpkg -b "待打包目录" "生成deb安装包目录"

我的待打包内容都在/home/maiqu/Downloads/pack文件夹下,那么我执行命令:

cd /home/maiqu/Downloads
dpkg -b "pack" "Typora-linux.deb"

等待安装包构建完成,然后我们就可以使用dpkg -i命令安装它了!

image.png

如果要卸载,还是使用apt remove命令即可,包名就是上面control文件中Package字段的值。

4,扩展内容

到此,我们已经学习了deb打包基本操作。不过在实际对软件,或自己开发的小工具进行打包时,仍然有很多需要注意的地方,这里将常见的技巧或内容进行讲解。

(1) 创建桌面图标

对于在图形化界面下使用的软件,我们通常还需要包含一个桌面图标配置文件,这样用户安装之后就能够直接在启动器打开。在Linux下,我们知道桌面图标文件位于/usr/share/applications目录下,因此这里只需在我们的打包目录pack文件夹中建立usr/share/applications,在里面新建一个desktop文件表示桌面图标即可,桌面图标文件的写法参考:链接

需要注意的是,快捷方式的可执行文件、图标、运行目录指向必须对应为这个安装包安装完成后应用程序的所在位置,如下图:

image.png

(2) 使用fakeroot保证所属用户一致性

文件权限是Linux操作系统中一个重要的部分,在我们制作安装包时,默认情况下安装后的文件所属用户是我们自己,大多数文件是744或者755权限。

如果只是在自己电脑上安装,可能不会出现什么问题,不过如果将安装包安装在其它机器上,安装后的文件所属用户和该机器上用户不一致,就有可能因为权限问题导致我们自己打包的软件在其它机器运行出现问题。

因此,推荐的做法是确保打包的文件所属用户统一为root用户即可,我们无需使用chown命令手动修改文件所属用户,而是在打包时直接借助fakeroot命令即可。

fakeroot命令用于模拟root用户,但不会实际具备root权限,这在一些需要root用户执行命令又要保证一定安全性的情景下非常适用,绝大多数系统已经自带fakeroot命令了,不过如果没有也可以通过下列命令安装:

sudo apt install fakeroot

然后打包时,在前面加上fakeroot即可:

fakeroot dpkg -b "pack" "Typora-linux.deb"

此时再安装该安装包,就会发现安装的文件所属用户是root了!建议自己制作安装包时,使用fakeroot命令进行。

(3) 命令行程序打包

除了本文提供的打包图形界面软件的例子之外,有时我们可能还会打包命令行程序。那么如何确保用户安装了我们打包的命令行程序之后,就可以直接在终端运行了呢?对于简单的命令行程序,我们直接使其安装到/usr/bin下即可。但是对于一些命令行程序可能还带有其它资源文件、动态链接库等,就不能一股脑扔到/usr/bin下面了。

在Linux中,要使得一个不在/usr/bin目录(或不在PATH中任意目录)下的命令行程序也能够在终端直接调用,可以采用下列方法:

  • 将命令行程序可执行文件链接到/usr/bin目录下
  • 将命令行程序所在目录加入到PATH环境变量

上述思路也可以用于我们打包命令行程序的过程,确保用户安装后就能直接使用我们的命令,而无需额外配置环境变量。

我这里的示例命令行程序打包目录如下:

image-20260418200346078

可见,我这里要打包的命令行程序安装目录将会是/opt/cli-demo,其中可执行文件位于安装目录的bin目录下,有两个命令行程序(已具备可执行权限),我希望用户安装后就能直接将这两个程序作为命令执行,而无需手动配置环境变量。

接下来,我们就根据上述思路分别实践一下,大家根据实际情况或喜好选其一即可。

① 链接法

我们可以确保安装后/usr/bin下存在这两个可执行文件的软链接指向这两个可执行文件本身,因此在打包之前我们可以手动完成这个软链接创建工作,使得我们安装包中就包含这个软链接。

在打包目录下,创建usr/bin并进入,然后使用ln命令,以相对路径形式创建指向这两个可执行文件的软链接即可:

# 当前你已cd到打包目录下
# 创建usr/bin目录
mkdir -p usr/bin
cd usr/bin

# 创建软链接
# ln -s 原始文件名/路径 软链接文件名/路径
ln -s ../../opt/cli-demo/bin/print-demo print-demo
ln -s ../../opt/cli-demo/bin/print-time print-time

此时,我们的打包目录就变成了这样:

image-20260418201220485

后面使用fakeroot dpkg -b打包即可。

如果可执行文件目录中有很多可执行文件,我们一个个ln -s会很慢,这种情况下可以使用下列命令批量创建软链接:

# 假设当前已位于打包目录的usr/bin目录下
# exe_path的值替换为你自己打包软件的可执行文件目录的相对路径
exe_path=../../opt/cli-demo/bin
for item in $(ls $exe_path); do
	ln -s ${exe_path}/${item} ${item}
done

通过这种方式,我们的安装包中不仅仅有我们的可执行程序,还包含了软链接,且这些软链接会被安装至/usr/bin目录下,用户可以直接在终端执行我们上述自定义的print-demoprint-time命令。

② 环境变量脚本

除了软链接方法之外,我们还可以编写环境变量脚本包含在安装包中,也就是安装后自动在用户的PATH环境变量里面加上我们的可执行文件目录。

回顾一下我们日常在Linux中设定系统环境变量,通常是使用下列方式:

  • /etc/profile追加内容
  • /etc/profile.d目录下新增sh脚本并在其中声明环境变量

显然,在/etc/profile.d目录下新增脚本的方式更好维护,因此我们也可以在安装包的etc/profile.d中包含对应的环境变量脚本:

# 假设当前已位于打包目录下
# 创建etc/profile.d并进入
mkdir -p etc/profile.d
cd etc/profile.d

# 创建环境变量脚本,扩展名必须是sh,确保权限是644(无需可执行权限)
touch demo-cli-env.sh
chmod 644 demo-cli-env.sh

编辑demo-cli-env.sh,在其中使用export语句将我们安装后软件的可执行目录加到PATH即可:

# 脚本顶部无需声明'#!/bin/sh'这一行
# export PATH时记得追加自己的目录在原始PATH值后(或之前也行),但不要覆盖PATH(即忘记写$PATH),PATH中多个路径是用:隔开的
# 追加的路径是安装后软件可执行文件所在目录绝对路径,这里无法使用相对路径
export PATH=$PATH:/opt/cli-demo/bin

最终,打包目录如下:

image-20260418203534281

最后打包即可。需要注意的是,这种方法用户在安装软件后需要重启或注销系统并重新登录后(远程SSH连接则需要断开重连),才能使用我们的命令,因为环境变量脚本在系统或Shell启动时就会加载完毕,后续即使/etc/profile.d新增了脚本也不会实时读取,这也是这个方法存在的一个局限性。

此外,如果用户使用的终端没有遵循POSIX Shell规范,例如Fish Shell,那么它并不会加载/etc/profile.d下的脚本。以Fish Shell为例,要想兼容它也很简单:在Fish Shell的全局配置目录下也增加一个专属的环境变量脚本即可。

# 假设当前已经在打包目录下
# 额外兼容Fish Shell,思路其实是一样的
mkdir -p etc/fish/conf.d
cd etc/fish/conf.d

# 创建用于Fish的环境变量脚本
touch demo-cli-env.fish
chmod 644 demo-cli-env.fish

编辑脚本内容:

#!/bin/fish

# 使用Fish Shell语法追加环境变量
set -gx PATH $PATH:/opt/cli-demo/bin

可见,环境变量脚本法虽然不会“破坏”系统的/usr/bin目录,但是仍然需要用户注销系统或重新连接才能生效。因此很多情况下,我们更加偏向于使用链接法实现该目的,且Debian官方也更加推荐链接法。

(4) 包名冲突导致更新覆盖

一些软件官方并不会提供安装包,我们希望自己下载一个最新的并打包使用,但是apt仓库中可能又存在该软件的老版本,这样我们如果打包该软件和apt中的重名了,后续在apt update时可能就会发生冲突,导致apt仓库中的老版本把我们自己打包安装的新版本反而覆盖掉了。

以Maven工具为例,官方网站仅能下载压缩包(目前最新3.9.15版本),而apt仓库也有maven这个包,但是版本较老(Ubuntu 24.04发行版中的apt仓库的maven版本号是3.8.7-2),因此,当我们自己打包的Maven的control文件如果直接写软件包名为maven,就会和apt仓库已有的后续发生冲突。

你可能会说:我们把control中的包名改成别的不就行了,例如maven-xxx。这确实是最简单有效的办法,但是可能不够“美观”,且后续如果你想卸载它,还忘记了具体包名,那就会带来点小麻烦。

那么有没有一种方法确保:即使包名冲突了,我们优先使用自己安装的最新版本,而不使用apt仓库的版本呢?

事实上是可以的,借助aptxxx.pref配置文件配置优先级即可,这些配置文件位于/etc/apt/preferences.d目录下。根据这个思路,我们在打包内容中包含一个maven.pref配置,并使其优先使用我自己打包的本地版本:

# 假设你当前已经在打包目录下
# 创建配置文件目录并进入
mkdir -p etc/apt/preferences.d
cd etc/apt/preferences.d

# 创建apt优先级配置文件,扩展名需要是pref,同时确保权限为644
touch maven.pref
chmod 644 maven.pref

编辑文件内容如下:

Package: maven
Pin: release *
Pin-Priority: -1

上述配置表示,禁止maven软件包从任何apt源安装(通过将该软件包的任意来源安装优先级设为-1),这样后续安装了该软件包后,就会以我们本地安装的这个版本为主,而不会去使用apt仓库中的。

最终,我的打包目录如下:

image-20260418211945871

后续进行打包安装后,再apt update,也不会出现apt源中的maven包和我们自己打包的这个发生冲突的情况。