来源:notes/topics/environment_backup_restore/nas-vm-sync.md

宿主机存文件、虚拟机跑程序:NAS ↔ 虚拟机同步方案

日期:2026-09-18 · 状态:已跑通并挂上定时任务

1. 分工与数据流

  • 虚拟机是唯一的运行环境,所有需要执行的东西都放在那里
  • 虚拟机从 GitHub 拉取代码(git pull),它是代码的消费端
  • 宿主机(NAS)只从虚拟机同步,单向镜像,不反向推送
编辑(本机) → GitHub → 虚拟机(拉取、运行) → NAS(单向同步、仅存储)

方向是固定的:VM 是源,NAS 是目的地。NAS 不参与代码分发

2. 拓扑

飞牛 fnOS 宿主机 andy6
  10.71.48.167 (ZeroTier) / 192.168.110.147 (内网)
  └── /vol1/1000/andy/1351800/cc-connect-work-space/    ← 只存文件
        ▲   SMB 共享名 andy,卷剩余 ~386 GB
        │   rsync over SSH(免密),单向:VM → NAS
        │
  libvirt 虚拟机 192.168.110.91 (Deepin 25)
      /home/admin/code/cc-connect-work-space/            ← 运行环境 + 拉代码

宿主机 andy 共享同时暴露为 SMB(//192.168.110.147/andy),Windows/macOS 也能直接访问。

3. 为什么用 rsync over SSH

先评估过其他路径,都不如它:

方案 结论
挂载 SMB(mount.cifs) ❌ 两台机器都没有免密 sudo,备份机还没装 cifs-utils
挂载 NFS ❌ 同上,且备份机无 nfs-common
rclone(SMB 后端) ⚠️ 用户态可行,但从备份机下载 rclone 实测只有 ~33 KB/s,20MB 下了 10 分钟没下完
rsync over SSH ✅ 两边都自带 rsync,免密打通后即用,增量、无需 root、无需下载

4. 凭据

  • 备份机的 ~/.ssh/id_ed25519.pub 已写入宿主机 ~/.ssh/authorized_keys
  • 撤销方式:删掉宿主机该文件里对应的那一行即可
  • 宿主机 SSH/SMB 账号:admin

5. 脚本

位于虚拟机 /home/admin/code/cc-connect-work-space/nas-sync/:

脚本 方向 用途
sync-push.sh 虚拟机 → NAS 日常唯一操作,把工作区镜像到 NAS
sync-pull.sh NAS → 虚拟机 仅灾难恢复时手工用,不属于日常流程

两者都支持 --dry-run 预览。

push 的关键行为:

  • --delete,NAS 侧与虚拟机严格一致
  • 但删除是软删除 —— 被删文件移入 /vol1/1000/andy/1351800/.rsync-trash/<时间戳>/,可随时找回
  • 排除 .venv/node_modules/__pycache__/(这些应当重建而非同步)

sync-pull.sh 存在的理由:代码本身可以从 GitHub 重新 clone, 但未提交的改动、nas-sync/ 工具目录、以及工作区里的非 git 内容只能从 NAS 找回。

6. 实测结果

项目 结果
首次全量(543 MB / 2024 项) 2.66 秒,~233 MB/s
增量(无变化) 秒级,零传输
新增文件 → push NAS 正确出现
删除文件 → push NAS 正确移除,且进入 .rsync-trash
4 个仓库 commit 校验 全部与虚拟机 HEAD 一致

速度之所以这么快,是因为虚拟机跑在这台宿主机上,同一物理机内通信近乎本地磁盘速度。

7. 定时任务

虚拟机上的用户 crontab,每 30 分钟 push 一次:

*/30 * * * * /bin/bash -lc "cd /home/admin/code/cc-connect-work-space/nas-sync && bash sync-push.sh >> /home/admin/.local/log/nas-sync.log 2>&1"

日志:~/.local/log/nas-sync.log

8. 注意事项

  • 不要在 NAS 侧直接改文件 —— 它是纯镜像,改动会被下次 push 覆盖。 要改就在虚拟机上改。
  • 软删除目录会持续增长,.rsync-trash/ 需要定期清理(尚未加自动清理)。
  • 这套同步解决的是「文件放在有冗余的存储上」,不等于云备份。 虚拟机损坏时有两条恢复路径:从 GitHub 重新 clone(代码), 或 sync-pull.sh 从 NAS 拉回(含未提交内容); 但 NAS 本身损坏时这份数据同样会丢,跨地域的云备份仍需另做 (见 cloud-options-research.md)。

9. 刻意的设计选择

9.1 git pull 不自动化

虚拟机上的 git pull 故意保持手动,不放进定时任务。

原因:虚拟机上经常有 agent 正在写代码,自动 git pull 会在它写到一半时 替换工作区文件,导致改动丢失或产生难以排查的冲突。拉取时机必须由 「当前没有任务在跑」这个人类判断来决定。

要自动化时请先想清楚这一点 —— 这是有意为之,不是遗漏。

9.2 保留 sync-pull.sh

虽然名为”灾难恢复”,但它覆盖了 GitHub 覆盖不到的部分: 未提交的改动、nas-sync/ 工具目录、工作区里的非 git 内容。 所以保留,但不纳入日常流程。

9.3 定时任务只做 VM → NAS

sync-push.sh 对虚拟机的文件是只读的(只读源、只写 NAS), 因此它在 agent 工作时也不会干扰,可以安全地每 30 分钟跑一次。