纯 Shell 跨平台组装 deb 安装包

本文揭秘 Debian 安装包的底层归档结构,介绍如何无需 Docker 与 dpkg 工具链,仅凭纯 POSIX Shell 在 macOS/CI 上秒级跨平台组装标准 deb 包。


在 Linux 桌面与服务器生态中,Debian 系发行版(如 Ubuntu、Debian、Deepin、统信 UOS)凭借庞大的用户基数,让 .deb 成为了软件分发最主流的标准载体。

然而在现代跨平台 CI/CD 流水线中,构建 .deb 安装包却常常面临尴尬的工具链断层:

  • 官方的标准打包工具是 dpkg-deb;
  • 但在实际团队开发中,CI Runner 可能是 macOS,本地开发机也可能是 Mac;
  • 为了打出一个 Linux 安装包,传统的做法往往是被迫在 CI 中启动一个沉重的 Docker 容器(拉取几百兆的 Ubuntu 镜像、挂载卷、执行 apt update 并调用 dpkg-deb),流水线耗时直接从几秒飙升到几分钟。

在开源博客工具 hpic 的跨平台发行体系中,我们通过剖析 .deb 的底层物理归档规范,实现了一套仅凭 POSIX 标准命令(tar 与 ar)跨平台组装合法 .deb 的极客方案:无需 root 权限、无需安装 Docker,在 macOS 本地不到 0.5 秒即可产出能在 Ubuntu 上直接双击或 apt install 顺利安装的标准安装包。


一、解密 .deb 包的物理归档本质

很多开发者误以为 .deb 是某种特殊的专有二进制格式,但其实它从设计之初就遵循 Unix 哲学的朴素理念:基于标准 Unix 归档工具拼接而成的复合包。

