一句话让 Claude 把 Ubuntu 22.04 升级到 24.04:一次几乎不用盯屏幕的跨 LTS 实战
先说结论
这台远端机器是家里那台 24 小时开机的代理机,系统一直停在 Ubuntu 22.04.4 LTS(jammy),内核 5.15。一句"帮我把 192.168.103.182 升级到 ubuntu24.04",Claude 就替我拆开了旧柜子,在另一个房间装上了新柜子,过程中它自己开了 tmux 守夜、自己起了 fallback sshd 备胎、自己用
do-release-upgrade -f DistUpgradeViewNonInteractive跑完了整段流水线。25 分钟后主机回来,内核已经是 6.8.0-124,所有服务依旧在听。本文不是讲 do-release-upgrade 怎么用,那是 Ubuntu 官方文档的事;本文是讲"当 Agent 拿到 SSH 之后,它在做什么、为什么这么做、以及哪些坑你必须提前排"。

封面:Ubuntu 24.04 LTS(Noble Numbat)官方桌面截图,这就是升级完成后那位"管家"坐镇的房间长什么样。
一、为什么我把升级交给 Agent
我家里跑着一台远端机器,主要干两件事:跑 v2ray、跑一个自研的 Python SOCKS5/HTTP 代理。它 24 小时不关机,IP 是固定的,我所有设备都指着它的代理出墙。
Ubuntu 22.04(jammy)从 2022 年一直服役到现在,默认内核 5.15,Python 还是 3.10。我已经习惯了它;但 24.04(noble)已经发布了一年半,内核换到了 6.8,systemd 换到了 255,Python 升级到了 3.12。长痛不如短痛,再不升就要错过下一个 LTS 周期了。
问题在于:这台机器不在我手边,装在弱电箱里接 PVE,登上去只能 SSH。远程升级 Ubuntu,我的痛点很具体——
- 升级过程不能断。
do-release-upgrade是长任务,几百兆的包要下、要解包、要 configure。途中如果 sshd 被替换,我没有 console 救场。 - 升级期间服务会断。v2ray 在被升级时会被 restart,有几分钟我家里所有设备都没法出墙。我能接受几分钟,但不能接受几十分钟。
- 我不想守着屏幕。在终端前面看 dpkg 一行一行跑,这是对注意力的浪费。
把这三件事拼在一起,答案就是:让 Agent 去做,我只审结果。 我给它一句话,它把 tmux 守夜、fallback sshd、do-release-upgrade、监控日志这些事自动串起来。如果出错,我能回看;如果顺利,我就喝杯茶等通知。
下面这张图是整件事的"鸟瞰":
图 1:你(左)说一句话,Agent 通过 ssh + tmux 跑到远端机器(右)。远端机器从 jammy 22.04 升级到 noble 24.04。tmux 负责"断线不杀进程",fallback sshd 负责"主 sshd 挂了走它"。整个过程你没有离开沙发。
二、Agent 做的第一件事:先看清对方是谁
老规矩,Agent 接到任务,先 SSH 探活。探活不是"能不能 ping 通",而是把对方的底细摸一遍:
ssh root@host 'cat /etc/os-release; uname -a; uptime; df -h /; free -h; ss -tlnp | head -20'
返回内容里 Agent 会重点看这几样:
- 发行版和 codename:
Ubuntu 22.04.4 LTS (jammy),目标noble。 - 内核:
5.15.0-101-generic,升级后会换成6.8.0-124-generic。 - 磁盘:
/dev/mapper/ubuntu--vg-ubuntu--lv 9.5G 4.6G 4.5G 51% /,剩 4.5G,够但偏紧。 - 内存:1G,带 774M swap;KVM 客户机,UEFI 启动。
- 关键服务:v2ray 多个端口、Python SOCKS 监听在 1080/1081/1090/1091。
- 升级器是否就绪:
/usr/bin/do-release-upgrade在,Prompt=lts。
这段话翻译成日常:房东先看一眼房子的户型图,才知道是 90 平的两居、还是 30 平的开间;才知道搬家公司该派多大的车。
注意一个细节——Prompt=lts。Ubuntu 的 release-upgrade 默认走 LTS 通道,但 jammy → noble 是跨 LTS,纯 Prompt=lts 不会自动发现 noble。Agent 会把 /etc/update-manager/release-upgrades 改成 Prompt=normal,这样 do-release-upgrade 才认 noble。
三、Agent 做的第二件事:tmux 守夜
升级跑起来之后,Agent 不能保证自己的 SSH 连接不断。中途网络抖一下、自己的笔记本休眠一下、Agent 上下文被截断一下,SSH 就死了。
如果直接 ssh root@host 'do-release-upgrade ...' &,父 SSH 死的时候,内核会把远端的 do-release-upgrade 一起带走——这就是俗话说的"杀进程"。中途死了 = apt 锁死 = 系统半升级 = 重启后大概率起不来。
这就像你请搬家公司在搬家,你下楼买杯咖啡,门铃没人按,搬家师傅就走了。你需要一个人在家门口一直守着,哪怕你出门了也守着。
tmux 就是这位"看门的"。Agent 在远端起一个 detached session:
tmux new-session -d -s upg -c /root 'bash -lc "echo tmux-ready; bash"'
之后所有操作,包括 do-release-upgrade,都通过 tmux send-keys 投递到这个 session 里。tmux 是服务端进程,不依赖 SSH 连接。Agent 的 SSH 断了 10 次都没事,只要 tmux attach -t upg 就能续上。
为什么必须 tmux,而不是 nohup + &?因为 tmux 还提供了可重连的回放:你可以滚回去看之前 dpkg 跑到哪一行、哪个包卡住、journalctl 倒数到哪一句。这在排错时是命根子。
四、Agent 做的第三件事:fallback sshd
升级过程中,sshd 包本身要被替换。dpkg 替换 /usr/sbin/sshd 的那一刻,主 sshd 进程会被 kill,然后 systemd 拉起新版本。
时间窗口很短,但不是零。如果此时 Agent 的 SSH 连接正好断在这个缝隙,后果是:
- 主 sshd 死了
- 新 sshd 还没起来
- Agent 重连 → “Connection refused”
Agent 重连失败,就再也连不上了。要么去 console(我没有),要么等开机(我也不知道要多久)。
这就像电梯换钢丝绳,主电梯停了,备用电梯得同时在旁边立着。不能让乘客在 20 楼悬空。
解决方法是:Agent 在升级之前,手动起一个独立于 systemd 的 sshd,监听 1022 端口:
ssh-keygen -A
/usr/sbin/sshd -D -p 1022 -o PidFile=/run/sshd-extra/sshd-1022.pid &
这条 sshd 不受 systemd 控制,绝对不会被 dpkg 替换影响——它加载的是已经被复制到内存里的二进制,文件被改写也不会影响已经在跑的进程。
之后 Agent 任何时候 SSH 断了,先试 22,再试 1022。1022 是真正的逃生通道。
升级完成后,这个 fallback 进程就可以 kill 掉了。
五、Agent 做的第四件事:实际跑升级
万事俱备,开跑。Agent 投到 tmux 里的是这一行:
DEBIAN_FRONTEND=noninteractive do-release-upgrade -f DistUpgradeViewNonInteractive 2>&1 | tee /var/log/dist-upgrade.log
两个关键开关:
-f DistUpgradeViewNonInteractive:完全不弹交互。默认情况下do-release-upgrade会问很多"是否保留这个 conffile / 是否重启服务",无人值守会卡住。-f DistUpgradeViewNonInteractive走"安全默认"分支。DEBIAN_FRONTEND=noninteractive:同样是为了让 debconf 不弹。
然后 Agent 自己就变成"观察者":不再发指令,而是每隔 30 秒 journalctl + tmux capture-pane + ps -ef | grep dpkg 看一次,确认进展。
整段流程大致分成这几段:
图 2:把升级拆成五段,每段都有"输出信号",Agent 看到信号就知道下一段该不该走。
具体每段发生什么,以下是 Agent 在远端日志里抓到的心电图:
图 3:同一次升级的 journalctl 时间线节选。10:14 进入 dpkg,10:21 udev 255(对应 noble)上线,10:24 autoremove 释放旧包,10:28 重启,10:35 新内核 6.8.0-124 上线,sshd PID 805 接管 22 端口。整段心跳很整齐,没有"卡住"的迹象。
如果你看官方文档,会告诉你"升级可能耗时 1 到数小时"。在我这台机器上,从 10:08 SSH 探活到 10:35 内核上线,共 27 分钟。其中下载包占了大约 6 分钟(全网百兆带宽),dpkg 阶段占了 14 分钟,剩下的是探测和重启。
六、Agent 做的第五件事:重启与自检
dpkg 阶段全部结束后,Agent 会问一次"是否重启",DistUpgradeViewNonInteractive 的回答是"是"。systemd 开始按依赖顺序 stop 所有 unit,最后 reboot.target 触发重启。
Agent 在外做的是:
nohup bash -c "sleep 2; /sbin/reboot" &触发重启(放在后台以免自己 SSH 被杀)- 等待主机 SSH 不可达(drop)
- 等待主机 SSH 回来(up)
- 回来后立即跑:
ssh root@host 'cat /etc/os-release; uname -r; uptime; ss -tlnp; systemctl is-active ssh'
期望的输出是:
PRETTY_NAME="Ubuntu 24.04.4 LTS (Noble Numbat)"
VERSION_ID="24.04"
VERSION_CODENAME=noble
6.8.0-124-generic
... up 0 min ...
LISTEN 0 4096 0.0.0.0:22 systemd
LISTEN 0 100 0.0.0.0:1080 python3
... (所有原端口都在听)
active
这一步就是"你怎么证明升级真的成功了"。光看 dpkg 跑完不算——内核不换,systemd 不切,都不算。真正说了算的是 cat 出来的版本号和 uname 出来的内核号。
下面是 Agent 抓到的 os-release 输出(脱敏过):
图 4:升级完成的"体检报告"。VERSION_CODENAME=noble 是终极判定,VERSION_ID=24.04 是次级判定,uname -r=6.8.0-124-generic 是内核判定。三项都对,才算真的升级了。
到这里,Agent 给我交差。
七、你最关心的两件事
7.1 升级会不会把我的服务搞坏?
会,但很有限。
这次升级过程中,v2ray 和我的 Python 代理服务都被自动 restart 过——systemd 把它们的 unit 重新拉起。但因为这些 unit 没有"等远端依赖"的设置,它们 restart 完之后立即就在 listen 了。
真正可能"掉"的是:
- 没有任何 systemd unit 的服务(比如你用
nohup python3 xxx &跑的脚本)。升级不会动它,但重启会杀掉它。如果你的服务是这一类,这次升级之后必须立刻把它写成 systemd unit,否则下次宿主机维护重启,你家里代理全断。 - 依赖特定 Python 版本的服务。这台机器 22.04 自带 Python 3.10,升级后变成 3.12。我那两个 Python 脚本没有用 3.10 的特殊语法,所以没事。但如果你
pip install过一堆包,升级之后它们可能装在 3.10 路径下,新 python 找不到。建议升级前先确认。 - systemd 配置文件被冲掉。我用
--force-confnew,意味着新版 unit 文件优先。如果之前你改过Port、PermitRootLogin、AllowUsers,现在这些自定义配置都没了,需要重新覆盖。可以用diff /etc/ssh/sshd_config /etc/ssh/sshd_config.dpkg-old对比找回。
7.2 升级期间我的网络会断多久?
取决于你的代理类型。这台机器的代理是 SOCKS5 + HTTP,客户端一般有重连机制,所以"卡顿几十秒"的感觉;但如果你跑了 TCP 长连接服务(SSH 反向隧道、WireGuard),那段时间会真的断。
27 分钟里,大约有 5 到 8 分钟是"服务在重启",其余时间服务在跑、只是性能受影响(因为 dpkg 在吃 CPU)。
7.3 升级完了还有什么要做的?
- 删旧内核:
apt purge linux-image-5.15.0-101-generic,释放约 100M+。 - autoremove 残留:
apt autoremove --purge,升级器通常会留一些 obsolete conffile。 - systemd drop-in 检查:
sshd_config等被改过的 unit 文件,确认你的自定义项还在。 - 服务自启化:把裸跑的脚本写成 systemd unit,见 7.1。
- /etc/update-manager/release-upgrades 改回
Prompt=lts,避免下次手贱do-release-upgrade跑到非 LTS 通道去。
八、一些事后反思
这次升级,Agent 替我做了完整的事——探活、风险评估、防断连、跑升级、自检、给报告。我自己只做了两件事:授权一次,和批准一次。剩下的 27 分钟,我在喝咖啡。
这件事让我重新思考了一下 Agent 的能力边界:
- Agent 擅长的是"流程明确 + 可验证"的任务。升级恰好就是:每一步都有输出、每一步都有判定条件(版本号、内核号、服务端口)、每一步失败都有具体补救方式(sshd 挂了走 1022,dpkg 中断走 dpkg –configure -a)。
- Agent 不擅长的是"凭直觉"的任务。比如"该不该把 v2ray 配置里的某个 IP 换掉",这种判断它做不到。这种事必须人来。
- Agent 最重要的能力是"被观察时诚实"。我让它做事的全程,我可以随时
tmux attach进去看,可以看到它在做什么、为什么这么做。它不会偷偷换内核——版本号是白纸黑字的cat结果,不是我嘴上说的"我升完了"。
Agent 不是替你思考,它是替你执行那些你已经知道怎么做的、但懒得盯着看的事。
如果你也在用 Claude Code、Cursor、Codex 这一类 Agent,试一次"一句话升级"吧。前提是你对这台机器足够熟悉,熟悉到能在 Agent 卡住的时候接手。
下次机器再要升 24.04 → 26.04,我就更省心了——连 tmux 和 fallback sshd 都不用再讲一遍。
参考资料
- Ubuntu 官方升级文档:https://help.ubuntu.com/community/NobleUpgrades
do-release-upgrade源码与 manpage:https://manpages.ubuntu.com/manpages/jammy/man8/do-release-upgrade.8.html- Ubuntu Wiki - ReleaseUpgrade SOP:https://wiki.ubuntu.com/SystemMaintenance/ReleaseUpgrades
tmux官方手册与最佳实践:https://github.com/tmux/tmux/wiki- systemd
service.d机制(drop-in override):https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html - SSH Fallback Port on Upgrade(launchpad bug 1192476,记录了 22→1022 fallback 的官方缘由):https://bugs.launchpad.net/ubuntu-release-upgrader/+bug/1192476
- Ubuntu 24.04 LTS Noble Numbat 桌面截图来源:Wikimedia Commons(CC BY-SA 4.0):https://commons.wikimedia.org/wiki/Category:Ubuntu_24.04
本文涉及的所有 IP、域名、用户名、密码、配置路径均已在公开前做脱敏处理;具体网络拓扑以你自家环境为准。