目录

Gentoo Binpkg 二进制包完全指南

Gentoo 一直以来以"从源码编译"著称,但这并不意味着所有包都必须从源码编译。从 2023 年底开始,Gentoo 官方正式提供了 Binary Package Host(Binhost)服务,用户可以直接使用预编译的二进制包来加速安装和更新。

最近给 homelab 同步了一下 Gentoo 的镜像站,其中包含了 Gentoo 的 binpkg,这篇文章记录一下如何使用 binpkg 来加速安装、如何自己创建 binpkg,以及如何搭建私有的 Binhost 服务器。

作为一个使用 Gentoo 十多年的人,深知从源码编译的好处——精细的 USE flag 控制、针对硬件的编译优化、完全透明的构建过程。但在以下场景中,二进制包可以极大地提升效率:

  • 批量维护多台相似系统:只需要一台机器编译,其他机器直接使用二进制包。在 homelab 这种有多台相同配置机器的场景下特别有用
  • 减少编译大型包的等待:像 dev-libs/icu、www-client/firefox、dev-qt/qtwebengine 这类编译动辄几个小时的包,用二进制包可以在几分钟内完成安装
  • 生产环境的安全更新:先在 staging 环境编译和测试,确认无误后通过二进制包快速部署到生产系统,最大程度减少停机时间
  • 老旧系统的更新:二进制包不需要安装编译时依赖,对于资源有限的老旧系统来说是救星
  • 故障恢复:当工具链(compiler/toolchain)损坏时,预编译好的二进制包可以快速恢复系统

Gentoo 目前支持两种二进制包格式:

格式 文件后缀 Portage 版本要求 特性
XPAK(旧格式) .tbz2 所有版本 传统格式,由 tar.bz2 + xpak 元数据组成
GPKG(新格式) .gpkg.tar ≥ 3.0.31 支持 OpenPGP 签名、标准 tar 可直接解压、符合 GLEP 78 规范

Portage 3.0.31 起默认就是 GPKG,make.conf 里不用写 BINPKG_FORMAT。只有需要回退到旧的 XPAK 格式时,才要显式设置 BINPKG_FORMAT="xpak"。

GPKG 格式的文件结构清晰,包含:一个 gpkg-1 标识文件、metadata.tar{.compression} 元数据归档、image.tar{.compression} 安装文件归档、Manifest 校验文件,以及可选的 metadata.tar.sig / image.tar.sig 签名成员(签名在容器内部,不是单独的旁路文件)。因为是标准 tar 格式,可以直接用 tar 命令解包查看内容,不需要专用工具。

使用二进制包有几个硬性要求,系统之间必须满足以下兼容条件:

  • 架构和 CHOST 必须匹配:构建机和客户端的 CPU 架构以及 CHOST 必须一致
  • CFLAGS/CXXFLAGS 必须兼容:客户端的 CPU 必须支持二进制包编译时使用的所有指令集
  • CPU_FLAGS_X86 必须是子集:客户端不能使用二进制包不支持的 CPU 特性 USE flag

Gentoo 官方 Binhost 使用的是比较保守的通用编译选项(-march=x86-64),所以大多数 amd64 系统都可以直接使用。

如果你需要针对特定 CPU 优化,可以使用 app-misc/resolve-march-native 来找到构建机和客户端之间兼容的 -march 子集。类似地,app-portage/cpuid2cpuflags 可以帮你找到兼容的 CPU_FLAGS_X86 设置。

新版本的 Stage3 安装包已经预置了 Binhost 配置文件。对于老的安装或自定义场景,需要手动配置。

编辑 /etc/portage/binrepos.conf/gentoobinhost.conf(如果目录不存在则创建):

mkdir -p /etc/portage/binrepos.conf

使用官方源:

# /etc/portage/binrepos.conf/gentoobinhost.conf
[gentoo]
priority = 9999
sync-uri = https://distfiles.gentoo.org/releases/amd64/binpackages/23.0/x86-64/
verify-signature = true

或者使用国内镜像(推荐,速度更快):

# /etc/portage/binrepos.conf/gentoobinhost.conf — 清华源
[gentoo]
priority = 9999
sync-uri = https://mirrors.tuna.tsinghua.edu.cn/gentoo/releases/amd64/binpackages/23.0/x86-64/
verify-signature = true
# /etc/portage/binrepos.conf/gentoobinhost.conf — 阿里云源
[gentoo]
priority = 9999
sync-uri = https://mirrors.aliyun.com/gentoo/releases/amd64/binpackages/23.0/x86-64/
verify-signature = true

参数说明:

  • priority 优先级,数值越高优先级越高。当同一个包在多个 binhost 中都存在时,Portage 会从优先级最高的源拉取
  • sync-uri 指向 binhost 中 Packages 索引文件所在的目录。URL 末尾包含了系统架构和安装类型
  • verify-signature 每仓库签名验证,Portage 3.0.74 引入;3.0.77 起默认就是 true,旧版本才需要显式写

sync-uri 路径中的 23.0/x86-64 对应你的 profile。如果你使用的是 23.0/x86-64-v3 profile(支持 AVX2 等较新指令集),那么路径也需要相应调整为 23.0/x86-64-v3。

