从零开始制作deb包:让你的软件分发像喝水一样简单

昨天分享了rpm包制作,评论区有小伙伴问能否在ubantu使用的时候我才发现。是我疏忽了,只考虑rpm了。没考虑到deb安装包的制作!今天就把这个坑补上(又混一篇,嘻嘻!!!)

image-20250906213518850image-20250906213518850

为什么要制作deb包你可能会问,直接复制文件不是挺好的吗?为什么非要搞成deb包呢?

我之前也是这么想的,直到遇到了几个问题。比如说,我们的监控工具需要创建特定的用户、设置权限、配置systemd服务,还要处理配置文件的备份和恢复。如果用脚本来做这些事情,代码会变得很复杂,而且容易出错。

用deb包就不一样了,它有标准化的安装、卸载流程,可以处理依赖关系,还能跟系统的包管理器完美集成。用户只需要一个dpkg -i命令就能搞定所有事情,卸载的时候也很干净。

准备工作环境这次用的是ubantu 24.04.1 LTS

image-20250906214013739image-20250906214013739

制作deb包需要安装一些工具:

代码语言:javascript复制sudo apt update

sudo apt install build-essential devscripts debhelper dh-make这些工具的作用各不相同。build-essential提供了基本的编译环境,devscripts包含了一堆有用的脚本,debhelper是制作deb包的核心工具,dh-make可以快速生成包的模板。

创建包的基本结构假设我们要打包一个叫做"mymonitor"的监控工具。整个工作目录的结构应该是这样的:

代码语言:javascript复制mymonitor-1.0/ # 主工作目录

├── src/ # 源码目录

│ └── mymonitor # 可执行文件

├── config/ # 配置文件目录

│ └── mymonitor.conf # 配置文件

├── systemd/ # systemd服务文件目录

│ └── mymonitor.service # 服务文件

└── debian/ # deb包制作目录

├── control # 包信息文件

├── rules # 构建规则文件

├── changelog # 变更日志

├── copyright # 版权信息

├── compat # 兼容性版本

├── postinst # 安装后脚本

├── prerm # 卸载前脚本

└── postrm # 卸载后脚本创建这个结构的步骤是:

代码语言:javascript复制# 1. 创建主工作目录

mkdir mymonitor-1.0

cd mymonitor-1.0

# 2. 创建源码相关目录

mkdir -p src config systemd

# 3. 创建debian目录

mkdir debian这里有个重要概念要理解:源码目录和安装目标目录是不同的。

• src/、config/、systemd/ 这些是源码目录,存放你要打包的原始文件• 而在rules文件中,debian/mymonitor/usr/bin/ 这些是安装目标目录,是deb包安装时文件的最终位置我来举个具体例子说明这个映射关系:

代码语言:javascript复制# 源码文件位置 → 安装后的系统位置

src/mymonitor → /usr/bin/mymonitor

config/mymonitor.conf → /etc/mymonitor/mymonitor.conf

systemd/mymonitor.service → /lib/systemd/system/mymonitor.service在rules文件中,我们通过cp命令建立这种映射:

代码语言:javascript复制override_dh_auto_install:

# 先创建目标目录结构

mkdir -p debian/mymonitor/usr/bin

mkdir -p debian/mymonitor/etc/mymonitor

mkdir -p debian/mymonitor/lib/systemd/system

# 然后复制文件到目标位置

cp src/mymonitor debian/mymonitor/usr/bin/

cp config/mymonitor.conf debian/mymonitor/etc/mymonitor/

cp systemd/mymonitor.service debian/mymonitor/lib/systemd/system/这里的debian/mymonitor/是一个临时目录,dpkg-buildpackage会把这个目录的内容打包成deb文件。安装deb包时,debian/mymonitor/usr/bin/mymonitor就会被复制到系统的/usr/bin/mymonitor。

我刚开始的时候也搞不清楚,总是把文件放错位置。后来发现一个简单的理解方法:

