62 - 自定义系统打包与分发
当现成的软件包不能满足需求时,创建自定义包是自动化部署的基石。无论是将内部开发的软件打包成 .deb/.rpm,搭建私有 apt/yum 仓库供团队使用,还是构建 OCI 容器镜像进行分发——掌握打包技能让你不再受限于发行版官方仓库的边界。本章覆盖 Debian(.deb)、RHEL(.rpm)、Arch(PKGBUILD)三系的打包方法,以及私有仓库搭建和现代分发方案。
62.1 为什么创建自定义包
典型场景
| 场景 | 说明 |
|---|---|
| 内部软件分发 | 公司自研的后端服务,需要标准化安装 |
| 配置文件管理 | 将应用配置打包为包,版本化管理 |
| 补丁/定制版本 | 对上游软件打补丁后重新分发 |
| 系统模板 | 预装一组工具的元包(metapackage) |
| 合规要求 | 统一软件版本和签名,通过审计 |
| 离线部署 | 将软件和依赖打包,用于无网络环境 |
包管理 vs 手动部署
手动部署:scp → 手动改权限 → 手写 systemd unit → 没人知道装了什么
打包部署:dpkg -i app.deb → systemd 自动启用 → dpkg -l 看版本
62.2 Debian/Ubuntu:创建 .deb 包
最简方式:dpkg-deb
# 创建包的目录结构
mkdir -p myapp/DEBIAN
mkdir -p myapp/usr/local/bin
mkdir -p myapp/usr/lib/systemd/system
mkdir -p myapp/etc/myapp
# 放入你的程序文件
cat > myapp/usr/local/bin/myapp << 'EOF'
#!/bin/bash
echo "Hello from MyApp v1.0"
EOF
chmod 755 myapp/usr/local/bin/myapp
# 放入 systemd 服务文件
cat > myapp/usr/lib/systemd/system/myapp.service << 'EOF'
[Unit]
Description=MyApp Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/myapp
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOFcontrol 文件(包的元信息)
cat > myapp/DEBIAN/control << 'EOF'
Package: myapp
Version: 1.0.0-1
Section: utils
Priority: optional
Architecture: amd64
Depends: bash, systemd
Maintainer: Ops Team <ops@example.com>
Description: MyApp internal application
This package contains the MyApp server application.
It includes the binary, systemd service, and base
configuration files.
EOF维护脚本(optional)
# DEBIAN/preinst — 安装前执行
cat > myapp/DEBIAN/preinst << 'EOF'
#!/bin/bash
echo "Pre-install: preparing..."
# 如创建用户
if ! id -u myapp >/dev/null 2>&1; then
useradd -r -s /usr/sbin/nologin myapp
fi
EOF
chmod 755 myapp/DEBIAN/preinst
# DEBIAN/postinst — 安装后执行
cat > myapp/DEBIAN/postinst << 'EOF'
#!/bin/bash
set -e
systemctl daemon-reload
systemctl enable myapp.service
systemctl start myapp.service || true
echo "MyApp installed and started"
EOF
chmod 755 myapp/DEBIAN/postinst
# DEBIAN/prerm — 卸载前执行
cat > myapp/DEBIAN/prerm << 'EOF'
#!/bin/bash
systemctl stop myapp.service || true
systemctl disable myapp.service || true
EOF
chmod 755 myapp/DEBIAN/prerm
# DEBIAN/postrm — 卸载后执行
cat > myapp/DEBIAN/postrm << 'EOF'
#!/bin/bash
if [ "$1" = "purge" ]; then
rm -rf /etc/myapp
userdel -r myapp 2>/dev/null || true
fi
EOF
chmod 755 myapp/DEBIAN/postrm构建与安装
# 构建 .deb
dpkg-deb --root-owner-group --build myapp
# 生成 myapp.deb
# 检查包内容
dpkg-deb --info myapp.deb
dpkg-deb --contents myapp.deb
# 安装
sudo dpkg -i myapp.deb
# 或(自动安装依赖)
sudo apt install ./myapp.deb
# 查看已安装的包
dpkg -l | grep myapp
apt list --installed | grep myapp
# 卸载
sudo apt remove myapp # 保留配置
sudo apt purge myapp # 删除配置
# 查看包中安装了哪些文件
dpkg -L myappdeb 签名
# 生成 GPG 密钥(如没有)
gpg --full-generate-key
# 签名包
dpkg-sig --sign builder myapp.deb
# 验证签名
dpkg-sig --verify myapp.deb62.3 RHEL/Fedora:创建 .rpm 包
设置构建环境
# 安装构建工具
sudo dnf install rpm-build rpmdevtools -y
# 设置 RPM 构建目录树
rpmdev-setuptree
# 创建 ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS}.spec 文件
vim ~/rpmbuild/SPECS/myapp.specName: myapp
Version: 1.0.0
Release: 1%{?dist}
Summary: MyApp internal application
License: MIT
URL: https://example.com
Source0: %{name}-%{version}.tar.gz
Requires: bash, systemd
BuildArch: x86_64
%description
MyApp is an internal server application. It provides
a simple service managed by systemd.
%prep
%setup -q
%install
# 安装到构建根目录
mkdir -p %{buildroot}/usr/local/bin
mkdir -p %{buildroot}/usr/lib/systemd/system
mkdir -p %{buildroot}/etc/%{name}
install -m 755 myapp %{buildroot}/usr/local/bin/myapp
install -m 644 myapp.service %{buildroot}/usr/lib/systemd/system/myapp.service
install -m 644 myapp.conf %{buildroot}/etc/%{name}/myapp.conf
%pre
# 安装前:创建用户
getent group myapp >/dev/null || groupadd -r myapp
getent passwd myapp >/dev/null || useradd -r -g myapp -s /usr/sbin/nologin myapp
exit 0
%post
# 安装后:启用服务
systemctl daemon-reload
systemctl enable myapp.service >/dev/null 2>&1 || :
systemctl start myapp.service >/dev/null 2>&1 || :
%preun
# 卸载前:停止服务
if [ $1 -eq 0 ]; then
systemctl stop myapp.service >/dev/null 2>&1 || :
systemctl disable myapp.service >/dev/null 2>&1 || :
fi
%postun
# 卸载后
if [ $1 -ge 1 ]; then
systemctl daemon-reload
fi
systemctl daemon-reload
%files
%attr(755,root,root) /usr/local/bin/myapp
%attr(644,root,root) /usr/lib/systemd/system/myapp.service
%config(noreplace) /etc/%{name}/myapp.conf
%changelog
* Thu Jul 24 2026 Ops Team <ops@example.com> - 1.0.0-1
- Initial release of MyApp准备源码并构建
# 创建源码 tar.gz(.spec 中 Source0 的文件名)
mkdir ~/rpmbuild/SOURCES/myapp-1.0.0
cp /path/to/myapp-script ~/rpmbuild/SOURCES/myapp-1.0.0/myapp
cp /path/to/myapp.service ~/rpmbuild/SOURCES/myapp-1.0.0/myapp.service
cp /path/to/myapp.conf ~/rpmbuild/SOURCES/myapp-1.0.0/myapp.conf
cd ~/rpmbuild/SOURCES
tar czf myapp-1.0.0.tar.gz myapp-1.0.0/
# 构建 RPM
rpmbuild -ba ~/rpmbuild/SPECS/myapp.spec
# -ba:构建二进制包 + 源码包
# -bb:仅构建二进制包
# 查看结果
ls ~/rpmbuild/RPMS/x86_64/
# myapp-1.0.0-1.el9.x86_64.rpm安装与签名
# 安装
sudo dnf install ~/rpmbuild/RPMS/x86_64/myapp-1.0.0-1.el9.x86_64.rpm
# 查看包信息
rpm -qip ~/rpmbuild/RPMS/x86_64/myapp-*.rpm
rpm -qlp ~/rpmbuild/RPMS/x86_64/myapp-*.rpm
# 查看已安装包
rpm -q myapp
rpm -qi myapp
# 卸载
sudo rpm -e myapp
# 签名(需配置 GPG)
rpm --addsign ~/rpmbuild/RPMS/x86_64/myapp-*.rpm常用 macros
# %{_bindir} → /usr/bin
# %{_sbindir} → /usr/sbin
# %{_sysconfdir} → /etc
# %{_unitdir} → /usr/lib/systemd/system
# %{_localstatedir} → /var
# %{_sharedstatedir} → /var/lib
# %{_datadir} → /usr/share
# 使用示例
%files
%{_bindir}/myapp
%{_unitdir}/myapp.service
%config(noreplace) %{_sysconfdir}/myapp/myapp.conf62.4 Arch:PKGBUILD
Arch 打包详见 03-AUR打包与上传,此处给出简要示例:
# PKGBUILD
pkgname=myapp
pkgver=1.0.0
pkgrel=1
pkgdesc="MyApp internal application"
arch=('x86_64')
url="https://example.com"
license=('MIT')
depends=('bash' 'systemd')
source=("$pkgname-$pkgver.tar.gz::https://example.com/myapp-$pkgver.tar.gz")
sha256sums=('SKIP')
package() {
cd "$srcdir/$pkgname-$pkgver"
install -Dm755 myapp "$pkgdir/usr/local/bin/myapp"
install -Dm644 myapp.service "$pkgdir/usr/lib/systemd/system/myapp.service"
install -Dm644 myapp.conf "$pkgdir/etc/myapp/myapp.conf"
}# 构建
makepkg -si # -s 安装依赖, -i 安装包
# 生成 myapp-1.0.0-1-x86_64.pkg.tar.zst62.5 搭建私有 apt 仓库(Debian/Ubuntu)
reprepro 方式
sudo apt install reprepro -y
# 创建仓库目录
sudo mkdir -p /srv/repo/ubuntu/{conf,dists,incoming,tmp}
sudo chown -R $USER:$USER /srv/repo
# 创建仓库配置
vim /srv/repo/ubuntu/conf/distributionsOrigin: Example Internal
Label: MyApp Repository
Suite: stable
Codename: jammy
Architectures: amd64 source
Components: main
Description: Internal Ubuntu repository for MyApp
SignWith: yes # 或指定 GPG key ID
# 添加 .deb 包到仓库
reprepro -Vb /srv/repo/ubuntu includedeb jammy myapp_1.0.0-1_amd64.deb
# 查看仓库中的包
reprepro -b /srv/repo/ubuntu list jammy
# 移除包
# reprepro -b /srv/repo/ubuntu remove jammy myapp使用 nginx 提供 HTTP 访问
sudo vim /etc/nginx/sites-available/reposerver {
listen 80;
server_name repo.example.com;
root /srv/repo/ubuntu;
autoindex on;
location / {
try_files $uri $uri/ =404;
}
}sudo ln -s /etc/nginx/sites-available/repo /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx客户端配置
# 添加仓库公钥
curl -fsSL https://repo.example.com/repo-key.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/myapp.gpg
# 添加 apt 源
echo "deb [arch=amd64] https://repo.example.com jammy main" | sudo tee /etc/apt/sources.list.d/myapp.list
# 更新并安装
sudo apt update
sudo apt install myapp简易 HTTP 方式(快速测试)
# 将 .deb 文件放入某目录
mkdir ~/repo-pool
cp myapp.deb ~/repo-pool/
# 生成 Packages 索引
cd ~/repo-pool
dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
# 启动 HTTP 服务
python3 -m http.server 8080
# 客户端添加源
echo "deb [trusted=yes] http://server-ip:8080 ./" | sudo tee /etc/apt/sources.list.d/local.list
sudo apt update62.6 搭建私有 yum/dnf 仓库(RHEL/Fedora)
createrepo 方式
sudo dnf install createrepo -y
# 创建仓库目录并放入 RPM
mkdir -p /srv/repo/el9
cp ~/rpmbuild/RPMS/x86_64/myapp-*.rpm /srv/repo/el9/
# 生成仓库元数据
createrepo /srv/repo/el9/
# 每次添加新包后重新生成
createrepo --update /srv/repo/el9/Nginx 托管
sudo vim /etc/nginx/conf.d/yum-repo.confserver {
listen 80;
server_name yum-repo.example.com;
root /srv/repo;
autoindex on;
location ~ /(el[89])/ {
autoindex on;
}
}sudo nginx -t && sudo systemctl reload nginx客户端配置
# 创建 repo 文件
sudo tee /etc/yum.repos.d/myapp.repo << 'EOF'
[myapp]
name=MyApp Internal Repository
baseurl=https://yum-repo.example.com/el9/
enabled=1
gpgcheck=1
gpgkey=https://yum-repo.example.com/repo-key.asc
EOF
# 更新并安装
sudo dnf makecache
sudo dnf install myapp
# 查看可用包
dnf --repo=myapp list available62.7 Ostree 与 rpm-ostree(不可变系统分发)
对于 Fedora CoreOS、Fedora Silverblue 等不可变系统,使用 rpm-ostree 创建自定义系统镜像:
# 安装 ostree 和 rpm-ostree
sudo dnf install rpm-ostree ostree -y
# 查看当前部署
rpm-ostree status
# 在不可变系统中层叠安装包
rpm-ostree install vim htop git
# 更新到新的 commit
rpm-ostree update
# 查看包的 commit 来源
rpm-ostree db list | head -20创建自定义 OSTree 仓库进行系统级分发:
# 创建 OSTree 仓库
mkdir -p /srv/ostree
ostree --repo=/srv/ostree init --mode=archive
# 从现有 compose 导入
rpm-ostree compose tree --repo=/srv/ostree fedora-silverblue.yaml
# 通过 HTTP 提供
# nginx 配置:root /srv/ostree; autoindex on;详见 48-不可变系统。
62.8 Flatpak:桌面应用分发
Flatpak 是跨发行版的桌面应用分发方案:
# 安装 Flatpak SDK
sudo apt install flatpak flatpak-builder -y # Debian
sudo dnf install flatpak flatpak-builder -y # RHEL
# 创建 Flatpak manifest
vim com.example.myapp.ymlapp-id: com.example.myapp
runtime: org.gnome.Platform
runtime-version: '46'
sdk: org.gnome.Sdk
command: myapp
modules:
- name: myapp
buildsystem: simple
build-commands:
- install -Dm755 myapp /app/bin/myapp
sources:
- type: file
path: myapp
finish-args:
- --socket=wayland
- --socket=fallback-x11
- --share=network# 构建
flatpak-builder build-dir com.example.myapp.yml --force-clean
# 导出到本地仓库
flatpak-builder --repo=myrepo --force-clean build-dir com.example.myapp.yml
# 安装
flatpak --user install myrepo com.example.myapp
# 运行
flatpak run com.example.myapp62.9 容器镜像作为分发方式
对于服务端应用,OCI 容器镜像是最通用的分发格式:
# Dockerfile
FROM debian:bookworm-slim
RUN apt update && apt install -y --no-install-recommends \
ca-certificates curl && \
rm -rf /var/lib/apt/lists/*
COPY myapp /usr/local/bin/myapp
RUN chmod +x /usr/local/bin/myapp
RUN useradd -r -s /usr/sbin/nologin myapp
USER myapp
EXPOSE 8080
HEALTHCHECK --interval=30s CMD curl -f http://localhost:8080/health || exit 1
ENTRYPOINT ["/usr/local/bin/myapp"]# 构建
docker build -t registry.example.com/myapp:1.0.0 .
docker push registry.example.com/myapp:1.0.0
# 目标服务器拉取运行
docker run -d --name myapp -p 8080:8080 registry.example.com/myapp:1.0.0详见 46-容器技术。
62.10 自动化打包流水线
GitHub Actions 自动构建 .deb
# .github/workflows/build-deb.yml
name: Build DEB Package
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build DEB
run: |
mkdir -p myapp_${GITHUB_REF_NAME#v}/DEBIAN
mkdir -p myapp_${GITHUB_REF_NAME#v}/usr/local/bin
cp myapp myapp_${GITHUB_REF_NAME#v}/usr/local/bin/
# ... 复制 control, service 等
dpkg-deb --root-owner-group --build myapp_${GITHUB_REF_NAME#v}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: myapp-deb
path: myapp_*.deb
- name: Push to repo
run: |
curl -X POST -F "file=@myapp_*.deb" \
https://repo.example.com/upload?token=${{ secrets.REPO_TOKEN }}简单的 Watch + Build 脚本
#!/bin/bash
# watch-build.sh — 监控源码变化,自动重新打包
set -euo pipefail
REPO_DIR="/srv/repo/ubuntu"
APP_DIR="/opt/myapp"
while true; do
inotifywait -e modify,create,delete -r "$APP_DIR"
VERSION=$(date +%Y%m%d%H%M%S)
echo "检测到变更,构建版本 $VERSION..."
# 包装你的构建逻辑
mkdir -p /tmp/myapp-pkg/DEBIAN
mkdir -p /tmp/myapp-pkg/usr/local/bin
cp "$APP_DIR/myapp" /tmp/myapp-pkg/usr/local/bin/
cat > /tmp/myapp-pkg/DEBIAN/control << EOF
Package: myapp
Version: 0.0.${VERSION}
Architecture: amd64
Maintainer: CI <ci@example.com>
Description: MyApp auto-built package
EOF
dpkg-deb --root-owner-group --build /tmp/myapp-pkg
reprepro -Vb "$REPO_DIR" includedeb stable /tmp/myapp-pkg.deb
rm -rf /tmp/myapp-pkg*
echo "构建并推送到仓库完成"
done62.11 常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
| deb 安装依赖错误 | control 文件中依赖写错 | apt install ./xxx.deb 自动处理 |
| rpm 构建签名失败 | 无 GPG 密钥 | gpg --full-generate-key 创建 |
| reprepro “not found” | 架构或 codename 不匹配 | 检查 conf/distributions 配置 |
| createrepo 404 | 路径错误/Nginx root 不对 | 确认 root 和 alias 匹配 |
| 包安装后服务未启动 | 缺少 postinst/post 脚本 | 添加 systemctl enable/start |
| dpkg-deb 权限问题 | 文件列表不是 root 所有 | 加 --root-owner-group 参数 |
62.12 本章总结
| 打包方式 | 适用发行版 | 核心命令 | 复杂度 |
|---|---|---|---|
| dpkg-deb | Debian/Ubuntu | dpkg-deb --build | 低 |
| rpmbuild | RHEL/Fedora | rpmbuild -ba | 中 |
| PKGBUILD + makepkg | Arch | makepkg -si | 低 |
| reprepro | Debian/Ubuntu 仓库 | reprepro includedeb | 中 |
| createrepo | RHEL/Fedora 仓库 | createrepo --update | 低 |
| Flatpak | 跨发行版桌面应用 | flatpak-builder | 中高 |
| OCI 容器镜像 | 所有 Linux | docker build && push | 低 |
| Ostree/rpm-ostree | Fedora 不可变系统 | rpm-ostree compose | 高 |
包管理基础见 14-软件包管理通识,Arch 的 AUR 打包详见 03-AUR打包与上传,不可变系统详见 48-不可变系统。