官方 binhost 目前只提供 x86-64(基线)和 x86-64-v3 两套,没有 v2/v4。hardened、musl、llvm、x32 等 profile 对应的目录里只有 stage3 自带的那部分包。

在 /etc/portage/make.conf 中启用 getbinpkg feature,让 Portage 自动从 binhost 获取二进制包:

FEATURES="getbinpkg"

如果找不到匹配的二进制包,Portage 会自动回退到从源码编译,不影响正常使用。

FEATURES="getbinpkg" 是全局默认;只想偶尔用一次,命令行加 -g 就行,不必写进 make.conf。两种方式选一种即可,不要同时依赖。

官方 binhost 的包用 Gentoo Release Engineering 的密钥签名。emerge 第一次下载二进制包时会自动调用 getuto,在 /etc/portage/gnupg 建立 keyring,一般不需要手动做什么。

如果 keyring 缺失或损坏,按文末「常见问题」里的步骤处理;正常流程不需要手动跑 getuto。

2026-05 的官方 news 之后,从 binhost 下载的包默认就会校验签名(verify-signature,Portage 3.0.77 起默认开启),官方 binhost 用户不需要再设置 binpkg-request-signature。只有当你希望连本机 buildpkg 编译的包也强制签名验证时,才需要它——它会让 Portage 拒绝任何没有有效签名的包(包括本机刚编译的),所以构建机必须同时开 binpkg-signing:

# /etc/portage/make.conf
FEATURES="getbinpkg binpkg-request-signature"

常见问题:如果之前已经存在 /etc/portage/gnupg 目录(比如早期用于 portage tree 验证的),getuto 不会覆盖它,可能导致配置不正确。此时可以先备份旧目录再重新初始化:

mv /etc/portage/gnupg /etc/portage/gnupg.bak
getuto

配置完成后就可以开始使用二进制包了。

安装单个包(使用二进制包):

emerge -avg app-editors/nano

使用二进制包更新整个系统:

emerge -avuDUg @world

emerge 常用的二进制包相关选项:

选项 简写 说明
--getbinpkg -g 从 binhost 下载二进制包,如果匹配则使用,不匹配则从源码编译
--getbinpkgonly -G 只使用 binhost 上的二进制包,找不到则报错(不回退到源码编译)
--usepkg -k 优先使用本地已有的二进制包(/var/cache/binpkgs),不主动从 binhost 下载
--usepkgonly -K 只使用本地已有的二进制包,不从源码编译

注意:不加 --getbinpkg 时,即使给了 --usepkgonly,Portage 也不会去 binhost 找包。

你可以将常用选项加到 EMERGE_DEFAULT_OPTS 中,避免每次手动指定:

# /etc/portage/make.conf
EMERGE_DEFAULT_OPTS="${EMERGE_DEFAULT_OPTS} --getbinpkg"

有些包不适合使用二进制包(如内核源码、虚拟包等),可以使用 --usepkg-exclude 来排除:

emerge -avuDUg @world --usepkg-exclude "sys-kernel/gentoo-sources virtual/* sys-kernel/gentoo-kernel"

同样可以写到 EMERGE_DEFAULT_OPTS 中:

# /etc/portage/make.conf
EMERGE_DEFAULT_OPTS="${EMERGE_DEFAULT_OPTS} --usepkg-exclude 'sys-kernel/gentoo-sources virtual/*'"

Portage 会检查二进制包的 USE flag 是否与本地配置匹配。如果不匹配,默认行为是忽略二进制包并从源码编译。

如果想查看某个包的二进制版本使用了哪些 USE flag:

emerge --pretend --getbinpkg --usepkgonly --binpkg-respect-use=n <package>

如果你不在意 USE flag 差异(比如只是测试),可以用 --binpkg-respect-use=n 忽略 USE 差异强制安装二进制包。官方文档明确警告这会破坏系统,只建议拿来排查,不要写进 EMERGE_DEFAULT_OPTS 长期使用。

自己创建二进制包是搭建私有 Binhost 的基础。Portage 提供了三种方式来创建二进制包。

在 emerge 安装软件时同时生成二进制包:

# 安装软件的同时创建二进制包
emerge --ask --buildpkg sys-devel/gcc

# 只创建二进制包但不安装到系统(需要编译时依赖已安装)
emerge --ask --buildpkgonly sys-devel/gcc

最常用的方式——在 /etc/portage/make.conf 中启用 buildpkg feature,以后每次 emerge 安装软件时都会自动生成二进制包:

# /etc/portage/make.conf
FEATURES="buildpkg"

buildsyspkg 是一个更保守的选项,只为 @system set 中的包自动创建二进制包。

如果某些包不需要生成二进制包(如内核源码包、上游已经是二进制的包如 firefox-bin),可以排除:

emerge -uDN @world --buildpkg --buildpkg-exclude "acct-*/* sys-kernel/*-sources virtual/* */*-bin"

quickpkg 可以从已安装的包直接打成二进制包,不需要重新编译:

# 为所有已安装的 GCC 版本创建二进制包
quickpkg sys-devel/gcc

# 为 @system set 中所有包创建二进制包
quickpkg @system

# 为系统上所有已安装的包创建二进制包
quickpkg "*/*"

quickpkg 默认不包含配置文件(被 CONFIG_PROTECT 保护的文件)。如果需要包含,可以使用 --include-config 或 --include-unmodified-config 选项。

所有方式创建的二进制包都存储在 PKGDIR 指向的目录,默认为 /var/cache/binpkgs。

二进制包的压缩格式由 BINPKG_COMPRESS 指定,默认就是 zstd,不改的话不用写。支持的格式有:bzip2、gzip、lz4、lzip、lzop、xz、zstd;要换的时候才需要设置,例如 BINPKG_COMPRESS="lz4"。

不同的压缩格式需要安装对应的依赖包。例如使用 lz4 需要安装 app-arch/lz4。zstd 在压缩比和速度之间取得了很好的平衡,推荐使用默认值。

对于需要在多台机器之间分发的二进制包,强烈建议启用 GPG 签名。

在构建机上配置签名:

# /etc/portage/make.conf
FEATURES="buildpkg binpkg-signing"
BINPKG_GPG_SIGNING_GPG_HOME="/root/.gnupg"
BINPKG_GPG_SIGNING_KEY="0xYOURKEYFINGERPRINT"

参数说明:

  • binpkg-signing 启用二进制包签名功能
  • BINPKG_GPG_SIGNING_GPG_HOME 签名用的 GPG home 目录,默认为 /root/.gnupg
  • BINPKG_GPG_SIGNING_KEY 签名密钥的指纹

Portage 只在启动时尝试解锁 GPG 私钥。如果你的密钥可能过期,可以启用 gpg-keepalive 来防止签名失败:

FEATURES="buildpkg binpkg-signing gpg-keepalive"

以下是快速生成 Portage GPG 密钥环的脚本,可以在客户端机器上使用来配置信任关系:

#!/bin/bash
 
KEY_NAME="sa-slchris.asc"
LOCAL_KEYSERVER="http://keyserver.internal:9001"
KEY="5BB4DC20DCA50A499219935036F2D646E2E89820" # 你的签名密钥指纹

GPG_DIR="/etc/portage/gnupg"

PASS="$(openssl rand -base64 32)"

KEY_CONFIG_FILE="$(mktemp)"
chmod 600 "${KEY_CONFIG_FILE}"

export GNUPGHOME="${GPG_DIR}"
cat > "${KEY_CONFIG_FILE}" <<EOF
     %echo Generating Portage local OpenPGP trust key
     Key-Type: default
     Subkey-Type: default
     Name-Real: Portage Local Trust Key
     Name-Comment: local signing only
     Name-Email: portage@localhost
     Expire-Date: 0
     Passphrase: ${PASS}
     %commit
     %echo done
EOF

mkdir -p "${GNUPGHOME}"
gpg --batch --generate-key "${KEY_CONFIG_FILE}"
rm -f "${KEY_CONFIG_FILE}"

touch "${GNUPGHOME}/pass"
chmod 600 "${GNUPGHOME}/pass"
echo "${PASS}" > "${GNUPGHOME}/pass"

# 导入你的签名公钥
curl  $LOCAL_KEYSERVER/$KEY_NAME | gpg --import - 
# 使用本地信任密钥签署你的签名公钥
gpg --batch --yes --pinentry-mode loopback --passphrase "${PASS}" --sign-key "${KEY}" 
# 设置信任等级为 ultimate (5)
echo -e "5\ny\n" | gpg --command-fd 0 --edit-key "${KEY}" trust

chmod ugo+r "${GNUPGHOME}/trustdb.gpg"

这个脚本做了几件事:

  1. 生成一个本地的 Portage 信任密钥(用于签署信任其他密钥)
  2. 从内部密钥服务器下载你的签名公钥
  3. 使用本地信任密钥签署你的签名公钥并设置为完全信任

也可以手动完成上述操作。关键步骤是:getuto 初始化 keyring → gpg --homedir=/etc/portage/gnupg --import public.key 导入公钥 → gpg --homedir=/etc/portage/gnupg --edit-key KEYID 然后依次输入 sign、yes、信任密钥的密码、trust、4、save。最后运行 gpg --homedir=/etc/portage/gnupg --check-trustdb 更新信任数据库。

Portage 支持通过 FTP、FTPS、HTTP、HTTPS、SSH/SFTP 多种协议下载二进制包。下面介绍几种常见的搭建方式。

最常见的方式,适合内网和公网分发。

使用 Nginx 作为文件服务器,提供 /var/cache/binpkgs 目录的 HTTP 访问:

# /etc/nginx/conf.d/binhost.conf
server {
    listen 80;
    server_name binhost.example.com;

    root /var/cache/binpkgs;

    # 开启目录列表,方便浏览
    autoindex on;
    autoindex_exact_size off;
    autoindex_localtime on;

    # 支持大文件传输
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    location / {
        try_files $uri $uri/ =404;
    }
}

客户端配置:

# /etc/portage/binrepos.conf/mybinhost.conf
[mybinhost]
priority = 1
sync-uri = http://binhost.example.com/
verify-signature = true

如果你更喜欢 Caddy 的简洁配置:

# Caddyfile
binhost.example.com:80 {
    root * /var/cache/binpkgs
    file_server browse
}

如果只是临时用一下,Python 自带的 HTTP 服务器也可以:

cd /var/cache/binpkgs
python -m http.server 8000

这个方式仅适合临时测试,不建议在生产环境使用。

适合安全性要求较高的场景,利用 SSH 密钥认证,不需要额外的 Web 服务器。

  1. 在 binhost 服务器上添加客户端 root 用户的 SSH 公钥:
cat root.id_rsa.pub >> /home/binpkguser/.ssh/authorized_keys
  1. 客户端配置:
# /etc/portage/binrepos.conf/ssh.conf
[ssh]
priority = 1
sync-uri = ssh://binpkguser@binhostserver/var/cache/binpkgs
verify-signature = true

SSH 方式需要 root 用户的 SSH 密钥不能设置 passphrase,因为包安装过程需要在后台无交互执行。如果 SSH 服务器使用非标准端口,在地址后面加上端口号即可:ssh://binpkguser@binhostserver:2222/var/cache/binpkgs。

在内网环境中,通过 NFS 共享二进制包目录是最简单的方式:

服务器端 exports:

# /etc/exports
/var/cache/binpkgs 192.168.100.0/24(ro,no_subtree_check,root_squash)

客户端 fstab 挂载:

# /etc/fstab
binhost:/var/cache/binpkgs /opt/nfs-binpkgs nfs defaults 0 0

客户端挂载后,把 PKGDIR 指向挂载点,用 --usepkg(-k)直接读本地目录,不需要 binrepos.conf:

# /etc/portage/make.conf
PKGDIR="/opt/nfs-binpkgs"
# 使用 NFS 挂载的二进制包;找不到再回退到源码编译
emerge -k -uDN @world

这是官方文档里 NFS/SSHFS 挂载 binhost 的用法。--usepkgonly(-K)要求必须找到二进制包。挂载是只读的话,客户端不要再开 buildpkg,否则写不进去。

如果你的 homelab 有 MinIO 或使用 AWS S3,可以通过 s3cmd 或 rclone 将二进制包同步到对象存储,再通过 HTTP(S) 网关提供访问。

上传脚本示例:

#!/bin/bash
# sync-binpkgs-to-s3.sh
# 将本地二进制包同步到 S3 兼容存储

BUCKET="my-binpkgs"
PKGDIR="/var/cache/binpkgs"

# 使用 rclone 同步(需要提前配置好 rclone remote)
rclone sync "${PKGDIR}/" "minio:${BUCKET}/" \
    --transfers=8 \
    --checkers=16 \
    --fast-list \
    --delete-during \
    -v

echo "Sync completed at $(date)"

对于包数量不多的场景(如个人 overlay 编译的包),也可以使用 Git 仓库来存储分发:

# 初始化仓库
cd /var/cache/binpkgs
git init
git checkout -b x86_64-pc-linux-gnu

# 添加并提交
git add .
git commit -m "binpkgs: $(date +%Y-%m-%d) update"

# 推送到远程
git remote add origin [email protected]:yourname/binpkgs.git
git push --set-upstream origin x86_64-pc-linux-gnu

注意:Git 不适合存储大量或体积较大的二进制文件。如果包数量较多,建议使用 Git LFS 或上面提到的其他方式。这个方案更适合几十个自定义编译的包、overlay 中的包等小规模场景。

客户端不能直接把 git 地址写进 sync-uri。先把仓库 clone 到本地,再指向那个目录:

# /etc/portage/binrepos.conf/git.conf
[git-binhost]
priority = 1
sync-uri = file:///var/cache/binhost/git
verify-signature = true

也可以用任意静态 HTTP 服务把这个目录发布出去,sync-uri 写成对应的 URL。

随着时间推移,旧版本的二进制包会持续累积占用存储空间。使用 eclean(来自 app-portage/gentoolkit)来清理不再对应任何 ebuild 的过时二进制包:

eclean-pkg   # 等价于 eclean packages

新版 Portage 会把从 binhost 下载的包放在 /var/cache/binhost/<repo>,和本地 buildpkg 生成的 /var/cache/binpkgs 分开,eclean-pkg 会一起清理。

也可以使用 app-portage/portage-utils 中的 qpkg 工具来清理未使用的二进制包:

qpkg -c

在 /var/cache/binpkgs 目录下有一个 Packages 索引文件,它是所有二进制包元数据的缓存。正常情况下 Portage 和 eclean 会自动维护这个文件。

如果因为手动操作(直接删除或拷贝二进制包文件)导致索引文件不一致,可以使用 emaint 重建:

emaint binhost --fix

如果需要完全清空所有二进制包缓存:

rm -r /var/cache/binpkgs/*

在 homelab 环境中,你可能有多台构建节点分别负责编译不同的包(比如一台编译桌面相关的包,另一台编译服务器端的包),最终需要把它们的二进制包合并到一个统一的 Binhost 上进行分发。

合并的核心很简单:将不同节点的 /var/cache/binpkgs 目录合并到同一个目录中,然后重建 Packages 索引文件。由于 binpkg 的目录结构是 category/package/package-version-build_id.gpkg.tar,不同包之间不会产生路径冲突。

graph LR
    subgraph 构建节点
        A["build-node-1
/var/cache/binpkgs"] B["build-node-2
/var/cache/binpkgs"] C["build-node-3
/var/cache/binpkgs"] end subgraph 中央 Binhost D["中央 PKGDIR
/var/cache/binpkgs"] E["emaint binhost --fix
重建 Packages 索引"] F["Web/SSH/NFS
对外分发服务"] end A -->|rsync| D B -->|rsync| D C -->|rsync| D D --> E --> F

最直接的方式是用 rsync 从各个构建节点拉取二进制包到中央 Binhost:

#!/bin/bash
# merge-binpkgs.sh
# 从多个构建节点合并二进制包到中央 Binhost

CENTRAL_PKGDIR="/var/cache/binpkgs"
LOG="/var/log/binpkg-merge.log"

# 构建节点列表
BUILD_NODES=(
    "root@build-node-1"
    "root@build-node-2"
    "root@build-node-3"
)

echo "=== Binpkg merge started at $(date) ===" >> "${LOG}"

for node in "${BUILD_NODES[@]}"; do
    echo "Syncing from ${node}..." >> "${LOG}"
    rsync -avz --ignore-existing \
        "${node}:/var/cache/binpkgs/" \
        "${CENTRAL_PKGDIR}/" \
        >> "${LOG}" 2>&1
done

# 重建索引文件
emaint binhost --fix >> "${LOG}" 2>&1

echo "=== Binpkg merge finished at $(date) ===" >> "${LOG}"

参数说明:

  • --ignore-existing 如果中央 Binhost 上已经有了同名同版本的包,就不覆盖。这在多个节点可能编译了同一个包的情况下很有用
  • -avz 归档模式 + 压缩传输,保留文件权限和时间戳

如果你希望总是用最新编译的版本覆盖旧的,可以将 --ignore-existing 替换为 --update(只在源文件比目标文件新的时候覆盖)。

当多个节点编译了同一个包的同一个版本时,可能存在以下差异:

  • USE flag 不同:比如节点 A 编译 dev-libs/openssl 带了 USE="-bindist",节点 B 带了 USE="bindist"
  • CFLAGS 不同:比如节点 A 用了 -march=x86-64-v3,节点 B 用了 -march=x86-64

Portage 3.0.15 起默认启用 binpkg-multi-instance,每个包文件名中包含了 build_id,因此同一个包的不同构建实例可以共存。Portage 会根据 USE flag 匹配来选择合适的版本。

但为了避免混乱,建议各构建节点使用统一的 make.conf 配置(至少 CFLAGS、CXXFLAGS、CPU_FLAGS_X86 保持一致),通过 Ansible 或其他配置管理工具来确保一致性。

以下是一个更实用的合并脚本,支持选择性合并和冲突检测:

#!/bin/bash
# advanced-merge-binpkgs.sh

CENTRAL_PKGDIR="/var/cache/binpkgs"
LOG="/var/log/binpkg-merge.log"
LOCK_FILE="/var/lock/binpkg-merge.lock"

# 防止并发执行
exec 200>"${LOCK_FILE}"
flock -n 200 || { echo "Another merge is running, exiting."; exit 1; }

# 构建节点配置:节点地址 + 负责的包类别
declare -A NODE_CATEGORIES
NODE_CATEGORIES[
    "root@build-desktop"]="x11-*/ kde-*/ gnome-*/ media-*/ www-client/"
NODE_CATEGORIES[
    "root@build-server"]="sys-*/ net-*/ app-containers/ dev-db/"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "${LOG}"
}

log "=== Merge started ==="

for node in "${!NODE_CATEGORIES[@]}"; do
    categories="${NODE_CATEGORIES[$node]}"
    log "Syncing from ${node} (categories: ${categories})"
    
    # 按类别选择性同步
    for cat in ${categories}; do
        rsync -avz --update \
            "${node}:/var/cache/binpkgs/${cat}" \
            "${CENTRAL_PKGDIR}/" \
            >> "${LOG}" 2>&1
    done
done

# 重建 Packages 索引
log "Rebuilding Packages index..."
emaint binhost --fix >> "${LOG}" 2>&1

# 统计
PKG_COUNT=$(find "${CENTRAL_PKGDIR}" -name '*.gpkg.tar' | wc -l)
DIR_SIZE=$(du -sh "${CENTRAL_PKGDIR}" | awk '{print $1}')
log "=== Merge finished: ${PKG_COUNT} packages, total size: ${DIR_SIZE} ==="

如果各构建节点使用不同的 GPG 密钥签名二进制包,那么客户端的 /etc/portage/gnupg 需要信任所有构建节点的签名密钥。

一种更简洁的方案是:各节点不做签名,合并到中央 Binhost 后统一由中央节点重新签名。可以使用 gpkg-sign 工具对已有的 GPKG 包进行签名:

# 只签未签名的包,已有的跳过
find /var/cache/binpkgs -name '*.gpkg.tar' \
  | xargs -n 1 -P "$(nproc)" gpkg-sign --skip-signed --allow-unsigned

# 签名后重建索引
emaint binhost --fix

gpkg-sign 需要 Portage 的 BINPKG_GPG_SIGNING_GPG_HOME 和 BINPKG_GPG_SIGNING_KEY 已正确配置。这样客户端只需要信任中央 Binhost 的一把密钥即可。

在聊完了二进制包之后,还有一个问题:对于那些 binhost 上找不到、必须从源码编译的包(比如你自定义了 USE flag 或使用了非标准的 -march),有没有办法加速编译?答案是 distcc——分布式编译工具。

distcc 可以把编译任务分发到网络中的多台机器上并行执行,结合 binpkg 一起使用的思路是:能用二进制包的直接用,用不了的通过 distcc 集群加速编译。这在 homelab 有多台机器的场景下效果非常好。

使用 distcc 有几个硬性要求:

  • 所有节点的 GCC 版本必须一致:版本不一致会导致编译错误或运行时崩溃。使用 gcc --version 确认
  • 所有节点的 binutils 版本必须一致:可以用 eselect binutils list 检查。版本不一致会导致链接失败
  • 不能使用 -march=native:distcc 会把编译任务分发到不同的机器上,-march=native 会根据当前机器的 CPU 自动选择指令集,导致在不同 CPU 上编译出不兼容的代码。必须使用显式的 -march 值(如 -march=x86-64 或 -march=znver3)

可以使用 app-misc/resolve-march-native 来查看 -march=native 在你的 CPU 上会展开成什么具体的 flags,然后选择集群中所有机器都兼容的子集。

在所有参与编译的机器上安装 distcc:

emerge -av sys-devel/distcc

如果想使用图形化监控界面,可以启用 gui USE flag:echo 'sys-devel/distcc gui' >> /etc/portage/package.use/distcc。

在所有参与编译的节点(包括编译发起方)上配置并启动 distccd 服务。

Systemd 环境下,编辑 drop-in 配置文件设置允许的客户端 IP:

mkdir -p /etc/systemd/system/distccd.service.d
# /etc/systemd/system/distccd.service.d/00gentoo.conf
[Service]
Environment="ALLOWED_SERVERS=192.168.100.0/24"

如果有多个子网或需要更细粒度的控制:

Environment="ALLOWED_SERVERS=192.168.100.0/24 --allow 10.0.0.0/8 --log-level error"

启用并启动服务:

systemctl daemon-reload
systemctl enable --now distccd

在发起编译的机器上配置 distcc 的主机列表:

# 设置参与分布式编译的主机列表
# 格式:IP[/最大并发数]
/usr/bin/distcc-config --set-hosts "localhost/4 192.168.100.11/8 192.168.100.12/8"

参数说明:

  • localhost/4 本机参与编译,最多 4 个并发编译任务。如果本机性能较弱可以降低或移除
  • 192.168.100.11/8 远程节点,最多 8 个并发任务。根据远程机器的 CPU 核心数调整
  • 没有指定 /N 的话默认为 4

Pump 模式可以进一步加速,它在服务端缓存预处理后的头文件,减少重复传输。配置方式是在主机后面加上 ,cpp,lzo 后缀:

/usr/bin/distcc-config --set-hosts "localhost/4,cpp,lzo 192.168.100.11/8,cpp,lzo"

编辑 /etc/portage/make.conf,启用 distcc feature 并调整 MAKEOPTS:

# /etc/portage/make.conf

# 启用 distcc(和 binpkg 不冲突,可以同时启用)
FEATURES="distcc buildpkg getbinpkg"

# MAKEOPTS 计算公式:
# -jN : N = 2 × (本地核心数 + 远程总核心数) + 1
# -lM : M = 本地 CPU 核心数(防止本地过载)
#
# 示例:本机 4 核 + 2 台远程各 8 核 = 总共 20 核
# N = 2 × 20 + 1 = 41, M = 4
MAKEOPTS="-j41 -l4"

# 重要:不要使用 -march=native!
COMMON_FLAGS="-march=x86-64-v3 -O2 -pipe"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"

-lM 参数非常重要——当某些远程节点不可用时,它会限制本机的最大负载,防止所有任务都堆积在本地导致系统卡死。

有些包不适合使用 distcc(如 Rust 包会导致过多的本地 I/O),需要单独处理:

# /etc/portage/env/nodistcc.conf
MAKEOPTS="-j5"
FEATURES="-distcc"
# /etc/portage/package.env/nodistcc
dev-lang/rust nodistcc.conf
www-client/firefox nodistcc.conf
mail-client/thunderbird nodistcc.conf
sys-libs/libcxx nodistcc.conf

distcc 和 ccache 可以同时使用,形成双重加速:ccache 在本地缓存已处理过的编译结果,distcc 将缓存未命中的任务分发到远程节点。远程节点上也可以配置 ccache,这样远程的编译结果同样会被缓存。

