ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

通过 SSH 无重启接管 Linux 系统:takeover.sh 内存救援环境实战与源码级原理

通过 SSH 无重启接管 Linux 系统:takeover.sh 内存救援环境实战与源码级原理
  • CLI
  • 运维

【免费下载链接】takeover.sh

Wipe and reinstall a running Linux system via SSH, without rebooting. You know you want to.

项目地址:https://gitcode.com/gh_mirrors/ta/takeover.sh
点击查看免费下载

本篇指南围绕当前仓库的核心文档 README.md 展开,完整讲解 takeover.sh 的使用前提、7 步实战流程与接管成功后的收尾操作,并结合仓库内的 takeover.sh 与 fakeinit.c 源码逐行拆解其底层机制。读完本文,你将理解如何借助 tmpfs 内存文件系统与pivot_root在不重启、不接触物理终端的前提下远程接管一台运行中的 Linux 主机,以及这套方案的风险边界与扩展方向。

项目定位:一条脚本完成"远程换系统"的疯狂任务

takeover.sh 是一个单文件 Shell 脚本,目标是让你通过 SSH 彻底接管一台正在运行的 Linux 系统:登录进一个驻留内存的救援环境,卸载原始根文件系统,然后为所欲为——全程无需重启。它的典型应用场景是"把一台运行中的发行版换成另一个发行版",而无需碰物理控制台(摘自 README.md 的项目简介)。

这套方案的技术骨架可以概括为三条主线:

  1. 内存中的救援环境:在宿主机上用 tmpfs 挂载一个独立目录(默认/takeover),把完整的救援系统解压进去,整个接管过程发生在 RAM 中;
  2. 替换 init:利用telinit u让 init 重新执行自身二进制,从而把真正的 init 偷梁换柱成接管脚本;
  3. 根目录切换:通过pivot_root把 tmpfs 变成新的根文件系统,旧根文件系统则被挂载到新根下的old_root,随后即可卸载并重新分区。

仓库共包含 4 个文件:核心脚本 takeover.sh(约 88 行)、编译成接管后 PID 1 的 C 程序 fakeinit.c、主文档 README.md 以及 LICENSE(MIT 协议,作者 Hector Martin "marcan",其中 fakeinit 的 init 思想基于 Rich Felker 的 trivial init)。

严肃警告:实验性工具,先弄清原理再用

README 开篇用整整一节发出警告,这是使用本工具前必须认真对待的前提条件:

  • 本脚本属于实验性质。如果你不能准确理解它的工作机制,就不要使用;
  • 不要在任何你在意的系统上运行,也不要在任何你期望它保持运行的系统上运行;
  • 除非你能接受用物理手段修复一次失败的接管,否则不要运行它;
  • 任何环节出错,系统极有可能直接 panic;
  • 好消息是:只要你从 tmpfs 运行,脚本本身不会对现有根文件系统做永久性修改(takeover.sh 中只是挂载 tmpfs、proc、sysfs、dev 等伪文件系统,全程不写旧根),因此只要能通过带外(out-of-band)机制远程重启机器,理论上你还有退路;
  • 但脚本没有任何机制让你从新环境"退回"到原有环境。完成后你必须重启;只要有一个环节搞错,机器就无法启动;
  • README 明确表示:这不是给新手看的指南,作者故意不提供可以直接复制粘贴的命令,需要你自己理解每一步在做什么。

这段警告的本质是在划定使用边界:它是"可逆性"与"破坏性"并存的高级运维工具,只适合能独立诊断故障的资深用户。

兼容性:依赖支持telinit u的 init 系统

脚本的设计前提是init 系统支持telinit u命令来重新加载 init 二进制(README.md 的 Compatibility 一节)。目前已知满足这一要求的主要是:

  • sysvinit
  • systemd

如果你的 init 系统工作方式不同(例如 Upstart 或其他自研 init),你需要自行改造脚本,否则可能完全无法工作。

README 还给出了两条重要实践建议:

  1. 务必先在虚拟机里测试。做法是:把你当前运行中的根文件系统打成 tarball,解压进一个 VM 镜像,把 VM 启动起来(引导加载器的配置留给读者自己完成),再在 VM 里完整走一遍接管流程;
  2. 一个实用提示:mount --bind / /mnt可以让你在/mnt看到根文件系统的完整视图,且不会包含挂载在它之上的其他文件系统——这正是制作 VM 镜像所需的"干净视图"。