1. 工作目录:你当前编辑文件的地方2. 源码目录:存放原始文件的地方(src、config等)3. 构建目录:debian/包名/ 下面的目录结构,这个要和最终安装位置一致4. 安装目录:用户系统上的最终位置(/usr/bin、/etc等)再看一个更复杂的例子,比如我们的监控工具需要这些文件:

代码语言:javascript复制# 创建完整的源码目录结构

mkdir -p src/bin src/lib

mkdir -p config/default config/examples

mkdir -p docs/man systemd init.d

mkdir -p web/static web/templates

# 放入对应的文件

echo '#!/usr/bin/env python3' > src/bin/mymonitor

echo 'import sys' > src/lib/monitor_lib.py

echo '[monitor]' > config/default/mymonitor.conf

echo 'interval=10' >> config/default/mymonitor.conf对应的rules文件就要这样写:

代码语言:javascript复制override_dh_auto_install:

# 创建所有需要的目标目录

mkdir -p debian/mymonitor/usr/bin

mkdir -p debian/mymonitor/usr/lib/mymonitor

mkdir -p debian/mymonitor/etc/mymonitor

mkdir -p debian/mymonitor/etc/mymonitor/examples

mkdir -p debian/mymonitor/usr/share/man/man1

mkdir -p debian/mymonitor/lib/systemd/system

mkdir -p debian/mymonitor/usr/share/mymonitor/web/static

mkdir -p debian/mymonitor/usr/share/mymonitor/web/templates

# 复制可执行文件

cp src/bin/mymonitor debian/mymonitor/usr/bin/

chmod +x debian/mymonitor/usr/bin/mymonitor

# 复制库文件