工作流程如下:

flowchart TD
    A["客户端发起编译"] --> B{"本地 ccache
缓存命中?"} B -->|命中| C["直接使用缓存
不走网络"] B -->|未命中| D["distcc 分发到远程节点"] D --> E{"远程 ccache
缓存命中?"} E -->|命中| F["返回远程缓存结果"] E -->|未命中| G["远程编译并缓存"] G --> H["返回编译结果到客户端"] F --> H style C fill:#2d8659,color:#fff style F fill:#2d8659,color:#fff style G fill:#c9841e,color:#fff

配置步骤:

# /etc/portage/make.conf
FEATURES="distcc ccache buildpkg getbinpkg"
CCACHE_DIR="/var/cache/ccache"

在远程节点的 distccd 配置中也要让 ccache 介入:

# /etc/systemd/system/distccd.service.d/00gentoo.conf
[Service]
Environment="ALLOWED_SERVERS=192.168.100.0/24"
Environment="CCACHE_DIR=/var/cache/ccache"
Environment="DISTCC_DIR=/var/tmp/portage/.distcc"

同时需要修改 distccd 的 ExecStart,让它通过 ccache 路径调用编译器:

systemctl edit --full distccd.service

修改 ExecStart 为:

ExecStart=/bin/bash -c "PATH=/usr/lib/ccache/bin:$PATH exec /usr/bin/distccd --no-detach --daemon --port 3632 -N 15 --allow $ALLOWED_SERVERS"

确保远程节点的 /var/cache/ccache 目录权限正确:

chown -R distcc:portage /var/cache/ccache

distcc 自带监控工具,可以实时查看编译任务的分发情况:

# 文字模式(每 5 秒刷新)
DISTCC_DIR="/var/tmp/portage/.distcc/" distccmon-text 5

# 图形模式(需要 gui USE flag)
DISTCC_DIR="/var/tmp/portage/.distcc/" distccmon-gnome

把 distcc 和 binpkg 结合起来,整个编译和分发的工作流变成了这样:

flowchart TD
    A["emerge 安装/更新包"] --> B{"binhost 上有
匹配的二进制包?"} B -->|有| C["下载二进制包
直接安装"] B -->|没有| D["从源码编译"] D --> E["distcc 将编译任务
分发到集群"] E --> F["本地 + 远程节点
并行编译"] F --> G["编译完成"] G --> H["buildpkg 自动生成
二进制包"] H --> I["binpkg-signing
GPG 签名"] I --> J["同步到中央 Binhost"] J --> K["其他节点下次可
直接使用二进制包"] C --> L["安装完成"] K --> L style C fill:#2d8659,color:#fff style L fill:#2d8659,color:#fff style E fill:#2b7ab5,color:#fff style F fill:#2b7ab5,color:#fff

构建机的完整 make.conf 示例:

# /etc/portage/make.conf — distcc + binpkg 构建机

# 编译选项(不要用 -march=native!)
COMMON_FLAGS="-march=x86-64-v3 -O2 -pipe"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"

# 同时启用所有加速特性
FEATURES="distcc ccache buildpkg binpkg-signing gpg-keepalive getbinpkg"

# MAKEOPTS:根据集群总核心数计算
# 本地 8 核 + 远程 2 × 16 核 = 40 核
# N = 2 × 40 + 1 = 81, M = 8
MAKEOPTS="-j81 -l8"

# ccache
CCACHE_DIR="/var/cache/ccache"

# 二进制包
BINPKG_GPG_SIGNING_GPG_HOME="/root/.gnupg"
BINPKG_GPG_SIGNING_KEY="0xYOURKEYFINGERPRINT"

# 排除不适合 distcc 的包和不需要二进制包的内容
EMERGE_DEFAULT_OPTS="${EMERGE_DEFAULT_OPTS} --usepkg-exclude 'sys-kernel/gentoo-sources virtual/*'"

Tips:在实际使用中,一开始可以先用保守一些的 -jN 值(比如总核心数的 1.5 倍),观察 distccmon-text 的分发情况和各节点的负载后再逐步调高。过高的并发数反而可能因为网络和 I/O 开销导致效率下降。

在 homelab 环境中,建议设置一台专门的构建机(build host),自动编译和分发二进制包。

# /etc/portage/make.conf — 构建机

# 编译优化(根据你的 CPU 调整)
COMMON_FLAGS="-march=x86-64 -mtune=generic -O2 -pipe"
CFLAGS="${COMMON_FLAGS}"
CXXFLAGS="${COMMON_FLAGS}"

# 自动为所有安装的包生成二进制包
FEATURES="buildpkg binpkg-signing gpg-keepalive"

# GPG 签名配置
BINPKG_GPG_SIGNING_GPG_HOME="/root/.gnupg"
BINPKG_GPG_SIGNING_KEY="0xYOURKEYFINGERPRINT"

# 压缩格式

构建机的 -march 应设置为所有客户端都支持的最低指令集。如果你的 homelab 中所有机器都是较新的 CPU,可以使用 x86-64-v3(支持 AVX2)来获得更好的性能。

创建一个 cron 脚本来自动更新系统并同步二进制包:

#!/bin/bash
# /root/scripts/binhost-update.sh

LOG="/var/log/binhost-update.log"

echo "=== Binhost update started at $(date) ===" >> "${LOG}"

# 同步 portage tree
emaint sync --auto >> "${LOG}" 2>&1

# 更新系统(自动创建二进制包)
emerge -uDN @world --with-bdeps=y --keep-going >> "${LOG}" 2>&1

# 清理过时的二进制包
eclean packages >> "${LOG}" 2>&1

# 重建索引
emaint binhost --fix >> "${LOG}" 2>&1

# 可选:清理过时的源码包
eclean distfiles >> "${LOG}" 2>&1

echo "=== Binhost update finished at $(date) ===" >> "${LOG}"

配置 cron 定时执行:

# crontab -e
# 每天凌晨3点执行更新
0 3 * * * /root/scripts/binhost-update.sh

以下是一个客户端机器的完整配置示例,同时使用私有 binhost 和 Gentoo 官方 binhost:

# /etc/portage/make.conf

# 签名验证默认开启,不用再写 binpkg-request-signature
FEATURES="getbinpkg"

# 使用二进制包时排除内核和虚拟包
EMERGE_DEFAULT_OPTS="${EMERGE_DEFAULT_OPTS} --usepkg-exclude 'sys-kernel/gentoo-sources virtual/*'"
# /etc/portage/binrepos.conf/private.conf — 私有 Binhost(优先)
[private]
priority = 10000
sync-uri = https://binhost.homelab.local/
verify-signature = true
# /etc/portage/binrepos.conf/gentoo.conf — 官方 Binhost(备选)
[gentoo]
priority = 9999
sync-uri = https://mirrors.tuna.tsinghua.edu.cn/gentoo/releases/amd64/binpackages/23.0/x86-64/
verify-signature = true

配置了多个 binhost 后,Portage 会按 priority 从高到低查找二进制包。私有 binhost 的 priority 设高一些,这样会优先使用自己编译并签名的包;如果私有 binhost 上找不到,再 fallback 到官方源。

注意:从 Portage 3.0.77 起,下载的 binpkg 默认要验签。私有 binhost 如果还没签名,这里会下载失败——要么先按上面的步骤签好,要么把这一条 repo 的 verify-signature 改成 false。

homelab 里几台机器,前面那套就够了。机器多到几十台时,真正需要额外做的只有三件事:版本一致、离线可控、能回退。

不要依赖"当天同步"这种隐式版本。Gentoo 的 tree 是 Git 仓库,直接锁 commit:

git clone https://github.com/gentoo-mirror/gentoo.git /var/db/repos/gentoo
git -C /var/db/repos/gentoo checkout <commit>

官方镜像仓库没有按日期打 tag,git tag -l 'gentoo-*' 不会有结果,锁 commit hash 最直接。想要可读的版本号,就在自己的内部镜像上打 tag,例如 corp-20261112。

然后在 /etc/portage/repos.conf/gentoo.conf 里关掉自动同步:

[gentoo]
location = /var/db/repos/gentoo
auto-sync = no
sync-type = git
sync-uri = https://github.com/gentoo-mirror/gentoo.git

所有节点用同一个 commit,升级 tree 当作一次变更:先在 staging 验证,再推给生产。

离线环境或出口受限时,先把源码包拉到本地:

# 只拉当前 @world 需要的
emerge --fetchonly -uDN @world --with-bdeps=y

# 或者镜像整个仓库
emirrordist --mirror --distfiles /srv/distfiles --repo gentoo \
    --jobs 8 --delete --deletion-delay 2592000

客户端把 GENTOO_MIRRORS 指向内部镜像;完全离线的环境可以用 U 盘把 distfiles 拷进去。

更新 tree → staging 编译/验证 → canary 节点观察 24h → 全量推送
  • 在 staging 上用 emerge -uDN @world --buildpkg --keep-going 生成包,验证通过后再同步到 binhost。
  • canary 阶段先给 1-2 台节点装,观察 systemctl is-system-running 和关键服务。
  • 回滚有三条路:tree 退回上一个 commit、用保留的旧 binpkg emerge -avK @world、ZFS 直接 zfs rollback。
  • 安全更新用 glsa-check --test all 检查、glsa-check --fix all 修复,放进定时任务即可。

从简单开始:先做「锁 tree + binhost + 每天定时构建」,跑稳了再加 canary 和审批。

最常见的原因是 USE flag 不匹配。Binhost 上的二进制包使用的 USE flag 和你本地配置的不同时,Portage 会自动回退到源码编译。可以通过以下命令检查差异:

emerge --pretend --getbinpkg --usepkgonly --binpkg-respect-use=n <package>

可以直接在浏览器中访问 binhost 的 URL,查看对应类别目录下是否有你需要的包。

/etc/portage/gnupg/ 中的 keyring 需要先生成。如果该目录不存在,运行 getuto 即可。如果已存在但有问题,先备份再重新生成:

mv /etc/portage/gnupg /etc/portage/gnupg.bak
getuto