3. 反客为主:主力linux系统

乾坤未定,一切皆有可能:nixos宿主+linux vm主力机,还是nixos直接主力?!

计划

核心目标:把盘2 的 NixOS 整体入主盘1,形成「盘1 NixOS 主机 + 盘2 NixOS 僚机 + 原 C 盘旧 Win」三引导结构;
          主机完善到工作机环境后,反向克隆覆盖僚机(2026-10-08 定案)

【定案说明】
  原方案「Linux VM 当主力、NixOS 只当宿主」作废。
  触发点:NixOS 的声明式配置更适合长期迭代 —— 明文、模块化、可回退、AI 可读写;
  多套一层 Linux VM 只增加接缝,不增加能力。因此 NixOS 宿主即主力,不再建主力 Linux VM。

【目标引导结构(固件启动菜单三项,默认主机)】
  1) 盘1 NixOS 主机   —— 盘1 的 2 GiB ESP + root 分区       【默认】
  2) 盘2 NixOS 僚机   —— 盘2 的 1 GiB ESP(原样保留)
  3) 旧 Win 物理系统   —— 盘1 的 BWINEFI + s500win(已搬到盘尾)
  〔2026-10-11 实测实装:最终由 xorboot 承担默认引导与选项菜单,菜单为
    主 nixos / 旧 win / 副 nixos 三项〕

【主机(盘1)文件布局】
  /           盘1 root 分区(ext4,约 1.57 TiB)
  /boot       盘1 的 2 GiB ESP
  /s500data   root 内的目录(不是挂载点),平铺 vms/ repos/ docker/ 个人数据

【世代与 EFI:各自管各自的(2026-10-09 定案)】
  世代 = 用「构建它时那台机的配置」编译出的 store path,配置里写死了 / 与 /boot 的 UUID
  ⇒ 同一个世代不可能同时适配两台机器;盘2 构建的世代拿到盘1 选,会去挂盘2 的 root
  ⇒ 定案:两机各自保留自己的世代,互不污染;两机各自的 ESP 各管各的,从不同步
  ⇒ 铁律:任何两机同步必须排除 /nix/var/nix/profiles/

【僚机(盘2)读写盘1 数据:bind mount】depends 不能省,否则 bind 会早于 /mnt/disk1 执行
  〔2026-10-09 变更:由 ro 改为 rw〕
  〔理由:目标是「任意切换都能当主力」,只读的僚机不算主力;两套系统同一台机不可能同时运行,双写风险不存在〕
  fileSystems."/mnt/disk1" = { device = "UUID=75ebd049-5ac5-43fe-a290-2cddeb7b748e"; fsType = "ext4";
                               options = [ "rw" "nofail" "x-systemd.device-timeout=5s" ]; };
  fileSystems."/s500data"  = { device = "/mnt/disk1/s500data"; fsType = "none";
                               options = [ "bind" "rw" "nofail" ];
                               depends = [ "/mnt/disk1" ]; };

【为什么僚机必须 bind】全入 root 方案下,僚机自己的 /s500data 是个空目录 —— 不 bind 的话,
  带写死路径的脚本会静默写进盘2 的空目录,产生两边同路径不同内容的分叉(比报错危险)。

【原方案里不再需要的东西】
  - 主力 Linux VM 的 16-24GB 内存分配与 VirtIO 磁盘直通
  - 为 Linux VM 服务的剪贴板共享(services.qemuGuest / services.spice-vdagentd)
  - /dataset/s500data 与 /dataset/repos 映射给 Linux VM 的 VirtioFS 配置
  - 注意:VirtioFS 本身仍要用,但服务对象从 Linux VM 改为 Win VM(见「5. 浴火重生」)

【原 Linux VM 职能的落点 —— 改由宿主直接承担】
  IDE 工具链  → 宿主直接装 Linux 原生 VS Code / JetBrains
  Conda 环境  → 宿主内建(不再需要跨 VM 导出 environment.yml 重建)
  日常文档    → 宿主 LibreOffice / OnlyOffice (Docx/Xlsx)
  文件同步    → rsync / Syncthing(数据就在宿主自己的 /s500data,无需跨 VM 映射)

执行清单