cp src/lib/*.py debian/mymonitor/usr/lib/mymonitor/

# 复制配置文件

cp config/default/mymonitor.conf debian/mymonitor/etc/mymonitor/

cp config/examples/* debian/mymonitor/etc/mymonitor/examples/

# 复制文档

cp docs/man/mymonitor.1 debian/mymonitor/usr/share/man/man1/

# 复制服务文件

cp systemd/mymonitor.service debian/mymonitor/lib/systemd/system/

# 复制web文件

cp -r web/static/* debian/mymonitor/usr/share/mymonitor/web/static/

cp -r web/templates/* debian/mymonitor/usr/share/mymonitor/web/templates/构建完成后,你可以用这个命令检查包的内容:

代码语言:javascript复制dpkg -c ../mymonitor_1.0-1_amd64.deb输出会显示包里所有文件的路径,这样你就能确认文件是否放在了正确的位置。

还有一个调试技巧,构建过程中可以查看临时目录:

代码语言:javascript复制# 构建过程中,可以查看这个目录的内容

ls -la debian/mymonitor/

tree debian/mymonitor/这个临时目录的结构就是最终安装到系统中的结构,非常直观。

我记得刚开始做包的时候,经常出现文件找不到或者权限不对的问题,基本都是因为目录结构搞错了。多练习几次,理解了这个映射关系就好了。

control文件这个文件定义了包的基本信息:

代码语言:javascript复制Source: mymonitor

Section: utils

Priority: optional

Maintainer: Your Name

Build-Depends: debhelper (>= 10)

Standards-Version: 4.1.2

Package: mymonitor

Architecture: amd64

Depends: python3, systemd

Description: A simple monitoring tool

This is a monitoring tool for system resources.

It provides real-time monitoring capabilities

and can send alerts when thresholds are exceeded.这里有个坑,Description字段的格式比较特殊。第一行是简短描述,后面的详细描述每行都要以空格开头。我第一次做的时候就是因为格式不对,构建一直失败。

rules文件这个文件定义了如何构建和安装包:

代码语言:javascript复制#!/usr/bin/make -f

%:

dh $@

override_dh_auto_install:

mkdir -p debian/mymonitor/usr/bin

mkdir -p debian/mymonitor/etc/mymonitor

mkdir -p debian/mymonitor/var/log/mymonitor

mkdir -p debian/mymonitor/lib/systemd/system

cp src/mymonitor debian/mymonitor/usr/bin/

cp config/mymonitor.conf debian/mymonitor/etc/mymonitor/

cp systemd/mymonitor.service debian/mymonitor/lib/systemd/system/

chmod +x debian/mymonitor/usr/bin/mymonitor记得给rules文件加上执行权限:

代码语言:javascript复制chmod +x debian/ruleschangelog文件这个文件记录版本变更历史,格式很严格:

代码语言:javascript复制mymonitor (1.0-1) unstable; urgency=medium

* Initial release

* Added basic monitoring functionality

* Added systemd service support

-- Your Name Mon, 15 Jan 2024 10:00:00 +0800时间格式必须严格按照RFC 2822标准,不然会报错。我建议用date -R命令生成正确的时间格式。

copyright文件版权信息文件:

代码语言:javascript复制Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/

Upstream-Name: mymonitor

Source: https://github.com/yourname/mymonitor

Files: *

Copyright: 2024 Your Name

License: MIT

License: MIT

Permission is hereby granted, free of charge, to any person obtaining a

copy of this software and associated documentation files (the "Software"),

to deal in the Software without restriction...compat文件这个文件只需要一个数字,表示debhelper的兼容级别:

代码语言:javascript复制10处理安装前后的脚本deb包的一个强大功能就是可以在安装前后执行自定义脚本。我们可以创建以下几个文件:

postinst文件(安装后执行)代码语言:javascript复制#!/bin/bash

set -e

# 创建用户

if ! id "mymonitor" &>/dev/null; then

useradd -r -s /bin/false mymonitor

fi

# 设置权限

chown mymonitor:mymonitor /var/log/mymonitor

chmod 755 /var/log/mymonitor

# 启用并启动服务

systemctl daemon-reload

systemctl enable mymonitor

systemctl start mymonitor

exit 0prerm文件(卸载前执行)代码语言:javascript复制#!/bin/bash

set -e

# 停止服务

if systemctl is-active --quiet mymonitor; then

systemctl stop mymonitor

fi

if systemctl is-enabled --quiet mymonitor; then

systemctl disable mymonitor

fi

exit 0postrm文件(卸载后执行)代码语言:javascript复制#!/bin/bash

set -e

case "$1" in

purge)

# 完全删除时清理用户和数据

if id "mymonitor" &>/dev/null; then

userdel mymonitor

fi

rm -rf /var/log/mymonitor

;;

remove)

# 只是删除包,保留配置

;;

esac

systemctl daemon-reload

exit 0记得给这些脚本加上执行权限:

代码语言:javascript复制chmod +x debian/postinst debian/prerm debian/postrm准备要打包的文件在项目根目录下创建对应的目录结构,放入要打包的文件:

代码语言:javascript复制mkdir -p src config systemd比如我们的监控脚本:

代码语言:javascript复制# src/mymonitor

#!/usr/bin/env python3

import time

import psutil

import json

def main():

while True:

cpu_percent = psutil.cpu_percent(interval=1)

memory = psutil.virtual_memory()

data = {

'cpu': cpu_percent,

'memory': memory.percent,

'timestamp': time.time()

}

print(json.dumps(data))

time.sleep(10)

if __name__ == '__main__':

main()配置文件:

代码语言:javascript复制# config/mymonitor.conf

[monitor]

interval = 10

cpu_threshold = 80

memory_threshold = 85

[logging]

level = INFO

file = /var/log/mymonitor/mymonitor.logsystemd服务文件:

代码语言:javascript复制# systemd/mymonitor.service

[Unit]

Description=MyMonitor Service

After=network.target

[Service]

Type=simple

User=mymonitor

ExecStart=/usr/bin/mymonitor

Restart=always

RestartSec=10

[Install]

WantedBy=multi-user.target构建deb包现在所有文件都准备好了,可以开始构建了:

代码语言:javascript复制dpkg-buildpackage -us -uc -b参数说明:

• -us: 不签名源码包• -uc: 不签名变更文件• -b: 只构建二进制包如果一切顺利,你会在上级目录看到生成的deb文件:

代码语言:javascript复制ls ../mymonitor_1.0-1_amd64.deb构建过程中可能会遇到各种错误。比较常见的有:

1. 文件权限问题:确保所有脚本都有执行权限2. 格式错误:特别是changelog和control文件的格式3. 依赖问题:检查Build-Depends是否正确我记得第一次构建的时候,光是格式错误就折腾了半天。debian的包格式要求真的很严格,一个空格都不能错。

测试安装包构建成功后,先在测试环境验证一下:

代码语言:javascript复制sudo dpkg -i ../mymonitor_1.0-1_amd64.deb检查安装是否正确:

代码语言:javascript复制# 检查文件是否正确安装

ls -la /usr/bin/mymonitor

ls -la /etc/mymonitor/

ls -la /var/log/mymonitor/

# 检查服务状态

systemctl status mymonitor

# 检查用户是否创建

id mymonitor测试卸载:

代码语言:javascript复制sudo dpkg -r mymonitor如果要完全清理(包括配置文件):

代码语言:javascript复制sudo dpkg -P mymonitor一些实用技巧在实际制作过程中,我总结了一些小技巧:

使用dh_make快速生成模板如果你觉得手动创建这些文件太麻烦,可以用dh_make生成模板:

代码语言:javascript复制dh_make -e your.email@example.com -f ../mymonitor-1.0.tar.gz不过生成的模板文件比较复杂,需要大量修改。我个人还是喜欢手动创建,更清楚每个文件的作用。

处理配置文件如果你的包包含配置文件,可以在debian目录下创建conffiles文件:

代码语言:javascript复制/etc/mymonitor/mymonitor.conf这样dpkg就知道这是配置文件,升级时会妥善处理。

添加依赖检查在postinst脚本中可以添加一些依赖检查:

代码语言:javascript复制# 检查Python3是否安装

if ! command -v python3 &> /dev/null; then

echo "Error: Python3 is required but not installed"

exit 1

fi

# 检查psutil模块

if ! python3 -c "import psutil" &> /dev/null; then

echo "Installing psutil..."

pip3 install psutil

fi处理多架构如果你的软件需要支持多种架构,可以在control文件中设置:

代码语言:javascript复制Architecture: any或者指定具体架构:

代码语言:javascript复制Architecture: amd64 arm64高级功能添加预依赖和冲突有时候你的包可能与其他包冲突,或者需要在某个包之前安装:

代码语言:javascript复制Pre-Depends: some-package

Conflicts: conflicting-package

Replaces: old-package使用triggers如果你的包需要在其他包安装后执行某些操作,可以使用triggers机制。创建debian/triggers文件:

代码语言:javascript复制interest /usr/share/applications然后在postinst中处理trigger:

代码语言:javascript复制case "$1" in

triggered)

# 处理trigger事件

update-desktop-database

;;

esac分包对于复杂的软件,可能需要分成多个包。比如主程序包、开发包、文档包等。在control文件中定义多个Package段即可。

调试技巧制作deb包的过程中难免会遇到问题,这里分享几个调试方法:

查看包内容代码语言:javascript复制dpkg -c mymonitor_1.0-1_amd64.deb查看包信息代码语言:javascript复制dpkg -I mymonitor_1.0-1_amd64.deb模拟安装代码语言:javascript复制dpkg --simulate -i mymonitor_1.0-1_amd64.deb检查包质量代码语言:javascript复制lintian mymonitor_1.0-1_amd64.deblintian会检查包是否符合Debian政策,虽然有些警告可以忽略,但错误一定要修复。

自动化构建如果你需要经常构建包,可以写个简单的脚本:

代码语言:javascript复制#!/bin/bash

VERSION=${1:-1.0}

PACKAGE_NAME="mymonitor"

# 清理旧文件

rm -f ../${PACKAGE_NAME}_*.deb

rm -f ../${PACKAGE_NAME}_*.changes

rm -f ../${PACKAGE_NAME}_*.buildinfo

# 更新版本号

sed -i "1s/.*/${PACKAGE_NAME} (${VERSION}-1) unstable; urgency=medium/" debian/changelog

