利用fpm打包二进制程序(简版教程)
本文记录了在 linux 环境下,利用 fpm 挂载打包二进制程序的教程。
利用fpm打包二进制程序为rpm或deb包
1. 安装所需工具
首先,需要确保你的系统安装了必要的工具和依赖项。
安装 fpm 的前提:
Ruby 和 相关开发工具。
GCC 和 Make 等编译工具。
RPM 和 Debian 打包工具。
在 CentOS 7 或 Debian/Ubuntu 系统上,你可以按以下步骤安装这些工具:
Debian 12 (或其他 Debian/Ubuntu 系统):
sudo apt-get install ruby ruby-dev gcc make rpm-build -y
CentOS 7(本教程在该平台部署)
yum install gcc make rpm-build -y
yum install -y rh-ruby27 rh-ruby27-ruby-devel
scl enable rh-ruby27 bash
ruby -v
# ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x86_64-linux]
安装 fpm:
fpm 是一个用于打包的软件包管理工具,可以方便地生成 .deb 和 .rpm 包。
使用 gem(Ruby 的包管理工具)来安装 fpm:
sudo gem install --no-document fpm
Fetching rexml-3.4.4.gem
Successfully installed rexml-3.4.4
Fetching stud-0.0.23.gem
Successfully installed stud-0.0.23
Fetching dotenv-2.8.1.gem
Successfully installed dotenv-2.8.1
Fetching insist-1.0.0.gem
Successfully installed insist-1.0.0
Fetching mustache-0.99.8.gem
Successfully installed mustache-0.99.8
Fetching clamp-1.3.3.gem
Successfully installed clamp-1.3.3
Fetching cabin-0.9.1.gem
Successfully installed cabin-0.9.1
Fetching pleaserun-0.0.33.gem
Successfully installed pleaserun-0.0.33
Fetching arr-pm-0.0.12.gem
Successfully installed arr-pm-0.0.12
Fetching backports-3.25.2.gem
Successfully installed backports-3.25.2
Fetching fpm-1.17.0.gem
Successfully installed fpm-1.17.0
11 gems installed
2. 准备软件包目录结构
为了让 fpm 正常工作,你需要创建一个目录结构,将你的应用和相关文件组织好。以下是本人的目录结构:
/root/haoyu/app
├── bin # 二进制文件目录
├── etc # 配置文件目录
├── sh # 脚本文件目录
├── system # systemd 服务文件目录
└── dist # 打包后的输出目录
3. 编写 fpm 打包脚本
使用 fpm 生成 .rpm 和 .deb 包,首先需要一个打包脚本。假设你的应用名为 hugo,下面是一个示例脚本 build.sh,它将自动识别并打包应用。
build.sh 示例:
#!/usr/bin/env bash
set -euo pipefail
ROOT_APP_DIR="/root/haoyu/app"
WORKDIR="$(pwd)/_work_haoyu"
OUTDIR="$(pwd)/dist"
DATE_VER="$(date +%Y.%m.%d)"
ITERATION="1"
ARCH_RPM="$(uname -m)"
[ "$ARCH_RPM" = "x86_64" ] && ARCH_DEB="amd64" || ARCH_DEB="$ARCH_RPM"
log() { echo -e "[`date +'%F %T'`] $*"; }
die() { echo "ERROR: $*" >&2; exit 1; }
check_fpm() { command -v fpm >/dev/null 2>&1 || die "请先安装 fpm"; }
ensure_dirs() { mkdir -p "$WORKDIR" "$OUTDIR"; }
clean_work() { rm -rf "$WORKDIR"; mkdir -p "$WORKDIR"; }
build_one() {
local APP="$1"
local SRC="$ROOT_APP_DIR/$APP"
[ -d "$SRC" ] || { log "skip $SRC 不存在"; return 1; }
log "开始构建应用: $APP"
# PKGROOT 映射到 /haoyu
local PKGROOT="$WORKDIR/pkg_$APP"
rm -rf "$PKGROOT"
mkdir -p "$PKGROOT/haoyu/bin" \
"$PKGROOT/haoyu/app" \
"$PKGROOT/haoyu/etc/$APP" \
"$PKGROOT/haoyu/sh/$APP" \
"$PKGROOT/haoyu/system"
# ----------------------
# 复制二进制文件
# ----------------------
if [ -d "$SRC/bin" ] && [ "$(ls -A "$SRC/bin")" ]; then
cp -pr "$SRC/bin/"* "$PKGROOT/haoyu/bin/"
else
log "⚠ 没有匹配到二进制文件"
fi
# ----------------------
# 复制脚本
# ----------------------
if [ -d "$SRC/sh" ] && [ "$(ls -A "$SRC/sh")" ]; then
cp -pr "$SRC/sh/"* "$PKGROOT/haoyu/sh/$APP/"
else
log "⚠ 没有匹配到脚本文件"
fi
# ----------------------
# 复制配置文件
# ----------------------
if [ -d "$SRC/etc" ] && [ "$(ls -A "$SRC/etc")" ]; then
cp -pr "$SRC/etc/"* "$PKGROOT/haoyu/etc/$APP/"
else
log "⚠ 没有匹配到配置文件"
fi
# ----------------------
# 复制 service 文件
# ----------------------
if [ -d "$SRC/system" ] && [ "$(ls -A "$SRC/system")" ]; then
cp -pr "$SRC/system/"* "$PKGROOT/haoyu/system/"
else
log "⚠ 没有匹配到 service 文件"
fi
# ----------------------
# 创建软链 /haoyu/app/<APP> -> ../bin/<最新版本二进制>
# ----------------------
local MAIN_EXEC_NAME=$(ls "$PKGROOT/haoyu/bin/" | grep "$APP" | sort -V | tail -n1)
ln -sf "../bin/$MAIN_EXEC_NAME" "$PKGROOT/haoyu/app/$APP"
# ----------------------
# post-install / pre-uninstall 脚本
# ----------------------
local SCRIPTDIR="$WORKDIR/scripts_$APP"
mkdir -p "$SCRIPTDIR"
local POSTINST="$SCRIPTDIR/post-install.sh"
local PREUNINST="$SCRIPTDIR/pre-uninstall.sh"
# post-install
cat > "$POSTINST" <<EOF
#!/bin/bash
set -e
# 设置 root 权限
chown -R root:root /haoyu || true
# 安装 service
if [ -f /haoyu/system/$APP.service ]; then
cp -p /haoyu/system/$APP.service /etc/systemd/system/$APP.service
chmod 644 /etc/systemd/system/$APP.service
systemctl daemon-reload
systemctl enable $APP.service
systemctl restart $APP.service
fi
EOF
# pre-uninstall
cat > "$PREUNINST" <<EOF
#!/bin/bash
set -e
if [ "\$1" = "remove" ] || [ "\$1" = "0" ]; then
if [ -f /etc/systemd/system/$APP.service ]; then
systemctl stop $APP.service || true
systemctl disable $APP.service || true
rm -f /etc/systemd/system/$APP.service || true
systemctl daemon-reload || true
fi
fi
EOF
chmod +x "$POSTINST" "$PREUNINST"
# ----------------------
# 打包
# ----------------------
local PKGNAME="haoyu.app-$APP"
pushd "$WORKDIR" >/dev/null
fpm -s dir -t rpm -n "$PKGNAME" -v "$DATE_VER" --iteration "$ITERATION" --architecture "$ARCH_RPM" \
--after-install "$POSTINST" --before-remove "$PREUNINST" "$PKGROOT/=/"
fpm -s dir -t deb -n "$PKGNAME" -v "$DATE_VER" --iteration "$ITERATION" --architecture "$ARCH_DEB" \
--after-install "$POSTINST" --before-remove "$PREUNINST" "$PKGROOT/=/"
popd >/dev/null
# 移动输出到 dist
find "$WORKDIR" -maxdepth 2 -type f \( -name "${PKGNAME}*.rpm" -o -name "${PKGNAME}_*.deb" \) -exec mv -f {} "$OUTDIR"/ \;
log "构建完成:$APP → 输出目录: $OUTDIR"
}
main() {
[ $# -ge 1 ] || { echo "Usage: $0 <app-name|all>"; exit 2; }
check_fpm
ensure_dirs
clean_work
if [ "$1" = "all" ]; then
for d in "$ROOT_APP_DIR"/*; do
[ -d "$d" ] || continue
build_one "$(basename "$d")" || log "跳过 $(basename "$d")"
done
else
build_one "$1"
fi
log "全部完成,输出目录:$OUTDIR"
}
main "$@"
4. 打包应用程序
假设你的应用是 hugo,你可以使用以下命令运行 build.sh 脚本,生成 .rpm 和 .deb 包:
./build.sh hugo
[2025-11-24 19:02:33] 开始构建应用: hugo
[2025-11-24 19:02:35] ⚠ 没有匹配到脚本文件
[2025-11-24 19:02:35] ⚠ 没有匹配到配置文件
Created package {:path=>"haoyu.app-hugo-2025.11.24-1.x86_64.rpm"}
Created package {:path=>"haoyu.app-hugo_2025.11.24-1_amd64.deb"}
[2025-11-24 19:04:06] 构建完成:hugo → 输出目录: /root/dist
[2025-11-24 19:04:06] 全部完成,输出目录:/root/dist
5. 检查生成的包
打包后,你可以在 dist 目录下找到生成的 .rpm 和 .deb 包。
ls /root/haoyu/app/dist/
du -sh /root/dist/*
39M /root/dist/haoyu.app-hugo-2025.11.24-1.x86_64.rpm
39M /root/dist/haoyu.app-hugo_2025.11.24-1_amd64.deb
6. 安装 .rpm 或 .deb 包
在 CentOS 7 上安装 .rpm 包:
rpm -ivh /root/haoyu/app/dist/haoyu.app-hugo-2025.11.23-1.x86_64.rpm
rpm -ivh /root/dist/haoyu.app-hugo-2025.11.24-1.x86_64.rpm
Preparing... ################################# [100%]
Updating / installing...
1:haoyu.app-hugo-2025.11.24-1 ################################# [100%]
Created symlink from /etc/systemd/system/multi-user.target.wants/hugo.service to /etc/systemd/system/hugo.service.
在 Debian 12 上安装 .deb 包:
sudo dpkg -i /root/haoyu/app/dist/haoyu.app-hugo_2025.11.23-1_amd64.deb
安装后启动服务
# /haoyu/app/hugo new site /opt/blog
# service hugo status
Redirecting to /bin/systemctl status hugo.service
● hugo.service - hugo service by haoyuli
Loaded: loaded (/etc/systemd/system/hugo.service; enabled; vendor preset: disabled)
Active: active (running) since Mon 2025-11-24 19:08:46 CST; 1min 22s ago
Docs: https://haoyu.app
Main PID: 775 (hugo)
CGroup: /system.slice/hugo.service
└─775 /haoyu/app/hugo server --disableFastRender --ignoreCache --source /opt/blog --baseURL https://haoyu.app/ --bind 0.0.0.0 --port 443...
Nov 24 19:08:47 centos7 hugo[775]: Non-page files │ 0
Nov 24 19:08:47 centos7 hugo[775]: Static files │ 0
Nov 24 19:08:47 centos7 hugo[775]: Processed images │ 0
Nov 24 19:08:47 centos7 hugo[775]: Aliases │ 0
Nov 24 19:08:47 centos7 hugo[775]: Cleaned │ 0
Nov 24 19:08:47 centos7 hugo[775]: Built in 7 ms
Nov 24 19:08:47 centos7 hugo[775]: Environment: "development"
Nov 24 19:08:47 centos7 hugo[775]: Serving pages from disk
Nov 24 19:08:47 centos7 hugo[775]: Web Server is available at https://haoyu.app:443/ (bind address 0.0.0.0)
Nov 24 19:08:47 centos7 hugo[775]: Press Ctrl+C to stop
你也可以使用 systemctl 启动服务。例如,安装 hugo.service 后,执行:
/haoyu/app/hugo new site /opt/blog
sudo systemctl start hugo.service
查看服务状态:
sudo systemctl status hugo.service
7. 卸载软件包
使用 apt 卸载 .deb 包:
sudo apt remove haoyu.app-hugo
如果需要完全删除(包括配置文件):
sudo apt purge haoyu.app-hugo
使用 rpm 卸载 .rpm 包:
sudo rpm -e haoyu.app-hugo
# sudo rpm -e haoyu.app-hugo
Removed symlink /etc/systemd/system/multi-user.target.wants/hugo.service.
8. 常见问题和解决办法
fpm 安装失败:
gem install --no-document fpm
ERROR: Error installing fpm: clamp requires Ruby version < 4, >= 2.5.
检查 Ruby 和其依赖是否正确安装。centos7默认ruby版本为2.0,可以启用2.7或者3.0。 然后你可以使用 gem install fpm 安装最新版本的 fpm。
apt 报错权限问题:
N: Download is performed unsandboxed as root as file '/root/haoyu/app/dist/haoyu.app-hugo_2025.11.23-1_amd64.deb' couldn't be accessed by user '_apt'. - pkgAcquire::Run (13: Permission denied)
确保 .deb 文件权限正确,特别是文件的所有者应该是 root,并且文件对所有用户可读。
总结
以上是从安装 fpm 到最终生成 .rpm 和 .deb 包的整个过程。通过 fpm,你可以轻松地将应用程序打包成适用于不同 Linux 发行版的安装包,并通过相应的包管理工具进行安装和卸载。希望这个教程能帮助你顺利打包并部署你的应用程序!