前置:计2 已完成(盘1 分区定稿)。本计结束时:盘1 能独立启动、盘2 仍是完整僚机、旧 Win 仍能引导,固件默认引导主机。编号:M = 入主(Move-in)。

    • 动作:从盘2 现行系统 rsync 根到盘1 的 root 分区(排除 /proc /sys /dev /run /tmp /mnt /boot),再在盘1 装引导
    • 复核:盘1 能独立启动到同一套配置;fileSystems 的 / 指向盘1
    • 注意:盘2 原样不动 —— 它是僚机,也是本次操作的退路
    • 已完成(2026-10-09):rsync 29.7 GB / 2,240,069 文件,耗时 3 分 37 秒;随后 nixos-install 装系统+引导
    • 已完成追平(2026-10-09 14:51):追加同步 1.06 GB + 重建,盘1 现为 gen50(jmy71df5…),nix-ld 库数 525
    • 动作:在盘1 的 2 GiB ESP 装 systemd-boot;固件 BootOrder 设盘1 优先
    • 复核:固件启动菜单能同时看到盘1 与盘2 两条 NixOS
    • 注意:盘2 的 1 GiB ESP 原样保留,不写不动 —— 两条引导链互不依赖
    • 已完成(2026-10-09):盘1 ESP 已落 systemd-bootx64.efi + BOOTX64.EFI,EFI 项 Boot0001 Linux Boot Manager(HD(3,GPT,f4bb77fa…,0x800,0x400000))
    • BootOrder 终态(2026-10-09 15:20):0001(盘1) → 0003(Windows) → 0000(盘2) → 000A → 000B,盘1 在首位
    • 附带修复:原 Windows 项的 device path 指向旧 LBA 0x28,已删除重建为正确值 0xc9825ab8(= BWINEFI 真实起始);另删除指向不存在 LBA 的僵尸项 Boot000C
    • 2026-10-11 后续:引导改由 xorboot 承担 —— xorboot 作为默认引导器,进入后选择 主 nixos / 旧 win / 副 nixos(详见 M7 条目)
    • 动作:在盘1 root 内建 /s500data,平铺 vms/ repos/ docker/ 与个人数据目录
    • 复核:路径存在且可写;lsblk 里 /s500data 不出现在 MOUNTPOINT 列(确认它只是目录)
    • 已完成(2026-10-09):/s500data/{vms,repos,docker} 已建,实测确认是普通目录不是挂载点
    • 补做(2026-10-09 16:55):修正属主。当初脚本以 root 跑 mkdir -p,结果是 root:root 0755 ⇒ 用户 cat 写进去被拒(报“权限不够”)。已 chown -R cat:users /s500data,并以 cat 身份实测可写、可建子目录。
      规矩:用 root 脚本建的用户数据目录,建完必须回改属主并以目标用户身份实测一次写入。
    • 动作:在主机(盘1)上逐项落地 —— 桌面与多屏、输入法、代理出网、Hermes、显卡驱动、docker 运行时;并补 nix.gc 等收尾项
    • 复核:日常使用不再需要回到旧 Windows;每个改动都有可回退的 generation
    • 注意:完善在主机上做,不再在僚机上提前做 —— 方向是主机 → 僚机
    • 改为用户手工执行(2026-10-09 定案,原 〔Hermes〕 作废)。完成判据 —— 以下五条全达成才算 M4 完成:
      ① 确认主力 NixOS 可引导
      ② 确认主力是默认引导
      ③ 主力与僚机各自保留各自的世代记录、互不污染〔原表述「世代列表完全相同」于 2026-10-09 撤回:两机 UUID 不同,同一世代物理上不可能同时适配〕
      ④ 手动重建 /s500data 数据:chrome,git新私钥,github新私钥,hermes分配新的key,看板项目全部修改为nixos本地地址
      ⑤ 逐个安装、恢复开发环境:miniconda替换为uv,微信,qq,企业微信,腾讯会议,VSCode,trae,重建mysql数据库可以从压缩包备份里拿出来
    • Hermes 已备妥的前置(不计入完成):桌面/多屏/输入法/代理/Hermes/显卡驱动/docker 运行时由克隆自带;nix.gc(weekly / --delete-older-than 30d)已加并在盘1 chroot 核验生效
    • 判据③ 已于 2026-10-09 达成:盘1 已清掉从盘2 镜像来的 gen1–49,现只剩一条自己的世代(gen50,default 指向它,init=/nix/store/jmy71df5… 就在盘1 自己的 store 里)——每条都能在盘1 启动。盘2 完整保留自己的 21 条。两边各自独立。
    • 盘1 清理做法:删 /nix/var/nix/profiles/system-N-link(只删链接,store 路径仍在、可逆)→ 重跑 nixos-install --root 重建菜单条目
    • 为什么必须各管各的(实测证据):盘1 上从盘2 镜像来的 gen45/46/47/49 的 /etc/fstab 写的是 a4c3948c(盘2 root)与 CAE3-699F(盘2 ESP);而盘1 自己的 gen50 写的是 75ebd049 / 8BE3-50EF
    • docker 口径(用户先前定案):运行时留在 root(不改 data-root),业务数据与配置映射到 /s500data/docker
    • 【2026-10-11 用户判定:基本完成】逐条对照:
      ① 可引导 ✅(多次实机启动;世代已收敛为 50/78/87/93/101/102 等,全部盘1 自建)
      ② 默认引导 ✅(改由 xorboot 承担,默认项即主机)
      ③ 世代各自独立 ✅(盘1 自建、清掉镜像世代;盘2 保留自己那套;同步一律排除 /nix/var/nix/profiles/)
      ④ /s500data 数据重建 ✅:Chrome 定案、git/github 新私钥(/s500data/GitRepo/sshkey-nixos)、Hermes 新 key、看板/skills/配置类路径全部改为 NixOS 本地路径
      ⑤ 开发环境 ✅:miniconda → uv(环境 ju314 / py313 / py313B / py368,含两段私有包处理);微信、QQ、企业微信、腾讯会议、VSCode、trae 已装;mariadb 容器(/s500data/docker/mariadb,127.0.0.1:3306)已起,库 forgejo_db / kc_portfolio / mpa_db / test_db
      额外完成(超出原清单):模块分层与「import 即生效」收口、世代记 52→6 并内容式命名、代理收敛(proxy.nix 只管 system 面 + docker/git 各自接线)、docker 完全 rootless 化、rime 数据迁至 /s500data/softnixos/usr/rime 并 nix 接线、KDE Connect 配对、跨环境 Jupyter 内核注册、uv 环境重建脚本
      判定:M4 基本完成(用户原话),遗留细项按实际使用继续补
    • 动作:盘2 配置里加两个挂载:/mnt/disk1(盘1 root 分区)、/s500data(bind 到 /mnt/disk1/s500data,depends = [ "/mnt/disk1" ])
    • 复核:僚机启动后 cat /proc/mounts | grep s500data 显示 bind;ls /s500data 能看到盘1 的数据;盘1 不在时(nofail)僚机仍能正常启动
    • 注意:depends 不能省
    • 已完成并实测(2026-10-09,最终为 rw):
      · /mnt/disk1 = nvme1n1p4 ext4 rw
      · /s500data = /dev/nvme1n1p4[/s500data] ext4 rw
      · 读写测试:touch /s500data 成功 ✅(早期版本的 ro 写测试是被拒的,已随定案改掉)
    • 实现细节(踩到的坑):
      1. bind 挂载也必须写 fsType,否则直接报 The option fsType was accessed but has no value defined ⇒ 用 fsType = "none"
      2. 挂载点目录必须真实存在,补了 systemd.tmpfiles.rules = [ "d /s500data 0755 root root -" ] 兜底
      3. ext4 硬规则:同一启动周期内某设备一旦被 rw 挂载过,就无法再以 ro 挂载(报 Can't mount, would change RO state);反向(先 ro 后 rw)同样不行,会得到只读挂载 ⇒ 给盘1 做写入前先停掉这对挂载
    • 本计不再保留 M5 条目;相应工作(僚机/备份/兜底相关)在「6. 不败之地」里继续追踪。
    • 动作:依次实际启动三项并确认可进系统 —— ① 盘1 NixOS 主机(默认)② 盘2 NixOS 僚机 ③ 旧 Win 物理系统
    • 复核:不接盘2也能从盘1 独立进入工作环境;三项目的均无手动救场;固件默认项指向主机
    • 注意:盘2 与盘1 的 /home 各走各的(用户后续用 goodsync 脚本处理)
    • ① 盘1 NixOS 主机(默认)—— 已实测达成 ✅(2026-10-09 16:30 开机):/ = nvme1n1p4、/boot = nvme1n1p3、efibootmgr 的 BootCurrent = 0001(盘1 那条),未手动救场。当前系统 gen50。
    • 剩余 ② 盘2 僚机、③ 旧 Win 物理系统 —— 待实际重启验证(② 顺便验收 M6 的 bind 在僚机上表现)
    • 预期:开机直接进盘1 的 systemd-boot,只有一个条目 nixos-generation-50.conf
    • 与 M4 的顺序关系:判据①②实际上就是 M7 的前两步,先验完这两步再进 M4 的 ④⑤
    • 【2026-10-11 用户判定:已完成】引导方式已由 systemd-boot 直启改为 xorboot 默认引导器:xorboot 先起,菜单为 主 nixos / 旧 win / 副 nixos 三项,默认进主机;用户已实机确认三项均可进入。

2026-10-09 执行记录与遗留

  • 盘1 现可独立启动:fstab 已指向盘1(75ebd049 / 8BE3-50EF)、gen50 的 init= 在盘1 root 上真实存在、nix-ld 525 库全齐(追平前是 437)。
  • 世代:各自独立,不再互相污染(M4 判据③已满足):盘1 已清掉 20 条镜像来的旧世代,现只剩 gen50 一条(本机构建、default 指向它);盘2 保留自己完整 21 条(含它自己的 gen50)。
    清理方式:删盘1 的 system-*-link(只删链接,store 路径仍在、可逆)+ 重跑 nixos-install --root 重建菜单。
    铁律:以后任何两机同步都必须排除 /nix/var/nix/profiles/。
  • 僚机对盘1 数据:已由 ro 改为 rw:/mnt/disk1(盘1 root)与 /s500data(bind)均 rw,实测可写。理由:目标「任意切换都能当主力」,只读的僚机不算主力;而两套系统同一台机不可能同时运行 ⇒ 无双写风险。
  • 克隆保真度抽查(权限/属主/内容):/home/cat 两边均 cat:users 700;/etc/shadow 均 root:shadow 640;/ 顶层均 19 项;/etc/nixos 盘1 163 / 盘2 162 个文件(差的是盘1 侧的备份文件)。
    /home/cat 内容差异:203 个普通文件 / 626 MB —— 是克隆之后两机各自产生的变化,属用户自制 goodsync 脚本的范畴,Hermes 未动。
  • BootOrder 监测:nixos-rebuild 确实会动固件启动项。本次由 0001,0002,0000,000A,000C,000B 变为 0001,0003,0000,000A,000B(Windows 项编号由 0002 转 0003,并新增盘1 ESP 的 fallback 项 000B)。盘1 仍在首位 ✅,但每次 rebuild 后都该核一遍。
  • clock 异常:盘2 本次开机瞬间系统钟快 8 小时(journal/utmp 记 22:29,真实 14:29),根因是 RTC 里存的是本地时间(Windows 默认写法)而被当 UTC 读;NTP 随后校正。根治在 Windows 侧设 RealTimeIsUniversal=1。
  • 权限审计(2026-10-09 16:57,回答「克隆过来的目录是不是都成 root 了」):
    · / 顶层属主权限(盘1 vs 盘2):18 项完全一致;3 项差异全是挂载态假象——boot(vfat 挂着 ⇒ fmask=0077 显 700;未挂载时是空目录 755)、proc/sys(虚拟 fs 555 vs 空目录 755)。
    · /home 下 root 属主条目:盘1 129 vs 盘2 128,差的那一条是 Hermes 写的交接说明 ⇒ 克隆保真。
    · 那 128 个 root 条目的来源:不是克隆造成的——是先前以 root 跑脚本留下的(~/.hermes/cache/scratch/boot-entries-backup-*、~/www/mihomo/data/*、plasma look-and-feel 符号链等),克隆如实复制了。
    · 统一处置:chown -R cat:users ⇒ 128 → 0;6 个关键路径(/s500data 及其三个子目录、.hermes/tmp、cache/scratch)以 cat 身份实测均可写。
    · 不该改:/etc /nix /var /usr /root 本就应是 root:root(NixOS 正常布局)。
    · 故意保留 root 的一处:盘2 底层那个真实的 /s500data 保持 root:root 755 —— 平时被 bind 盖住不可见;万一 bind 失败,root 属主能挡住 cat 的静默写入,正是设计要防的「同路径不同内容分叉」。