工作原理:从旧 init 到 fakeinit 的三步切换

在进入实战步骤前,先建立整体机制认知。整套接管动作可以拆成三个阶段:

阶段一:在 tmpfs 中搭好"影子系统"

脚本把救援环境安放在/takeover(脚本顶部的TO=/takeover,见 takeover.sh)。接管前,它在这个目录里完成一系列准备:

  • 创建old_root目录,作为后续 pivot_root 时旧根文件系统的挂载点(takeover.sh);
  • 把etc/mtab换成指向/proc/mounts的符号链接(takeover.sh);
  • 挂载伪文件系统:tmpfs(挂到tmp)、proc、sysfs、devtmpfs;若devtmpfs挂载失败,则回退为 tmpfs 并把/dev内容拷贝进去、重建dev/pts(takeover.sh);
  • 最后mount --bind /dev/pts dev/pts,保证新环境里有一个可用的 PTY 设备树(takeover.sh)。

阶段二:偷梁换柱替换 init 二进制

脚本通过readlink /proc/1/exe拿到当前 PID 1(init)的真实路径(takeover.sh),例如 sysvinit 下的/sbin/init或 systemd 下的/usr/lib/systemd/systemd。随后:

  1. 生成一段 Shell 脚本写入tmp/${OLD_INIT##*/},即与原 init 同名的新"假 init"(takeover.sh);
  2. mount --bind tmp/${OLD_INIT##*/} ${OLD_INIT}——用绑定挂载把假 init 盖在真 init 的路径上(takeover.sh);
  3. 执行telinit u,通知 init "重新加载自身"(takeover.sh)。由于该路径现在指向的是假 init,init 重新执行后就变成了我们的接管脚本。

阶段三:pivot_root 与假 init 接管 PID 1

假 init 脚本的核心动作如下(takeover.sh 中生成的内容):

mount --make-rprivate / pivot_root . old_root exec ./busybox chroot . /fakeinit
  • mount --make-rprivate /先把根挂载点设为私有,防止挂载事件向外部传播,为 pivot_root 做准备;
  • pivot_root . old_root把当前目录(即/takeover)变成新根,旧根被移动到old_root;
  • exec ./busybox chroot . /fakeinit让 PID 1 最终变成 fakeinit.c 编译出的程序。

fakeinit.c本身只有 21 行,是一个"最简 init"(fakeinit.c),逻辑如下:

  • 关闭所有文件描述符(0~1023),与旧世界的句柄彻底切割;
  • 检查getpid() != 1则退出,确保自己确实是 PID 1;
  • 阻塞所有信号;
  • 进入无限wait(&status)循环,持续回收子进程,避免新世界里产生僵尸进程。

它的角色就是"安静地活着、回收孤儿",把系统完全交给你在新 SSH 会话里控制。

使用步骤:完整实战流程

以下是 README.md 给出的完整 7 步流程,结合源码补充了每一步的细节与注意事项。

第 1 步:选择并准备救援环境

首先决定要使用哪个救援环境。README 推荐SystemRescueCD(自带大量实用工具,需要先 loopmount 它的 ISO,再用unsquashfs解包)。选择时注意:

  • 救援环境必须能装进空闲内存,且要留有富余;
  • 如果救援环境里带有/lib/modules,建议删除以节省空间——它的内核模块在宿主内核上本来就用不了。

第 2 步:创建/takeover并解压救援环境

在目标系统上创建目录/takeover并挂载 tmpfs,然后把救援环境解压进去。关键动作:

  • 用chroot进入该目录运行几条命令,确认环境能正常工作;
  • 注意不要破坏文件系统权限(解压时务必保留属主与权限位);
  • 验证完成后退出 chroot。

第 3 步:放入静态链接的 busybox

下载一份静态链接的 busybox 二进制放到/takeover/busybox(busybox.net 提供现成的多架构静态编译产物)。用类似/takeover/busybox sh的命令确认它能运行。

为什么必须静态链接:后续脚本与假 init 都在新环境里执行,新环境可能缺少动态链接库,静态链接才能保证自包含。

第 4 步:拷贝仓库内容并编译 fakeinit

把本仓库的全部内容复制到/takeover,然后编译fakeinit.c。编译必须保证产物能在接管环境内运行:

  • 如果救援环境自带 gcc,可以直接在 chroot 内编译:chroot /takeover gcc /fakeinit.c -o /fakeinit;
  • 否则建议静态链接编译,避免运行时找不到动态库。

脚本在启动时也会检查fakeinit是否存在,缺失则直接报错退出(takeover.sh)。

第 5 步:尽量关闭宿主机上的服务

接管前尽量关停宿主机的服务。脚本默认会在 80 端口拉起一个备用的 SSHd(PORT=80,见 takeover.sh),这个端口可以在脚本里自行修改。80 端口之所以被选中,是因为它几乎不会被防火墙规则拦掉,作为"逃生通道"很稳妥。

第 6 步:运行接管脚本并跟随提示

执行sh /takeover/takeover.sh,然后按提示操作。脚本的交互流程如下:

  1. 首先要求为 sshd设置 root 密码:脚本会通过chroot . /bin/passwd在救援环境内设置密码(takeover.sh);
  2. 完成伪文件系统挂载与 TTY 重定向后,提示输入OK继续(takeover.sh);
  3. 生成假 init、启动备用 sshd(ssh-keygen -A生成主机密钥,再以sshd -p $PORT -o PermitRootLogin=yes启动,见 takeover.sh);
  4. 提示你用新会话 SSH 到备用 sshd,确认登录成功后回到原会话输入OK(takeover.sh);
  5. 脚本随即执行mount --bind覆盖 init 路径并telinit u(takeover.sh),然后暂停约 10 秒等待切换完成(takeover.sh)。

值得注意的细节:脚本会把自己的标准输入/输出/错误重定向到当前会话的 TTY 设备(takeover.sh),因此切换成功后新 init 的输出会出现在你原来的 SSH 会话里("Init takeover successful" 等提示)。

第 7 步(成功之后):清理旧世界

如果一切顺利,你就可以在新 SSH 会话里收尾了:

  1. 杀掉残留的旧守护进程:推荐用kill -9,确保它们在关机过程中不会做任何"多余的事";
  2. 卸载/old_root下的所有文件系统,包括/old_root本身。建议先把/old_root/lib/modules拷贝进新的 tmpfs,以备需要旧内核模块时使用;
  3. 至此你完全在内存中运行,可以随心所欲了。注意在安全重新分区前,可能需要清理 LVM 卷(dmsetup是你的朋友)等存储栈——而这正是做这件事的终极目的:装 Gentoo(README 原话,作者式幽默)。

结束:重启

收工后,卸载所有文件系统、sync,然后执行reboot -f或echo b > /proc/sysrq-trigger并祈祷(README 原话 "cross your fingers")。注意整个流程没有回头路,最终必须重启。

脚本关键变量速查

变量位置作用默认值
TOtakeover.sh接管环境根目录/takeover
OLD_INITtakeover.sh当前 PID 1 的真实路径(readlink /proc/1/exe)自动探测
PORTtakeover.sh备用 sshd 监听端口80(可按需修改)

进一步阅读与扩展方向

README 的最后一部分提到了两条延伸路线:

  1. 手动实现类似流程的替代方案:有社区方案(源于 Unix StackExchange 上的一个问答)展示了如何用现有根文件系统的子集而非独立救援系统来完成类似操作。它的做法是保留(一份新的)现有 init 系统与必要守护进程继续运行,完成后还能回到原始根文件系统。这种方案在只想调整根文件系统大小或重新配置磁盘分区、但不打算换发行版、且希望完全不重启时更为实用——可以理解为"更温和的版本"。

  2. takeover.sh 的潜在扩展:可以扩展为在接管完成后重新 exec 一个新的 init。这样,只要你还愿意沿用旧内核,就能在完全不重启的情况下整体切换到一个全新的发行版。作者表示对此方向的 pull request 持欢迎态度。

小结

takeover.sh 的价值不在于提供一堆可以复制粘贴的现成命令,而在于它演示了一套精巧的机制组合:tmpfs 内存环境 + busybox 自包含工具链 +mount --bind覆盖 init 路径 +telinit u触发重载 +pivot_root切换根文件系统 + 极简 C 程序接管 PID 1。如果你恰好需要在一台无法物理接触、又必须换系统的机器上作业,并且已经完整理解上述每个环节的风险与原理,那么这套仓库就是你按图索骥的最佳起点——但请务必先在 VM 里完整演练一遍。

  • CLI
  • 运维

【免费下载链接】takeover.sh

Wipe and reinstall a running Linux system via SSH, without rebooting. You know you want to.

项目地址:https://gitcode.com/gh_mirrors/ta/takeover.sh
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进