flowchart TD
    subgraph DEB[".deb 文件实体 (标准 Unix ar 归档包)"]
        direction TB
        M1["1. debian-binary
(纯文本固定内容: 2.0\n)"] M2["2. control.tar.gz
(元数据与安装控制信息)"] M3["3. data.tar.gz
(实际的文件系统安装目录树)"] M1 --> M2 --> M3 end subgraph Control["control.tar.gz 内部解构"] direction TB C1["control (包名、版本、架构、依赖、体积等)"] C2["prerm / postinst (安装前与卸载前 Shell 钩子)"] end subgraph Data["data.tar.gz 内部解构 (Linux FHS 规范)"] direction TB D1["/usr/local/bin/your-app (可执行程序)"] D2["/usr/share/applications/app.desktop (桌面启动器)"] D3["/usr/share/pixmaps/app.png (高清图标)"] end M2 -.-> Control M3 -.-> Data

一个标准的 .deb 文件实际上就是一个 Unix ar 格式的归档文件,它严格且必须按以下顺序存放三个成员文件:

  1. debian-binary:一个纯文本文件,内容固定为字符串 2.0\n,声明该包遵循 Debian 2.0 打包协议;
  2. control.tar.gz:包含安装包元信息与维护脚本的压缩包;
    • control:包含 Package 名、Version、Architecture、Installed-Size、Maintainer 与依赖描述;
    • prerm / postinst(可选):生命周期钩子,例如卸载前执行 pkill 退出正在运行的应用。
  3. data.tar.gz:待释放到目标操作系统中的文件树(遵循 Linux FHS 标准文件目录规范,如 /usr/local/bin、/usr/share/applications 等)。

只要我们严格按照规范生成这三个文件,并使用标准 ar 命令按顺序打包,产出的文件就和 dpkg-deb 构建出的产物在二进制结构上 100% 一致!


二、生产级纯 Shell 打包脚本实现

以下提取自 hpic 的完整独立打包脚本 scripts/build-deb.sh,可直接复用于任意 Go、Rust、C++ 或可执行二进制项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#!/usr/bin/env bash
# ==============================================================================
# build-deb.sh - 纯 POSIX 原生跨平台 .deb 组装脚本
# 支持在 Linux、macOS 以及各类 CI Runner 下零外部依赖极速构建
# 用法: ./build-deb.sh <arch: amd64|arm64> <bin_path> <version> <output_dir>
# ==============================================================================
set -euo pipefail

ARCH="$1" # amd64 / arm64
BIN_PATH="$2" # 编译好的 Linux 二进制文件路径
VERSION="$3" # 纯版本号,如 1.0.0
OUTPUT_DIR="$4" # 产物输出目录

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
VERSION="${VERSION#v}" # 剥离可能带有的 v 前缀

# 1. 初始化临时工作区
STAGE_DEB="${OUTPUT_DIR}/.stage_deb_${ARCH}"
rm -rf "$STAGE_DEB"
mkdir -p "$STAGE_DEB"

# 2. 组织标准 Linux FHS 目录结构
mkdir -p "${STAGE_DEB}/DEBIAN"
mkdir -p "${STAGE_DEB}/usr/local/bin"
mkdir -p "${STAGE_DEB}/usr/share/applications"
mkdir -p "${STAGE_DEB}/usr/share/pixmaps"

# 拷贝核心二进制并赋予 755 可执行权限
cp "$BIN_PATH" "${STAGE_DEB}/usr/local/bin/hpic"
chmod 755 "${STAGE_DEB}/usr/local/bin/hpic"

# 拷贝桌面快捷方式与图标(若存在)
if [ -f "${SCRIPT_DIR}/assets/hpic.desktop" ]; then
cp "${SCRIPT_DIR}/assets/hpic.desktop" "${STAGE_DEB}/usr/share/applications/"
chmod 644 "${STAGE_DEB}/usr/share/applications/hpic.desktop"
fi

if [ -f "${SCRIPT_DIR}/assets/icon.png" ]; then
cp "${SCRIPT_DIR}/assets/icon.png" "${STAGE_DEB}/usr/share/pixmaps/hpic.png"
chmod 644 "${STAGE_DEB}/usr/share/pixmaps/hpic.png"
fi

# 3. 动态计算安装占用空间 (单位: KB)
INSTALLED_SIZE=$(du -sk "${STAGE_DEB}" | awk '{print $1}')

# 4. 生成 DEBIAN/control 控制元信息
cat <<EOF > "${STAGE_DEB}/DEBIAN/control"
Package: hpic
Version: ${VERSION}
Section: utils
Priority: optional
Architecture: ${ARCH}
Installed-Size: ${INSTALLED_SIZE}
Maintainer: bubao <https://github.com/bubao>
Description: Hexo Blog Cover and Drafts Image Manager
A lightweight desktop GUI tool for managing blog post covers.
EOF

# 5. 生成卸载前钩子 (避免用户卸载时程序仍在运行导致文件被锁)
cat <<'EOF' > "${STAGE_DEB}/DEBIAN/prerm"
#!/bin/sh
set -e
if command -v pkill >/dev/null 2>&1; then
pkill -f "/usr/local/bin/hpic" 2>/dev/null || true
fi
exit 0
EOF
chmod 755 "${STAGE_DEB}/DEBIAN/prerm"

DEB_NAME="hpic_${VERSION}_${ARCH}.deb"
FINAL_DEB="${OUTPUT_DIR}/${DEB_NAME}"

# 6. 双轨构建机制:检测系统工具并无缝回退
if command -v dpkg-deb >/dev/null 2>&1; then
echo "📦 检测到 dpkg-deb,调用系统工具打包..."
dpkg-deb -Zgzip --build "$STAGE_DEB" "$FINAL_DEB" >/dev/null 2>&1
else
echo "⚡ 未检测到 dpkg-deb,切换为纯 POSIX (ar + tar) 跨平台极速组装..."
BUILD_WORK="${OUTPUT_DIR}/.deb_raw_${ARCH}"
rm -rf "$BUILD_WORK"
mkdir -p "$BUILD_WORK"

# 生成规范文件一: debian-binary
echo "2.0" > "${BUILD_WORK}/debian-binary"

# 生成规范文件二: control.tar.gz
(cd "${STAGE_DEB}/DEBIAN" && tar -czf "${BUILD_WORK}/control.tar.gz" .)

# 生成规范文件三: data.tar.gz (排除 DEBIAN 自身)
(
cd "$STAGE_DEB"
# 兼容 macOS BSD tar 与 Linux GNU tar 的排除语法
tar --exclude='./DEBIAN' -czf "${BUILD_WORK}/data.tar.gz" .
)

# 关键:按严格次序使用 ar 命令装配为最终 .deb 包
(
cd "$BUILD_WORK"
ar -rc "$FINAL_DEB" debian-binary control.tar.gz data.tar.gz
)

rm -rf "$BUILD_WORK"
fi

rm -rf "$STAGE_DEB"
echo "✅ 打包完成: ${FINAL_DEB}"

三、避坑关键细节剖析

虽然整体原理干净利落,但在跨平台运行(尤其是从 macOS 打包给 Linux)时,有几个高频踩坑点必须严格防范:

1. ar 归档文件的严格成员顺序

在 Unix 归档格式中,Debian 包管理器(dpkg / apt)在读取 .deb 时做的是流式扫描:

  • 归档内第一个文件必须是 debian-binary;
  • 第二个必须是 control.tar.gz;
  • 第三个必须是 data.tar.gz。如果顺序颠倒(例如写成 ar -rc out.deb data.tar.gz ...),dpkg -i 会当场报错 is not a Debian format archive。

2. BSD tar 与 GNU tar 的兼容性

macOS 自带的是 BSD 版本的 tar,而 Linux 默认是 GNU tar:

  • 在排除目录时,务必使用 --exclude='./DEBIAN',避免将 DEBIAN 控制目录重复打包进 data.tar.gz;
  • 使用 -czf 默认采用通用 gzip 压缩算法,杜绝部分旧版本 dpkg 无法识别 zstd 压缩包的问题。

3. 权限必须预先规范化

  • prerm、postinst 等脚本必须在打包前执行 chmod 755,否则安装或卸载时系统会报 permission denied 无法执行;
  • 二进制可执行文件放入 /usr/local/bin 同样必须设为 755;
  • 图标与 .desktop 描述文件设为标准只读权限 644。

四、方案对比与总结

对比维度传统 Docker 方案dpkg-deb 原生方案纯 Shell (ar + tar) 方案
跨平台环境要求需安装 Docker 守护进程必须运行在 Debian/Ubuntu 系统任意 POSIX 系统(macOS / Linux / WSL)
构建耗时30 秒 ~ 2 分钟 (拉镜像/启动)~ 1 秒< 0.5 秒 (瞬时完成)
系统权限要求需要 Docker 用户组 / root需 root 或 fakeroot普通用户完全免 root
CI/CD 适用性沉重,需配 Docker-in-Docker局限于 Ubuntu runner极佳,可在任何 runner 极速秒出包

利用基础的 Unix 归档规范代替沉重的容器化方案,体现了“大道至简”的工程之美。一套十余行的纯 Shell 组装脚本,不仅彻底解除了对 Linux 宿主环境的依赖,也让桌面应用的跨平台自动化发布管道变得空前轻盈敏捷。


参考资料与延伸阅读


纯 Shell 跨平台组装 deb 安装包
https://bubao.github.io/posts/9df0c6d.html
作者
一念
发布于
2026年11月5日
许可协议