<!-- 文档分类: MGMT-OTHER-002 | 阶段: 项目管理 | 原路径: docs/事后分析-终端崩溃-2026-05-09.md -->
# 事后分析：ISO 构建破坏宿主终端 (2026-05-09)

## 事故摘要

在首次对 `scripts/alpine/build-khy-os-iso.sh` 进行真实环境测试时，
构建过程被多次中断（通过 `kill` / 任务停止）。
每次中断都会留下仍处于激活状态的 bind-mount，而清理流程中的 `rm -rf`
经由这条仍然生效的 bind-mount 摧毁了宿主机的设备节点，导致
Linux 终端无法使用。

## 时间线

| Time  | Event |
|-------|-------|
| 11:17 | 启动首次 ISO 构建；因 DNS 故障被终止 |
| 11:17 | `/dev` 仍处于 bind-mount 状态时执行了 `rm -rf /tmp/khy-os-rootfs.*` → **`/dev/null` 被摧毁**（变成了一个普通文件） |
| 11:49 | 用户报告："Failed to open PTY: No such file or directory" |
| 11:53 | 通过 `mknod /dev/null c 1 3; chmod 666 /dev/null` 恢复 `/dev/null` |
| 11:54 | 启动第二次构建；`mount -t devpts devpts …/dev/pts` 以 `ptmxmode=000` **覆盖了宿主机的 `/dev/pts`** |
| 11:54 | 用户报告："Text was empty (or contained only whitespace)" |
| 11:56 | 以正确选项重新挂载宿主机 `/dev/pts`；GNOME Terminal 配置文件中 `use-custom-command=true` 但命令为空；重置为 `false` |
| 11:58 | 终端完全恢复 |

## 根因分析

### 1. 经由 live bind-mount 的 `rm -rf`

```bash
# DANGEROUS: if /dev is still bind-mounted, this deletes HOST device nodes
mount --bind /dev "${ROOTFS}/dev"
# ... build interrupted ...
rm -rf "${STAGING}"   # deletes ${ROOTFS}/dev/* = HOST /dev/*
```

**影响**：宿主机上的 `/dev/null` 和 `/dev/ptmx` 被删除。

### 2. `devpts` 挂载覆盖宿主机

```bash
# DANGEROUS: mounts a NEW devpts over the host's /dev/pts
mount -t devpts devpts "${ROOTFS}/dev/pts"
```

在缺少 `newinstance` 的情况下，这会用一个采用受限默认值（`ptmxmode=000`）的新
实例替换掉宿主机的 devpts 挂载，从而在整个系统范围内阻止任何新的
PTY 分配。

### 3. 销毁性清理前缺少安全检查

`cleanup()` 函数假定 `umount` 总是成功，随后无条件执行了
`rm -rf`。在构建被中断的情况下，`umount` 可能会静默失败
（目标繁忙、已被子进程卸载等）。

## 已实施修复（commit 3f70078）

### 修复 1：挂载隔离

```bash
mount --bind /dev "${ROOTFS}/dev"
mount --make-rslave "${ROOTFS}/dev"          # NEW: prevent propagation
mount -t devpts devpts "${ROOTFS}/dev/pts" \
  -o newinstance,gid=5,mode=620,ptmxmode=666 # NEW: isolated instance
```

- `--make-rslave`：chroot 内部的卸载事件不会传播到宿主机
- `newinstance`：创建一个独立的 devpts 文件系统，宿主机 `/dev/pts` 不受影响

### 修复 2：`rm` 前的安全检查

```bash
cleanup() {
  umount -lf "${ROOTFS}/dev/pts" 2>/dev/null || true
  umount -lf "${ROOTFS}/dev"     2>/dev/null || true
  # ...
  if mountpoint -q "${ROOTFS}/dev" 2>/dev/null; then
    warn "dev still mounted! Skipping rm to protect host."
    return 1
  fi
  rm -rf "${STAGING}"
}
```

### 修复 3：正确的卸载顺序

改为按相反顺序卸载：`proc → sys → dev/pts → dev`。

## 恢复命令

若再次发生此问题，使用以下命令恢复：

```bash
# Restore /dev/null
sudo mknod /dev/null c 1 3
sudo chmod 666 /dev/null

# Restore /dev/ptmx
sudo mknod /dev/ptmx c 5 2
sudo chmod 666 /dev/ptmx

# Fix /dev/pts permissions
sudo mount -o remount,gid=5,mode=620,ptmxmode=666 /dev/pts

# If GNOME Terminal shows "Text was empty"
dconf write /org/gnome/terminal/legacy/profiles:/:b1dcc9dd-5262-4d8d-a863-c897e6d979b9/use-custom-command false
```

## 经验教训

1. **绝不要对包含 bind-mount 的目录树执行 `rm -rf`** —— 务必先用
   `mountpoint -q` 进行验证。
2. **在 chroot 中挂载 devpts 时务必使用 `newinstance`** —— 否则
   宿主机的 PTY 子系统会被静默替换。
3. **在 bind-mount 上使用 `--make-rslave`**，以阻止事件在 chroot
   与宿主机之间传播。
4. **构建被中断是常态，而非例外** —— 清理逻辑必须能够
   稳健应对部分完成的状态。
