挂载迁移:容器数据怎么放到外置盘
一句话结论
~/Library/Containers/ 和 ~/Library/Group Containers/ 里的数据(微信聊天记录、QQ 音乐缓存、App Store 应用的数据)不能用符号链接搬走。AppPorts 1.9.0 起改为:在外置盘上新建一个专用的 APFS 数据卷,把数据复制进去,再把这个卷挂载到原来的目录上。应用看到的路径没变,签名没动。
前提三条:外置盘是未加密的 APFS;第一次打开应用时点允许授权框;打开应用前先连接外置盘。
什么时候会用到
在「数据目录」→「应用数据」里选中一个应用,Containers 和 Group Containers 分组下的目录,按钮是「挂载迁移」而不是「迁移」。其它分组(Application Support、缓存等)和工具目录、自定义目录照旧用符号链接迁移,不受影响。
为什么容器目录特殊、为什么不看主程序是不是沙盒,见容器数据、沙盒与签名身份。
点「挂载迁移」之后
AppPorts 先只读检查一次外部存储,不改动任何东西,再按结果给出下一步:
| 检查结果 | 你会看到 | 可以怎么做 |
|---|---|---|
| 未加密的 APFS,空间足够 | 迁移说明:本机能释放多少空间、第一次打开要点允许、平时要连着盘 | 点「迁移数据」开始 |
| exFAT、NTFS、HFS+ 等 | 「这块外部存储是 exFAT 格式」 | 保留现状;选择其他位置;查看准备方法 |
| 加密的 APFS | 「这块外部存储已加密」 | 保留现状;改选未加密的 APFS 位置 |
| 空间不足 | 需要多少、还剩多少 | 清理空间后重新检查,或选择其他位置 |
| 没选外部存储 / 没连接 | 提示选择或连接外部存储 | 选择外部存储、重新检查 |
不能迁移时什么都不用改。 应用本体、Application Support、缓存、工具目录照常可以迁移到这块盘,只有容器数据留在本机,应用照常使用。以后想迁移时,按准备 APFS 外置盘的方法准备好再来。
为什么加密的 APFS 盘暂不支持
挂载迁移新建的数据卷不会继承原卷的密码。照常迁移的话,原本在本机受 FileVault 保护的聊天记录会落在一个没有密码的卷上。自动解锁和密码管理完成之前,AppPorts 不做这种悄悄降级。详见加密的外置盘。
迁移前
- 请先把 AppPorts 放进「应用程序」文件夹,再从那里打开。 登录代理需要一个持久有效的程序路径。从下载目录或 DMG 直接运行时,macOS 可能使用临时的 App Translocation 路径;检测到这种路径时,AppPorts 会阻止新的挂载迁移并提示安装。移动或更新 AppPorts 后请打开一次,让它校准代理路径;路径不变时不会重新加载代理。
- 完全退出要迁移的应用。 AppPorts 会检查,正在运行时不允许迁移。
- AppPorts 需要完全磁盘访问权限。 把卷挂到容器路径这个动作本身受系统管控,没有权限会失败。
- 想想备份。 和所有数据迁移一样,重要数据建议先自行备份一份。迁移后这部分数据在外置盘上,而 Time Machine 通常不备份外置盘,需要时请在「系统设置 › 通用 › 时间机器」的选项里确认这块盘在备份范围内。
迁移过程中发生了什么
- 在外置盘的 APFS 容器里新建一个卷,建好后不自动挂到
/Volumes。卷名形如AppPorts-<Bundle ID>-<目录名>-xxxxxx,和盘上其它卷共用剩余空间,不用指定大小。 - 把新卷临时挂到
~/Library/Application Support/AppPorts/mounts/下,用 AppPorts 的复制器把目录内容复制进去,在卷根写一个.appports-mount-metadata.plist标记,然后卸载。 - 把原目录改名为同卷安全备份,在原路径新建一个空目录,把卷挂上去,校验挂上的确实是这个卷。
- 写入挂载记录
~/Library/Application Support/AppPorts/container-mounts.plist,安装登录时自动重挂的代理,最后删除安全备份。
外置盘空间不够时,在建卷之前就停下。迁移尚未完成就失败时,AppPorts 会尝试回滚;如果无法安全完成回滚,会保留副本并说明保留路径。
如果迁移已经完成,只是最后清理本地安全备份失败,挂载的数据仍可正常使用。AppPorts 会明确显示尚未清理的备份路径,无需重新迁移。
迁移完成后,mount 命令能看到卷直接挂在容器路径上:
/dev/disk7s5 on /Users/<user>/Library/Containers/com.tencent.xinWeChat/Data/Documents/xwechat_files (apfs, local, nodev, nosuid, journaled, noowners, nobrowse)迁移后第一次打开应用
系统会弹出「"某某"想要访问可移动宗卷上的文件」,点允许。这是 macOS 对数据落在外置盘上的正常检查,只弹一次。
点了拒绝,应用会当作没有数据,显示空白。补救:系统设置 → 隐私与安全性 → 文件和文件夹(或「可移动宗卷」),找到该应用打开开关,或者在终端执行 tccutil reset SystemPolicyRemovableVolumes <Bundle ID> 让它下次重新询问。
/System/Applications 下的系统应用不会弹框,直接静默拒绝。AppPorts 本来就不迁移它们。
日常使用
打开应用前先连接外置盘。 盘不在时,挂载点只是一个被锁住的空目录(权限 000),应用会看到空数据,不会报错,也不会把新数据写到本机形成两份。连上外置盘后 AppPorts 会自动把卷接回来,数据就回来了。
Finder 里平时看不到这些数据卷。 AppPorts 挂载时带 nobrowse 选项,数据卷不出现在 Finder 边栏和桌面。外置盘刚接入的一两秒内,macOS 可能先把卷自动挂到 /Volumes 并短暂显示图标,AppPorts 接回后就会消失。用早期版本迁移、目前仍显示在 Finder 里的卷,AppPorts 下次启动或外置盘下次接入时会原地隐藏,不需要卸载重挂。「磁盘工具」里仍能看到名为 AppPorts-… 的卷,不要在那里抹掉或删除它们,那就是迁移过去的数据。
拔盘前先退出应用,再在 AppPorts 里点「卸载」或在 Finder 里推出外置盘。 直接拔盘的话,最后几秒钟的写入可能丢失,数据库文件可能需要修复;我们做过拔盘测试,APFS 卷这种情况下丢的只是最后几个事务,见实验记录:拔盘测试。
什么时候自动接回:
- AppPorts 运行时:启动时以及每次有卷接入系统时,自动把在线但没挂载的记录挂回去。
- 没开 AppPorts 时:迁移成功后 AppPorts 会安装一个登录代理,登录时以及每次外置盘接入时静默挂回,然后退出。最后一条记录被还原后代理自动卸载。
- 开机后立刻打开应用,仍可能读到十几秒的空目录(系统把登录代理排在登录项之后启动),退出应用再打开即可。兜底的是挂载点一直保持为空:应用就算先起来了,也只读到空目录,不会把新数据写到本机形成分叉,等卷挂回来自己就恢复了(微信实测三次都能恢复)。
macOS 12 等旧系统需要输入管理员密码
macOS 27 允许普通用户把卷挂到自己目录下的路径;macOS 12 实测不允许。AppPorts 遇到这个错误会弹系统的管理员密码框重试,一次迁移通常只问一次。登录代理没有界面,没法弹密码框,所以在这类系统上登录后不能自动重挂,需要打开 AppPorts(它会提示输密码)或在「应用数据」页手动点「挂载」。13 到 26 之间从哪一版开始放开,还没有逐一验证。
状态与操作
| 状态 | 含义 | 可用操作 |
|---|---|---|
| 已挂载 | 卷挂在原目录上,应用正常读写 | 卸载、还原 |
| 待挂载 | 卷在线但没挂载(刚插盘、或手动卸载过) | 挂载、还原 |
| 外置盘未连接 | 找不到这个数据卷,多半是外置盘没连接 | 连上外置盘,AppPorts 会自动接回;已经连接仍显示时,见下方排查 |
还原会把卷上的数据复制回本机,然后删除卷和记录,外置盘要保持连接:
- 开始前检查本机剩余空间,不够时直接停下,卷和记录原样保留。
- 复制完成后,AppPorts 先把挂载记录转为待清理信息,防止自动重挂,再切换回本地目录。它只删除卸载后留下的空挂载点,不会递归删除任何目录。
- 如果切换回本地目录未完成,暂存的本地副本和外置卷都会保留。本地副本仍在同一目录下名为
.appports-restore-staging-…的隐藏文件夹里;请按 AppPorts 显示的保留路径和提示处理。 - 如果本地目录已经恢复,只是删除外置卷或更新清理记录失败,AppPorts 会明确提示还原完成但清理未完成。可在数据目录页重试清理,不要重复迁移或还原。
无法确认副本是否仍在时,可选择「仅移除清理记录」;这不会删除本地备份或外置卷,也不会重新挂载,仍存在的副本需自行清理。
如果挂载点目录里出现了本地文件(比如应用在盘不在时想办法写了东西),AppPorts 会拒绝在上面挂载,免得盖住这份数据。把文件移走再挂载。
卸载或移动 AppPorts 之前
挂载迁移靠 AppPorts 的登录代理在每次登录后把卷接回来。删除 AppPorts 之前,先在「应用数据」里把挂载迁移的目录「还原」回本机。 不还原就删除 AppPorts 的话,数据仍完好地留在外置卷上,但登录后没人把它接回来,应用会看到空目录;重新安装 AppPorts 并打开一次即可恢复。
只是更新或移动 AppPorts 的话,打开一次新版本即可,它会自动把代理指向新的程序位置。
排查
| 现象 | 处理 |
|---|---|
| 状态是「外置盘未连接」,但盘已经连上 | 在「磁盘工具」里确认外置盘上还有 AppPorts-… 的卷。卷还在:点 AppPorts 工具栏的刷新,或重新插拔一次外置盘。卷已被删除:这个目录的数据已经不在外置盘上,只能从备份恢复 |
| 应用打开后是空的 | 先看该目录是不是「已挂载」;不是就连上外置盘或点「挂载」。是「已挂载」仍为空,检查第一次打开应用时有没有点了拒绝 |
| 点「挂载」提示挂载点不为空 | 挂载点目录里有本地文件。确认这些文件是什么,把它们移走后再挂载 |
| 迁移或还原提示后台正在连接外部存储 | 登录代理正在挂载,稍等几秒再点一次 |
为什么不用磁盘映像
exFAT 用户不少,我们认真试过在 exFAT 盘上放一个 APFS 磁盘映像(sparsebundle)再挂载的办法。它不弹授权框、性能相当、任何格式都能放,看起来更好。
然后做了拔盘测试:写入数据的时候直接拔 U 盘。APFS 卷只丢最后几个事务,数据库能修复;磁盘映像两轮都整个打不开,里面的数据全部失联。原因是映像自己的"目录簿"每隔几秒重写一次,拔盘正好撞上写一半,整个映像就成了没有目录的碎片。完整数据见实验记录:拔盘测试,面向用户的解释见为什么外置盘必须是 APFS。
所以只支持 APFS 卷。
技术细节
自动重挂的时序
- 登录代理是
~/Library/LaunchAgents/com.shimoko.AppPorts.container-mount.plist,运行AppPorts --mount-agent。它同时监听/Volumes:外置盘一挂上系统就再跑一次。实测这次开机(2026-09-22)的顺序是「登录完成 → 4.6 秒后代理启动 → 18 秒后两个卷挂回容器路径」,比早先版本快了约 19 秒,但登录项里的应用约 3 秒就起来了。时间线和为什么不能更早,见实验记录:开机前挂载。 - 盘迟迟没出现时,代理不会只试一遍就走:它在进程内盯着
/Volumes,等下一次真实变化再试,而不是按固定间隔轮询 —— 开机时系统很忙,外置盘可能好几分钟后才被识别(实测 2026-09-23 那次是开机后 2 分 33 秒),定时轮询要么在最忙的时候空转diskutil,要么错过应用启动。等不到事件时每 20 秒兜底复查一次,总窗口 180 秒;等卷期间不占锁。实测:卷出现到挂载完成约 1 秒。 - 挂载前先看一眼
/Volumes/<卷名>:开机和插盘时系统几乎总是先把卷挂到那里,用statfs加一次卷根标记读取(微秒级)就能确认是不是我们的卷,省掉一次diskutil info—— 2026-09-23 开机实测那次查询花了 9 秒(登录后系统正忙),是整条链路上最贵的一步。认不出来(卷名被系统改名、标记缺失)才退回diskutil查询。 - 挂载之后要校验:
diskutil mount -mountPoint有个坑 —— 卷已经被系统挂上时(开机/插盘时 macOS 常抢先把它挂到/Volumes),它会忽略挂载点参数、照样打印mounted并返回 0。所以每次挂载后都确认卷真的落在目标路径上;没落到位就重新查一次卷挂在哪,把它从/Volumes卸下来再挂(最多 3 轮)。2026-09-21 和 09-23 各出过一次这种情况,当时判定为失败并放弃,微信随后读到空目录。 - 只动自己的卷:挂载、卸载、还原之前都核对挂载点上的 Volume UUID 是否与记录一致;路径上挂着别的卷时直接停下,不会卸载、覆盖或删除它。
- 代理和 AppPorts 共用一把跨进程锁(
~/Library/Application Support/AppPorts/operation.lock):AppPorts 正在做挂载迁移、卸载或还原时,代理会先让路(最多等 120 秒),等不到就跳过这一轮,留到下次插盘或下次登录。反过来,AppPorts 拿不到锁时不会开始操作,只提示稍后重试。锁只在每一轮挂载动作期间持有 —— 代理等卷的那几分钟不占锁,不会卡住你在 AppPorts 里的操作。 - 迁移记录文件读不出来时,AppPorts 不会把它当成空记录覆盖写入,而是保留原文件并拒绝新增或删除记录。
- 不用
/etc/fstab:实测UUID=形式挂不上,设备号形式插拔后会变,而且开机由 root 挂载同样受权限管控。
卷上的 Spotlight 索引
系统会把挂在容器路径上的卷当成普通外置卷,给它建一份自己的索引。实测两个微信卷的 .Spotlight-V100 合计 110 MB,而且开机后还在重写。这些是应用数据,系统索引没有任何用处,所以:
- 建卷后往卷根写一个空的
.metadata_never_index,mds 会跳过整个卷;写入时如果系统已经建好.Spotlight-V100,一并删掉。 - 标记跟着卷走:换盘位、还原挂载迁移都不用重做。
- 在这个版本之前迁移过的卷,下次挂载时自动补上标记。
- 标记不写回本机目录:还原时
.metadata_never_index和.fseventsd、.Spotlight-V100一样被跳过。
自查命令
# 挂载记录
plutil -p ~/Library/Application\ Support/AppPorts/container-mounts.plist
# 当前挂载
mount | grep Containers
# 登录代理
launchctl print gui/$(id -u)/com.shimoko.AppPorts.container-mount
# 卷根有没有防索引标记;系统的索引状态应为 Indexing disabled
ls -la "<挂载点路径>/.metadata_never_index"
mdutil -s "<挂载点路径>"
# 手动重挂(要在有完全磁盘访问权限的终端里执行;旧系统前面加 sudo)
diskutil mount nobrowse -mountPoint "<挂载点路径>" <Volume UUID>相关文档
- 为什么外置盘必须是 APFS
- 容器数据、沙盒与签名身份
- macOS 27 升级说明:旧方案迁移过的用户怎么切换
- 实验记录:挂载点:本功能依据的原始实验
