メインコンテンツまでスキップ

SSHFS 多云端磁盘自动挂载

生产级目录设计

只保留一个 launchd 服务:com.fa.cloud-disks_bin/bootstrap.sh 会按当前 cloud-disks 目录生成路径稳定的 ~/Library/LaunchAgents/com.fa.cloud-disks.plist;该服务周期执行 _bin/mount_all.sh,每次扫描所有 cloud-disks/*/cloud-disk.conf,所以新增云盘只需要新增一个目录和配置文件,下一轮自动生效,不需要再新增服务。

cloud-disks/
├── _bin/
│   ├── bootstrap.sh                 # 生成并加载 ~/Library/LaunchAgents/com.fa.cloud-disks.plist
│   ├── mount_all.sh                 # 扫描所有云盘配置
│   └── sshfs_mount.sh               # 单盘 one-shot runner
└── fa.intranet.company/
    └── cloud-disk.conf              # 单盘配置;新增云盘只复制这一类目录
  • cloud-disk.conf 只需要配置 REMOTE;磁盘 ID、默认挂载点、默认日志名都从 SSH host 自动推导。
  • mount_all.sh 每 60 秒扫描一次 cloud-disks/*/cloud-disk.conf
  • 新增云盘目录后无需新增 plist;下一轮扫描自动尝试挂载。

配置字段

最小配置:

REMOTE="fa.intranet.company:/data/nfs_share/dev"

可选覆盖项:

SSH_PORT="22"
IDENTITY_FILE="/Users/fa/.ssh/id_ed25519_sshfs_fa"
LOG_DIR="/Users/fa/Library/Logs/cloud-disks"
MOUNT_TIMEOUT="30"
PROBE_TIMEOUT="5"
UNMOUNT_TIMEOUT="10"
EXTRA_SSHFS_OPTS=""

配置由 runner 按严格 KEY="VALUE" 文本解析,不会被 source 执行;DISK_IDREMOTE 的 SSH host 自动推导;默认挂载点为 /Users/fa/mount/cloud-disks/${host},默认日志为 /Users/fa/Library/Logs/cloud-disks/${host}.log

首次准备

# 1. 安装能提供 sshfs 命令的 macOS 实现
brew install macos-fuse-t/homebrew-cask/sshfs-fuse-t

# 2. 确认 sshfs 位于 runner 允许的绝对路径之一
ls /opt/homebrew/bin/sshfs /usr/local/bin/sshfs

# 3. 准备非交互 SSH key
mkdir -p ~/.ssh
chmod 700 ~/.ssh
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_sshfs_fa -C "cloud-disk-fa.intranet.company" -N ""
chmod 600 ~/.ssh/id_ed25519_sshfs_fa

# 4. 首次交互连接,接受并固定 host key;如果提示认证失败,先把公钥加入远端 authorized_keys
ssh -i ~/.ssh/id_ed25519_sshfs_fa -p 22 fa.intranet.company true

# 5. 最终认证检查:不得出现密码、passphrase、host key 交互提示
ssh -i ~/.ssh/id_ed25519_sshfs_fa -o BatchMode=yes -o NumberOfPasswordPrompts=0 -p 22 fa.intranet.company true

启用自动挂载

find ./cloud-disks -name cloud-disk.conf -exec chmod 600 {} \;
mkdir -p /Users/fa/mount/cloud-disks /Users/fa/Library/Logs/cloud-disks
chmod 700 /Users/fa/Library/Logs/cloud-disks

# 本地验证,不会挂载
/bin/zsh ./cloud-disks/_bin/sshfs_mount.sh ./cloud-disks/fa.intranet.company/cloud-disk.conf --self-test

# 只需要 bootstrap 一次;脚本会生成路径稳定的 ~/Library/LaunchAgents/com.fa.cloud-disks.plist
/bin/zsh ./cloud-disks/_bin/bootstrap.sh ./cloud-disks
launchctl print gui/$(id -u)/com.fa.cloud-disks

新增云盘

NEW_HOST="aliyun.example.com"
mkdir -p "./cloud-disks/${NEW_HOST}"
nano "./cloud-disks/${NEW_HOST}/cloud-disk.conf"
chmod 600 "./cloud-disks/${NEW_HOST}/cloud-disk.conf"

cloud-disk.conf 最少只写:

REMOTE="aliyun.example.com:/data"

保存后不需要再 bootstrap;com.fa.cloud-disks 下一轮运行时会自动扫描到它。

运维与调试

# 查看全局扫描服务是否已加载,以及上次退出状态
launchctl print gui/$(id -u)/com.fa.cloud-disks

# 立即触发一次扫描;新云盘无需重新 bootstrap,用这个命令加速下一轮
launchctl kickstart -k gui/$(id -u)/com.fa.cloud-disks

# 卸载全局服务
launchctl bootout gui/$(id -u)/com.fa.cloud-disks

# 查看扫描器本身输出;如果没有任何单盘日志,先看这里
mkdir -p /Users/fa/Library/Logs/cloud-disks
tail -f /Users/fa/Library/Logs/cloud-disks/scanner.log

# 查看单盘日志;文件名默认等于 REMOTE 的 SSH host
tail -f /Users/fa/Library/Logs/cloud-disks/fa.intranet.company.log

# 手动跑一次扫描,直接看 stderr;适合排查 plist 路径、配置权限、runner 不可读
/bin/zsh ./cloud-disks/_bin/mount_all.sh ./cloud-disks

# 手动跑单盘,适合排查 REMOTE、SSH key、sshfs、挂载点和权限问题
/bin/zsh ./cloud-disks/_bin/sshfs_mount.sh ./cloud-disks/fa.intranet.company/cloud-disk.conf

# 查看挂载状态
mount | grep cloud-disks

如果 kickstart 后没有挂载也没有单盘日志,按顺序查:launchctl print 是否显示服务存在、scanner.log 是否有 launchd/stdout/stderr 输出、mount_all.sh 手动扫描是否能读到配置、cloud-disk.conf 是否为当前用户所有且权限不超过 600

手动卸载

# 普通卸载
/usr/sbin/diskutil unmount /Users/fa/mount/cloud-disks/fa.intranet.company

# 仍然失败时再手动强制卸载;自动 runner 不会执行强制卸载,避免数据丢失
/usr/sbin/diskutil unmount force /Users/fa/mount/cloud-disks/fa.intranet.company
/sbin/umount -f /Users/fa/mount/cloud-disks/fa.intranet.company

设计要点

  • 只有一个 launchd 服务:com.fa.cloud-disks
  • 新增云盘只新增 cloud-disks/${ssh-host}/cloud-disk.conf
  • 目录名、挂载点、日志名默认由 REMOTE 的 SSH host 推导,不让用户再自定义 DISK_ID
  • mount_all.sh 负责扫描所有云盘配置;sshfs_mount.sh 负责单盘挂载。
  • runner 是 one-shot:已挂载且远端来源匹配才退出;未挂载才尝试挂载一次。
  • 自动路径只执行普通卸载;普通卸载失败时记录占用进程,不自动强制卸载。
  • 日志目录权限为 700,日志文件权限为 600,且日志目录、日志文件和挂载点路径中不允许出现软链接。
  • allow_other 不在默认自动路径里;只有明确需要本机其他用户访问挂载目录时才在 EXTRA_SSHFS_OPTS 中显式追加。