目录

利用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 发行版的安装包,并通过相应的包管理工具进行安装和卸载。希望这个教程能帮助你顺利打包并部署你的应用程序!