# 构建包

dpkg-buildpackage -us -uc -b

if [ $? -eq 0 ]; then

echo "Package built successfully: ../${PACKAGE_NAME}_${VERSION}-1_amd64.deb"

else

echo "Build failed!"

exit 1

fi实战案例:将nginx打包成deb包现在我们来把自定义编译的nginx打包成deb包。因为我们需要添加一些第三方模块,官方的nginx包不能满足需求。这个过程比较复杂,但很有代表性,我详细说一下。

首先准备nginx源码和需要的模块:

代码语言:javascript复制wget http://nginx.org/download/nginx-1.20.2.tar.gz

tar -xzf nginx-1.20.2.tar.gz

cd nginx-1.20.2创建debian目录结构:

代码语言:javascript复制mkdir debiannginx的control文件比较复杂,因为依赖比较多:

代码语言:javascript复制cat <control

Source: nginx-custom

Section: httpd

Priority: optional

Maintainer: Your Name

Build-Depends: debhelper (>= 10), libssl-dev, libpcre3-dev, zlib1g-dev, libgeoip-dev

Standards-Version: 4.1.2

Package: nginx-custom

Architecture: amd64

Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, lsb-base

Provides: httpd, nginx

Conflicts: nginx, nginx-full, nginx-light

Description: High performance web server (custom build)

