同一套软件源,一台升到 9.2、一台卡死 9.1:揪出让 PVE 更新“静默失效”的假钥匙
先说结论
手头有两台 Proxmox VE 9 的物理节点,软件源配置一模一样(同一个国内镜像站的
pve-no-subscription源)。一台早已升到最新的 9.2.18,另一台却死活只能升到 9.1.9,而且这样一卡就是四个多月,期间没有任何报错提醒。排查下来,根因既不是网络、也不是源配置,而是那台机器上的 GPG 密钥环文件
/etc/apt/trusted.gpg.d/proxmox-release-trixie.gpg从一开始就存错了内容——里面装的不是一个"公钥",而是一段"签名",导致 Debian 13 的新一代验签器每次都拒绝刷新 PVE 源的软件包索引,然后安安静静地继续用四个月前的旧索引。修复只需要三步:备份坏文件 → 换上正确的官方公钥 → 重新
apt update。不需要重装系统,不需要动软件源,全程 5 分钟。本文完整复盘追凶过程,并给出 Windows 11 / Ubuntu 26.04 / macOS 26 三个平台的一键修复脚本,以及「人工执行」和「交给 Agent 自动配置」两种用法。
图 1:原创封面图。同一套软件源,两台机器却活在两个版本世界里,真凶是密钥环里的一把"假钥匙"。
一、问题背景:一次平平无奇的"版本对齐"
我的家庭实验室里跑着两台 PVE 物理节点,下面挂着20来个虚拟机,承担着软路由、数据库、构建机、反向代理等日常角色。两台机器当初都是 PVE 9.0 时代装的系统,软件源统一指向同一个国内镜像站的 pve-no-subscription 免费源——这个配置在之前的文章里也提过,属于家庭实验室的标准操作。
最近打算给两台机器做一次版本对齐,然后顺手把能升的虚拟机都升一遍。结果出现了非常诡异的一幕:
- 节点 B:Web UI 里点「更新」→「刷新」,列表里赫然躺着 9.2.18,一键升级,天下太平;
- 节点 A:同样的操作,翻遍更新列表,最高就是 9.1.9。命令行里
apt full-upgrade也一样——系统拍着胸脯告诉你"已是最新,无需升级"(0 upgraded, 0 newly installed)。
第一反应是"这台机器的源肯定配得不一样"。毕竟两台机器是不同时间装的,人肉配置难免有出入。于是有了下面这场历时不到半小时的追凶。
二、问题表现:同源不同命
先把两台机器的差异可视化一下,方便大家建立直觉:
图 2:问题全景。同样的软件源,节点 B 能看到 9.2.18,节点 A 的天花板却被死死焊在 9.1.9。
2.1 版本差了整整一个系列
两台机器分别执行 pveversion,输出对比一目了然:
图 3:真实命令输出。节点 A 停在 9.1.9(内核还是 6.17 系列),节点 B 已经是 9.2.18(内核 7.0 系列)。
2.2 apt 认为 9.1.9 就是"最新版"
更关键的是节点 A 上 apt-cache policy pve-manager 的输出:
图 4:真实命令输出(有删节)。注意 Candidate(候选版本)也是 9.1.9,而且版本列表里连 9.1.10 都不存在。
这里科普一下 apt-cache policy 怎么读:Installed 是已经装了的版本,Candidate 是 apt 认为可以升级到的版本。Candidate 停在 9.1.9,意味着 apt 的"进货清单"里压根没有 9.2 系列的记录——问题不在"装"这个环节,而在"看见"这个环节:apt 根本不知道世界上存在 9.2.18。
顺带把常见猜测当场排除:
| 猜测 | 验证方式 | 结论 |
|---|---|---|
| 源配得不一样? | diff 两台机器的 sources.list 和 sources.list.d/ |
完全相同 |
| 镜像站不同步? | 两台机器指向同一个镜像站,B 却能看到 9.2.18 | 排除 |
| 版本被 pin 住? | apt-cache policy 无 pin 记录,/etc/apt/preferences 干干净净 |
排除 |
| 磁盘满/锁文件? | df -h 正常,无 apt 锁 |
排除 |
三、问题分析:时间戳是第一案发现场
排除法走到尽头,换个角度:既然 apt"看不见"新版本,那就看看它的"进货清单"最后一次更新是什么时候。apt 的索引缓存都躺在 /var/lib/apt/lists/ 里,文件的时间戳不会说谎:
图 5:真实命令输出(文件名有删节)。节点 A 的 PVE 索引停留在 2026-04-25,节点 B 的则是 9 月刚刷新的。
真相的第一层浮出水面:节点 A 的 PVE 软件包索引,已经四个多月没有刷新成功过了。而 2026 年 4 月下旬,恰好就是 9.1.9 发布的时间点——从那以后 PVE 源对这台机器来说就是"查无此人"。
那么问题来了:索引刷新失败,为什么没人知道?手动跑一次 apt-get update 看看:
图 6:真实命令输出。Err: 指向 InRelease 验签失败,缺的钥匙指纹是 24B30F06…,但整条命令只有 W: 警告,“正常"退出。
这段报错的信息量非常大,逐句翻译一下:
Sub-process /usr/bin/sqv returned an error code (1)——sqv是 Debian 13(Trixie)起 apt 采用的新一代签名验证器,来自 Sequoia-PGP 项目,用来替代老旧的gpgv;Missing key 24B30F06ECC1836A4E5EFECBA7BCD1420BFE778E, which is needed to verify signature——验证器在自己的"钥匙串"里找不到能核对 PVE 源签名的公钥;The repository is not updated and the previous index files will be used——这条是灵魂:仓库索引拒绝更新,继续用旧的凑合。
最阴险的是最后一行注释里说的事:整个过程只有 W: 开头的警告,没有任何 E: 级别的错误,命令退出一团和气。如果你的 cron 里挂着自动 apt update && apt upgrade,它永远"成功”,永远升不了级,永远不吵不闹。
3.1 那把"缺失的钥匙"到底缺在哪?
apt 核对签名用的公钥,按这台机器的源配置方式,应该住在 /etc/apt/trusted.gpg.d/proxmox-release-trixie.gpg 这个文件里。把两台机器的这个文件并排一验,真凶当场落网:
图 7:真实命令输出。节点 A 的"密钥环"竟然是一段 -----BEGIN PGP SIGNATURE----- 开头的签名块;节点 B 的才是正经的 OpenPGP 公钥。
看到这里整件事就通了:当初给节点 A 配置 PVE 源的时候,下载密钥这一步拿错了文件——把仓库里的"签名文件"当成了"公钥文件"存进了密钥目录。签名块不是公钥,验签器读不懂,自然每次都报"缺钥匙"。而文件的时间戳恰好是 4 月 26 日——也就是说这台机器从 4 月底某次"看起来成功的换源操作"之后,就再也没能从 PVE 源拿到过任何更新。
四、问题根因:一次验签失败,如何静默锁死版本
把整个链条画出来,就是一个"静默降级"的经典案例:
图 8:apt 的一次验签之旅。钥匙环里没有对应公钥时,sqv 拒绝刷新索引,只留下一行警告。
如果觉得 GPG、密钥环这些词太抽象,用一个餐厅的比喻,小学生也能听懂:
图 9:餐厅比喻。钥匙环 = 官方印鉴样本册;密钥环损坏 = 样本册里塞了废纸,怎么比对都对不上。
对应到技术细节:
- 软件源(仓库) 每次更新都会发布一份
InRelease索引清单,内含全部软件包版本信息,并附带开发商的 GPG 签名——相当于"盖了防伪公章的菜单"; - 密钥环(keyring) 是本地存放"官方公章样本"的册子,
trusted.gpg.d目录下的每个文件都会被当作样本收进去; - 验签器(sqv) 每次刷新索引时,拿菜单上的公章和样本册比对,对不上就拒收;
- 静默回退:拒收后 apt 不会报错中断,而是继续使用上一次成功时的旧索引——这就是"四个多月无人察觉"的全部秘密。
根因一句话总结:节点 A 的 PVE 密钥环文件内容错误(签名块冒充公钥)→ 每次索引刷新验签失败 → apt 长期使用 4 月的陈旧索引 → 版本天花板被锁死在 9.1.9。
五、如何解决:三步换钥匙,5 分钟搞定
修法的思路极其朴素:把正确的公钥放回密钥环,然后重新刷新索引。关键是"正确的公钥"从哪来。实测两条路都通,按优先级推荐:
5.1 修法一(推荐):用官方密钥包恢复,不依赖任何外网直连
PVE 源里自带一个官方包 proxmox-archive-keyring,里面装着全套现行有效签发公钥。而且 apt 安装软件包时只做校验和验证(用的是本地已有索引),不依赖即时联网到国外官方站——这对访问 download.proxmox.com 困难的网络环境非常友好:
图 10:真实命令输出。官方密钥包内的指纹清单里,赫然躺着报错信息点名要的 24B30F06…。
apt-get install --reinstall -y proxmox-archive-keyring
install -m 0644 /usr/share/keyrings/proxmox-archive-keyring.gpg \
/etc/apt/trusted.gpg.d/proxmox-release-trixie.gpg
apt-get update
5.2 修法二(备选):从同版本健康节点复制
如果局域网里有一台源配置相同的健康机器(我的场景恰好就有),直接把它的密钥文件拷过来也是完全等价的——两台机器用的是同一个源,公钥必然相同:
图 11:真实修复过程。备份坏文件 → 从健康节点拷贝正确密钥 → 刷新索引 → 候选版本立刻从 9.1.9 跳到 9.2.18。
两条修法共同的注意点:
- 动手前先备份:
cp -a一份到/root,任何时候都能秒回滚; - 修完先看 Candidate 再升级:
apt-cache policy pve-manager的 Candidate 变成 9.2.18 才算修复成功; - 升级与重启分开做:
apt full-upgrade会顺带把内核从 6.17 升到 7.0 系列,需要重启才生效,生产节点记得挑维护窗口。
顺带一提:Proxmox 官方 Wiki 现在推荐的新姿势是 deb822 格式的
.sources文件 +/usr/share/keyrings/proxmox-archive-keyring.gpg(在源的Signed-By:里显式指明钥匙路径),比trusted.gpg.d这种"全局钥匙串"更可控。这次先按最小改动修复,换源迁移留作后续优化。
六、一键自动修复脚本
懒得逐条敲命令?我把上面的逻辑封装成了三平台一键脚本,全部只用 SSH + 官方仓库,不依赖任何第三方服务,并且都内置了"体检 → 备份 → 修复 → 验证"的完整流程,密钥环本来就健康时会自动跳过、不做任何改动:
图 12:脚本流程。先体检、再备份、后修复、终验证,幂等可重复执行。
方法一:人工一键执行
Ubuntu 26.04 / PVE 节点本地(fix-pve-keyring.sh)——既能在 PVE 节点上直接跑,也能传一个 root@目标节点 参数远程修另一台:
#!/usr/bin/env bash
# fix-pve-keyring.sh — 修复 PVE 源验签失败导致的版本天花板
# 本机修复: sudo bash fix-pve-keyring.sh
# 远程修复: bash fix-pve-keyring.sh root@<目标节点IP>
set -euo pipefail
PAYLOAD=$(cat <<'EOS'
set -euo pipefail
KEY="/etc/apt/trusted.gpg.d/proxmox-release-trixie.gpg"
PKG="/usr/share/keyrings/proxmox-archive-keyring.gpg"
echo "== [1/5] 修复前版本 =="
pveversion || true
apt-cache policy pve-manager | sed -n '1,4p'
echo "== [2/5] 密钥环体检 =="
if file "$KEY" 2>/dev/null | grep -q "OpenPGP Public Key"; then
echo "OK:密钥环格式正常。若仍无法升级,请另行排查软件源与网络。"
else
echo "BAD:密钥环不是有效的公钥文件,需要修复。"
fi
echo "== [3/5] 备份原文件 =="
if [ -e "$KEY" ]; then
cp -a "$KEY" "$KEY.bak.$(date +%Y%m%d%H%M%S)"
echo "已备份:$KEY.bak.*"
fi
echo "== [4/5] 用官方密钥包恢复公钥 =="
apt-get install --reinstall -y proxmox-archive-keyring \
|| echo "重装失败,尝试使用本机已有的官方密钥"
if [ ! -s "$PKG" ]; then
echo "未找到官方密钥文件 $PKG,请参考文章手动方案。"; exit 1
fi
install -m 0644 "$PKG" "$KEY"
echo "== [5/5] 刷新索引并复核 =="
apt-get update
echo "== 修复后版本信息(Candidate 应变为最新版)=="
apt-cache policy pve-manager | sed -n '1,4p'
EOS
)
if [ "${1:-}" != "" ]; then
ssh "$1" 'bash -s' <<<"$PAYLOAD"
else
bash -c "$PAYLOAD"
fi
Windows 11(fix-pve-keyring.ps1)——用系统自带的 OpenSSH 客户端远程修复,PowerShell 里执行 .\fix-pve-keyring.ps1 -Target "root@<目标节点IP>":
# fix-pve-keyring.ps1 — Windows 11 一键远程修复 PVE 密钥环
param(
[Parameter(Mandatory = $true)][string]$Target, # 例如 root@<目标节点IP>
[int]$Port = 22
)
$payload = @'
set -euo pipefail
KEY="/etc/apt/trusted.gpg.d/proxmox-release-trixie.gpg"
PKG="/usr/share/keyrings/proxmox-archive-keyring.gpg"
echo "== [1/5] 修复前版本 =="
pveversion || true
apt-cache policy pve-manager | sed -n '1,4p'
echo "== [2/5] 密钥环体检 =="
file "$KEY" 2>/dev/null | grep -q "OpenPGP Public Key" \
&& echo "OK:密钥环格式正常" || echo "BAD:密钥环损坏,开始修复"
echo "== [3/5] 备份原文件 =="
[ -e "$KEY" ] && cp -a "$KEY" "$KEY.bak.$(date +%Y%m%d%H%M%S)"
echo "== [4/5] 用官方密钥包恢复公钥 =="
apt-get install --reinstall -y proxmox-archive-keyring \
|| echo "重装失败,尝试使用本机已有的官方密钥"
[ -s "$PKG" ] || { echo "未找到 $PKG"; exit 1; }
install -m 0644 "$PKG" "$KEY"
echo "== [5/5] 刷新索引并复核 =="
apt-get update
apt-cache policy pve-manager | sed -n '1,4p'
'@
$payload | ssh -p $Port $Target "bash -s"
if ($LASTEXITCODE -eq 0) {
Write-Host "修复流程执行完成,请确认上方 Candidate 已更新到最新版。" -ForegroundColor Green
} else {
Write-Host "执行出错,请检查 SSH 连接与上方输出。" -ForegroundColor Red
}
macOS 26(fix-pve-keyring-macos.sh)——终端里执行 bash fix-pve-keyring-macos.sh root@<目标节点IP>:
#!/usr/bin/env zsh
# fix-pve-keyring-macos.sh — macOS 一键远程修复 PVE 密钥环
set -euo pipefail
TARGET="${1:?[用法] bash fix-pve-keyring-macos.sh root@<目标节点IP>}"
ssh "$TARGET" 'bash -s' <<'EOS'
set -euo pipefail
KEY="/etc/apt/trusted.gpg.d/proxmox-release-trixie.gpg"
PKG="/usr/share/keyrings/proxmox-archive-keyring.gpg"
echo "== [1/5] 修复前版本 =="
pveversion || true
apt-cache policy pve-manager | sed -n '1,4p'
echo "== [2/5] 密钥环体检 =="
file "$KEY" 2>/dev/null | grep -q "OpenPGP Public Key" \
&& echo "OK:密钥环格式正常" || echo "BAD:密钥环损坏,开始修复"
echo "== [3/5] 备份原文件 =="
[ -e "$KEY" ] && cp -a "$KEY" "$KEY.bak.$(date +%Y%m%d%H%M%S)"
echo "== [4/5] 用官方密钥包恢复公钥 =="
apt-get install --reinstall -y proxmox-archive-keyring \
|| echo "重装失败,尝试使用本机已有的官方密钥"
[ -s "$PKG" ] || { echo "未找到 $PKG"; exit 1; }
install -m 0644 "$PKG" "$KEY"
echo "== [5/5] 刷新索引并复核 =="
apt-get update
apt-cache policy pve-manager | sed -n '1,4p'
EOS
三个脚本执行完,只要最后的 Candidate 跳到了 9.2.x(或你阅读本文时的最新版),修复即宣告成功。
方法二:Agent 自动配置
更省事的做法是把整个问题直接丢给 AI Agent(ZCode、Claude Code 等均可),你只需要提供 SSH 连接方式。下面这段提示词可以直接复制:
请帮我修复一台 Proxmox VE 服务器的"无法升级到最新版本"问题,大概率根因是
/etc/apt/trusted.gpg.d/proxmox-release-trixie.gpg 密钥环文件损坏(例如内容是
PGP 签名块而不是公钥),导致 apt 验签失败、索引长期不刷新。请按以下步骤操作:
1. 通过 SSH 登录该节点(连接方式见会话上下文/我会另行提供);
2. 记录现状:执行 pveversion 和 apt-cache policy pve-manager;
3. 用 file 命令检查上述密钥文件,若不是 "OpenPGP Public Key",
先用 cp -a 备份原文件到 /root/ 下;
4. 执行 apt-get install --reinstall -y proxmox-archive-keyring,
然后将 /usr/share/keyrings/proxmox-archive-keyring.gpg 以 0644 权限
复制为上述密钥路径;若重装失败但本机已有该文件,直接复制即可;
5. 执行 apt-get update,确认输出中没有 Err:、Missing key、unsupported filetype;
6. 输出 apt-cache policy pve-manager 的 Candidate 作为修复证据。
约束:不要执行 dist-upgrade/full-upgrade,不要重启服务器,不要修改软件源地址。
Agent 跑完后,重点核对它给出的两点证据:apt-get update 无 Err: 警告、Candidate 已更新到最新版本。
七、举一反三:怎么避免再踩一次
图 13:防复发清单。核心思想只有一个——别相信"没报错"就是"没问题"。
这次事件里最值得记住的教训,其实不是 GPG 本身,而是"静默降级"这种故障形态:
- 升级前先跑"体检三连":
apt-get update盯一眼有没有Err:/W:;apt-cache policy <包名>看候选版本;ls -l /var/lib/apt/lists/看索引时间戳。三步加起来不到 30 秒,能识破 90% 的"假最新"。 - 自动化脚本别只看退出码:
apt-get update在仓库验签失败时依然"成功"退出。脚本里要加apt-get update 2>&1 | grep -E '^(Err|W):' && exit 1这类断言,把警告升级为失败。 - 密钥文件认准官方来源:重装
proxmox-archive-keyring包是最稳的获取方式;从网页上手工下载密钥时,保存后务必用file确认是OpenPGP Public Key而不是其他什么东西——本次事故的直接起因就是存错了文件。 - 多机环境互为参照:同集群的机器出现版本差、行为差,第一反应就应该是
diff两台机器的源配置和密钥目录,往往一眼见底。
八、Q&A
Q1:为什么 Web UI 的仓库页面看起来一切正常,问题却藏了四个多月?
A:一方面,apt-get update 遇到验签失败只输出 W: 警告,命令照常"成功",挂在 cron 里的自动更新永远发现不了;另一方面,PVE Web UI 的「节点 → 更新 → 仓库」页面其实自带仓库体检、能标出签名异常,但大多数人(包括当时的我)很少主动点开这一页。建议把它加进日常巡检清单。
Q2:在源上加 [trusted=yes] 岂不是一步到位?
A:那是把防伪公章检查整个拆掉,中间人塞什么包你都照单全收。生产环境绝对不要这么做。
Q3:换个镜像站能不能解决?
A:不能。密钥环坏在本地,换一百个镜像站,验签照样失败。这次两台机器用的是同一个镜像站,一台好一台坏就是明证。
Q4:9.1 → 9.2 需要按官方迁移文档一步步操作吗?
A:不需要。同一个大版本内的小版本更新,apt full-upgrade 即可;只有跨大版本(比如 8 → 9)才需要严格按照官方升级手册执行。
Q5:修复之后可以立刻升级吗?
A:技术上随时可以,但建议:先给重要虚拟机做备份/快照,确认集群状态正常,然后挑一个维护窗口执行 apt full-upgrade,重启后新内核才生效。我的节点 A 上跑着软路由和数据库,就是留着深夜窗口才动手的。
Q6:Debian 12 / PVE 8 也会有这个问题吗?
A:会的,只是报错长相不同。老一代验签器 gpgv 会报 NO_PUBKEY,原理一模一样:密钥环坏了 → 索引停更 → 版本被锁死。本文的修复思路(恢复官方密钥 + 刷新索引)同样适用,只是密钥文件名换成对应的 bookworm 版本。
参考资料
- Proxmox VE 官方 Wiki:Package Repositories——各仓库的权威说明、密钥下载地址与 deb822 新式写法
- Proxmox VE 官方 Wiki:Install Proxmox VE on Debian 13 Trixie
- Proxmox 论坛:升级 9 后 Conflicting values set for option Signed-By——密钥路径混乱的常见坑
- Proxmox 论坛:repository … InRelease is not signed——Trixie 时代验签失败的典型病例