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命令解包查看内容,不需要专用工具。
使用官方 Binhost
前提条件
使用二进制包有几个硬性要求,系统之间必须满足以下兼容条件:
- 架构和 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设置。
配置 Binhost 源
新版本的 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-v3profile(支持 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。两种方式选一种即可,不要同时依赖。
GPG 签名验证
官方 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/*'"
USE flag 不匹配的处理
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 提供了三种方式来创建二进制包。
方式一:使用 –buildpkg 参数
在 emerge 安装软件时同时生成二进制包:
# 安装软件的同时创建二进制包
emerge --ask --buildpkg sys-devel/gcc
# 只创建二进制包但不安装到系统(需要编译时依赖已安装)
emerge --ask --buildpkgonly sys-devel/gcc
方式二:通过 FEATURES 自动构建
最常用的方式——在 /etc/portage/make.conf 中启用 buildpkg feature,以后每次 emerge 安装软件时都会自动生成二进制包:
# /etc/portage/make.conf
FEATURES="buildpkg"
buildsyspkg是一个更保守的选项,只为@systemset 中的包自动创建二进制包。
如果某些包不需要生成二进制包(如内核源码包、上游已经是二进制的包如 firefox-bin),可以排除:
emerge -uDN @world --buildpkg --buildpkg-exclude "acct-*/* sys-kernel/*-sources virtual/* */*-bin"
方式三:使用 quickpkg
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在压缩比和速度之间取得了很好的平衡,推荐使用默认值。
OpenPGP 签名
对于需要在多台机器之间分发的二进制包,强烈建议启用 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/.gnupgBINPKG_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"
这个脚本做了几件事:
- 生成一个本地的 Portage 信任密钥(用于签署信任其他密钥)
- 从内部密钥服务器下载你的签名公钥
- 使用本地信任密钥签署你的签名公钥并设置为完全信任
也可以手动完成上述操作。关键步骤是:
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更新信任数据库。
搭建 Binhost 服务器
Portage 支持通过 FTP、FTPS、HTTP、HTTPS、SSH/SFTP 多种协议下载二进制包。下面介绍几种常见的搭建方式。
基于 Web 服务器
最常见的方式,适合内网和公网分发。
Nginx
使用 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
如果你更喜欢 Caddy 的简洁配置:
# Caddyfile
binhost.example.com:80 {
root * /var/cache/binpkgs
file_server browse
}
Python http.server(临时方案)
如果只是临时用一下,Python 自带的 HTTP 服务器也可以:
cd /var/cache/binpkgs
python -m http.server 8000
这个方式仅适合临时测试,不建议在生产环境使用。
基于 SSH
适合安全性要求较高的场景,利用 SSH 密钥认证,不需要额外的 Web 服务器。
- 在 binhost 服务器上添加客户端 root 用户的 SSH 公钥:
cat root.id_rsa.pub >> /home/binpkguser/.ssh/authorized_keys
- 客户端配置:
# /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
在内网环境中,通过 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,否则写不进去。
基于 S3 / 对象存储
如果你的 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)"
基于 GitHub/Gitea
对于包数量不多的场景(如个人 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
维护 Packages 索引文件
在 /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 合并
最直接的方式是用 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 签名的合并
如果各构建节点使用不同的 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 的一把密钥即可。
结合 distcc 分布式编译
在聊完了二进制包之后,还有一个问题:对于那些 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
在所有参与编译的机器上安装 distcc:
emerge -av sys-devel/distcc
如果想使用图形化监控界面,可以启用
guiUSE flag:echo 'sys-devel/distcc gui' >> /etc/portage/package.use/distcc。
配置 distccd 守护进程
在所有参与编译的节点(包括编译发起方)上配置并启动 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"
配置 Portage 使用 distcc
编辑 /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
有些包不适合使用 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
结合 ccache 使用
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 完整工作流
把 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),自动编译和分发二进制包。
构建机 make.conf 配置
# /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 里几台机器,前面那套就够了。机器多到几十台时,真正需要额外做的只有三件事:版本一致、离线可控、能回退。
锁定 portage tree
不要依赖"当天同步"这种隐式版本。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 验证,再推给生产。
预先拉取 distfiles
离线环境或出口受限时,先把源码包拉到本地:
# 只拉当前 @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 和审批。
常见问题
Q: 为什么 Portage 还是在从源码编译?
最常见的原因是 USE flag 不匹配。Binhost 上的二进制包使用的 USE flag 和你本地配置的不同时,Portage 会自动回退到源码编译。可以通过以下命令检查差异:
emerge --pretend --getbinpkg --usepkgonly --binpkg-respect-use=n <package>
Q: 如何判断某个包是否有二进制版本?
可以直接在浏览器中访问 binhost 的 URL,查看对应类别目录下是否有你需要的包。
Q: keyblock resource: '/etc/portage/gnupg/pubring.kbx': No such file or directory
/etc/portage/gnupg/ 中的 keyring 需要先生成。如果该目录不存在,运行 getuto 即可。如果已存在但有问题,先备份再重新生成:
mv /etc/portage/gnupg /etc/portage/gnupg.bak
getuto
相关阅读
参考文档
- Gentoo goes Binary! — 官方公告
- Binary package guide — Gentoo Wiki
- Binary package quickstart — Gentoo Wiki
- GLEP 78: Gentoo binary package container format
- Gentoo Binhost 可用包和配置
- Portage Profiles — Gentoo Wiki
- Creating an ebuild repository — Gentoo Wiki
- Ebuild repository — Gentoo Wiki
- GLSA (Gentoo Linux Security Advisories)
- Distcc — Gentoo Wiki
- Ccache — Gentoo Wiki
- Eclean — Gentoo Wiki
- Inlining -march=native for distcc
如果你觉得这篇文章对你有所帮助,欢迎赞赏~
赞赏