Nginx is a web server with a strong focus on high concurrency, performance

and low memory usage. This is a custom build with additional modules.

EOF这里要注意几个点。${shlibs:Depends}会自动检测动态库依赖,Conflicts字段防止与系统自带的nginx冲突。

rules文件需要处理编译过程:

代码语言:javascript复制#!/usr/bin/make -f

export DEB_BUILD_MAINT_OPTIONS = hardening=+all

export DEB_CFLAGS_MAINT_APPEND = -Wp,-D_FORTIFY_SOURCE=2 -fPIC

%:

dh $@

override_dh_auto_configure:

./configure \

--prefix=/etc/nginx \

--sbin-path=/usr/sbin/nginx \

--modules-path=/usr/lib/nginx/modules \

--conf-path=/etc/nginx/nginx.conf \

--error-log-path=/var/log/nginx/error.log \

--http-log-path=/var/log/nginx/access.log \

--pid-path=/var/run/nginx.pid \

--lock-path=/var/run/nginx.lock \

--http-client-body-temp-path=/var/cache/nginx/client_temp \

--http-proxy-temp-path=/var/cache/nginx/proxy_temp \

--http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp \

--http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp \

--http-scgi-temp-path=/var/cache/nginx/scgi_temp \

--user=nginx \

--group=nginx \

--with-http_ssl_module \

--with-http_realip_module \

--with-http_addition_module \

--with-http_sub_module \

--with-http_dav_module \

--with-http_flv_module \

--with-http_mp4_module \

--with-http_gunzip_module \

--with-http_gzip_static_module \

--with-http_random_index_module \

--with-http_secure_link_module \

--with-http_stub_status_module \

--with-http_auth_request_module \

--with-http_xslt_module=dynamic \

--with-http_image_filter_module=dynamic \

--with-http_geoip_module=dynamic \

--with-threads \

--with-stream \

--with-stream_ssl_module \

--with-stream_ssl_preread_module \

--with-stream_realip_module \

--with-stream_geoip_module=dynamic \

--with-http_slice_module \

--with-file-aio \

--with-http_v2_module

override_dh_auto_install:

$(MAKE) DESTDIR=$(CURDIR)/debian/nginx-custom install

# 创建必要的目录

mkdir -p debian/nginx-custom/var/cache/nginx

mkdir -p debian/nginx-custom/var/log/nginx

mkdir -p debian/nginx-custom/etc/nginx/conf.d

mkdir -p debian/nginx-custom/etc/nginx/sites-available

mkdir -p debian/nginx-custom/etc/nginx/sites-enabled

mkdir -p debian/nginx-custom/usr/share/nginx/html

mkdir -p debian/nginx-custom/lib/systemd/system

# 复制配置文件

cp debian/nginx.conf debian/nginx-custom/etc/nginx/

cp debian/default.conf debian/nginx-custom/etc/nginx/conf.d/

cp debian/nginx.service debian/nginx-custom/lib/systemd/system/

# 复制默认页面

cp debian/index.html debian/nginx-custom/usr/share/nginx/html/

cp debian/50x.html debian/nginx-custom/usr/share/nginx/html/

override_dh_auto_clean:

dh_auto_clean

rm -f objs/Makefilenginx的postinst脚本比较复杂,需要创建用户、设置权限、处理服务:

代码语言:javascript复制cat <postinst

#!/bin/bash

set -e

case "$1" in

configure)

# 创建nginx用户

if ! getent group nginx >/dev/null; then

addgroup --system nginx

fi

if ! getent passwd nginx >/dev/null; then

adduser --system --disabled-login --ingroup nginx \

--no-create-home --home /nonexistent \

--gecos "nginx user" --shell /bin/false nginx

fi

# 设置目录权限

chown -R nginx:adm /var/log/nginx

chmod 755 /var/log/nginx

chown -R nginx:nginx /var/cache/nginx

chmod 700 /var/cache/nginx

# 创建默认站点软链接

if [ ! -e /etc/nginx/sites-enabled/default ] && [ -e /etc/nginx/sites-available/default ]; then

ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default

fi

# 测试配置文件

if nginx -t 2>/dev/null; then

systemctl daemon-reload

if systemctl is-enabled nginx.service >/dev/null 2>&1; then

systemctl reload nginx.service || systemctl restart nginx.service

else

systemctl enable nginx.service

systemctl start nginx.service

fi

else

echo "Nginx configuration test failed. Please check your configuration."

echo "Run 'nginx -t' for more details."

fi

;;

esac

exit 0

eoofprerm脚本处理服务停止:

代码语言:javascript复制cat <prerm

#!/bin/bash

set -e

case "$1" in

remove|upgrade|deconfigure)

if [ -x /usr/sbin/nginx ]; then

if systemctl is-active nginx.service >/dev/null 2>&1; then

systemctl stop nginx.service

fi

fi

;;

esac

exit 0

eoof还需要准备一些配置文件模板。nginx.conf:

代码语言:javascript复制cat <nginx.conf

user nginx;

worker_processes auto;

pid /run/nginx.pid;

events {

worker_connections 1024;

}

http {

log_format main '$remote_addr - $remote_user [$time_local] "$request" '

'$status $body_bytes_sent "$http_referer" '

'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

error_log /var/log/nginx/error.log;

sendfile on;

tcp_nopush on;

tcp_nodelay on;

keepalive_timeout 65;

types_hash_max_size 2048;

include /etc/nginx/mime.types;

default_type application/octet-stream;

include /etc/nginx/conf.d/*.conf;

include /etc/nginx/sites-enabled/*;

}

eoofsystemd服务文件nginx.service:

代码语言:javascript复制cat <nginx.service

[Unit]

Description=A high performance web server and a reverse proxy server

Documentation=man:nginx(8)

After=network.target nss-lookup.target

[Service]

Type=forking

PIDFile=/run/nginx.pid

ExecStartPre=/usr/sbin/nginx -t -q -g 'daemon on; master_process on;'

ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'

ExecReload=/bin/kill -s HUP $MAINPID

KillMode=mixed

KillSignal=SIGTERM

PrivateDevices=yes

LimitNOFILE=65536

[Install]

WantedBy=multi-user.target

eoof代码语言:javascript复制# changelog

cat > debian/changelog << EOF

nginx-custom (1.20.2-1) unstable; urgency=medium

* Custom nginx build with additional modules

-- $(whoami) <$(whoami)@$(hostname)> $(date -R)

EOF

# copyright

echo "Custom nginx build" > debian/copyright

# compat

echo "10" > debian/compat

EOF

代码语言:javascript复制#开始构建

dpkg-buildpackage -us -uc -b构建中...

image-20250906231247313image-20250906231247313

构建完成

image-20250906231907845image-20250906231907845

构建nginx包的时候经常会遇到依赖问题,特别是一些开发库。我建议先在干净的Docker环境中测试构建过程:

代码语言:javascript复制FROM ubuntu:20.04

RUN apt update && apt install -y build-essential devscripts debhelper \

libssl-dev libpcre3-dev zlib1g-dev libgeoip-dev libxslt1-dev libgd-dev

COPY . /src

WORKDIR /src

RUN dpkg-buildpackage -us -uc -bnginx包构建成功后,测试安装:

代码语言:javascript复制sudo dpkg -i nginx-custom_1.20.2-1_amd64.deb

sudo systemctl status nginx

curl http://localhost这个nginx打包的例子比较复杂,但涵盖了很多实际场景中会遇到的问题:编译配置、用户管理、服务处理、配置文件管理等。掌握了这个例子,基本上大部分软件的打包都能搞定了。

发布和分发布和分发包构建好了,接下来就是分发的问题。有几种常见的方式:

直接分发deb文件最简单的方式就是直接把deb文件放到文件服务器上,用户下载后手动安装。不过这种方式有个问题,就是更新比较麻烦,用户需要手动检查新版本。

搭建APT仓库如果你有多个包需要维护,或者需要频繁更新,建议搭建自己的APT仓库。我之前用过几种方案:

最简单的是用reprepro工具:

代码语言:javascript复制sudo apt install reprepro创建仓库配置:

代码语言:javascript复制mkdir -p /var/www/apt/conf

cat > /var/www/apt/conf/distributions << EOF

Origin: MyCompany

Label: MyCompany Repository

Codename: focal

Architectures: amd64 arm64

Components: main

Description: Internal software repository

EOF添加包到仓库:

代码语言:javascript复制cd /var/www/apt

reprepro includedeb focal /path/to/mymonitor_1.0-1_amd64.deb然后配置nginx或apache提供HTTP访问。用户只需要添加你的仓库源就能用apt安装了:

代码语言:javascript复制echo "deb http://your-server/apt focal main" | sudo tee /etc/apt/sources.list.d/mycompany.list

sudo apt update

sudo apt install mymonitor使用GitHub Releases如果你的项目托管在GitHub上,可以利用GitHub Actions自动构建和发布:

代码语言:javascript复制name: Build DEB Package

on:

push:

tags:

- 'v*'

jobs:

build:

runs-on: ubuntu-latest

steps:

- uses: actions/checkout@v2

- name: Install build dependencies

run: |

sudo apt update

sudo apt install build-essential devscripts debhelper

- name: Build package

run: |

dpkg-buildpackage -us -uc -b

- name: Create Release

uses: actions/create-release@v1

env:

GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

with:

tag_name: ${{ github.ref }}

release_name: Release ${{ github.ref }}

draft: false

prerelease: false

- name: Upload DEB file

uses: actions/upload-release-asset@v1

env:

GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

with:

upload_url: ${{ steps.create_release.outputs.upload_url }}

asset_path: ../mymonitor_*.deb

asset_name: mymonitor.deb

asset_content_type: application/vnd.debian.binary-package版本管理策略随着软件的迭代,版本管理变得很重要。Debian包的版本号格式是upstream-version-debian-revision。

比如1.2.3-1表示上游版本是1.2.3,Debian打包版本是1。如果只是修改了打包脚本而软件本身没变,可以发布1.2.3-2。

我一般的做法是:

• 软件功能更新:增加upstream版本号• 修复打包问题:增加debian版本号• 重大更新:直接跳到下一个大版本更新changelog的时候要注意格式:

代码语言:javascript复制mymonitor (1.1-1) unstable; urgency=medium

* Added email notification feature

* Fixed memory leak in monitoring loop

* Updated systemd service configuration

-- Your Name Wed, 20 Jan 2024 14:30:00 +0800

mymonitor (1.0-2) unstable; urgency=low

* Fixed postinst script permissions issue

* Updated package description

-- Your Name Tue, 16 Jan 2024 09:15:00 +0800常见问题和解决方案在制作deb包的过程中,我遇到过不少坑,这里总结一些常见问题:

文件权限问题有时候安装后文件权限不对,特别是可执行文件。除了在rules文件中设置chmod,还可以在postinst中再次确认:

代码语言:javascript复制chmod +x /usr/bin/mymonitor

chown root:root /usr/bin/mymonitor服务启动失败systemd服务配置有问题是常见情况。记得在postinst中加上错误处理:

代码语言:javascript复制systemctl daemon-reload

if ! systemctl enable mymonitor; then

echo "Warning: Failed to enable mymonitor service"

fi

if ! systemctl start mymonitor; then

echo "Warning: Failed to start mymonitor service"

echo "Check 'systemctl status mymonitor' for details"

fi依赖地狱有时候你的软件依赖的包在目标系统上版本不对。可以在control文件中指定版本范围:

代码语言:javascript复制Depends: python3 (>= 3.6), python3-psutil (>= 5.0)配置文件冲突如果用户修改了配置文件,升级时可能会有冲突。dpkg会询问用户如何处理,但你也可以在脚本中智能处理:

代码语言:javascript复制# 在postinst中备份用户配置

if [ -f /etc/mymonitor/mymonitor.conf ]; then

cp /etc/mymonitor/mymonitor.conf /etc/mymonitor/mymonitor.conf.backup

fi多环境适配如果你的软件需要在不同的Ubuntu版本上运行,可能需要做一些适配:

条件依赖代码语言:javascript复制Depends: python3 (>= 3.6) | python3.6, systemd | upstart版本检测在脚本中检测系统版本:

代码语言:javascript复制if [ -f /etc/os-release ]; then

. /etc/os-release

if [ "$VERSION_ID" = "18.04" ]; then

# Ubuntu 18.04 特殊处理

echo "Detected Ubuntu 18.04"

fi

fi说实话,多版本适配是个很头疼的问题。我的建议是尽量保持简单,只支持主流的LTS版本。

最后!!!制作deb包看起来复杂,但掌握了基本套路后其实不难。关键是要理解每个文件的作用,特别是control、rules和各种脚本文件。

我觉得最重要的几点是:

1. 格式要严格按照标准,特别是changelog和control文件2. 安装卸载脚本要处理好各种异常情况3. 文件权限和用户权限要设置正确4. 多测试,特别是在干净的环境中测试虽然现在容器化部署很流行,但deb包在某些场景下还是很有用的,特别是系统级的工具和服务。而且掌握了deb包制作,对理解Linux系统的包管理机制也很有帮助。

最后提醒一下,制作包是个细致活,第一次做肯定会遇到各种问题,不要着急,多看日志,多查文档。Debian的官方文档虽然比较枯燥,但很全面,遇到问题时是最好的参考。

如果这篇文章对你有帮助,别忘了点赞转发支持一下!想了解更多运维实战经验和技术干货,记得关注微信公众号@运维躬行录,领取学习大礼包!!!我会持续分享更多接地气的运维知识和踩坑经验。让我们一起在运维这条路上互相学习,共同进步!

公众号:运维躬行录

个人博客:躬行笔记