| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| AppPorts1.9.3.dmg | 2026-10-20 | 11.1 MB | |
| README.md | 2026-10-19 | 22.8 kB | |
| v1.9.3 source code.tar.gz | 2026-10-19 | 11.6 MB | |
| v1.9.3 source code.zip | 2026-10-19 | 11.9 MB | |
| Totals: 4 Items | 34.5 MB | 0 | |
AppPorts v1.9.3 Build 43 更新说明 (in English) · 直链下载 · 赞助
总结
这次更新要解决的是「等」:把一个大应用搬到外盘,本来该是几分钟的事,却可能要等上几个小时,最后还可能失败。
原因不在外盘的速度,而在校验的方式。应用包被套上了一份为数据目录设计的严格契约,几乎所有时间都花在「证明搬对了」上;而应用包本来就带着开发者的签名清单,系统校验这份清单更严格,也快得多。现在已签名应用改用这份证明,数据目录的严格契约一个字没改。
搬得动之后,还要搬得对。以前把应用迁到 exFAT 这类存不住全部信息的盘会失败,现在可以完成,但会先告诉你这块盘存不住哪几项,由你决定要不要继续;还原一份早就降级的副本时也会先提醒——损失发生在迁出那一刻,还原补不回来。应用迁移和还原的进度窗口也换成了和数据目录一样的阶段提示,长校验、长落盘不再是「卡在 100% 却什么都没有」。
升级提示
这次更新不需要你做任何准备工作。 已经迁移的应用和数据留在原处即可,插盘后的「接回」、需要时的「还原」都按原记录进行;升级不会改动应用签名,也不会要求你重新搬一遍已经迁好的内容。
外盘是 exFAT 或 FAT32 的用户会看到变化。 以前把应用迁到这类盘会失败,现在可以完成。迁移前会列出这块盘存不住的项目:访问控制列表(ACL)、硬链接、资源叉、POSIX 权限位;点「仍要迁移」继续,应用迁移后仍能打开、签名仍能通过。带特权后台服务的应用(VPN、代理、系统工具)建议改用 APFS 格式的盘,或让它留在本机。
数据目录和挂载迁移的校验没有放宽。 逐文件哈希、时间戳严格比较、逐文件落盘全部保留;这次只改了应用包这一条路径。挂载迁移仍然需要未加密的 APFS 外置盘和完全磁盘访问权限。
已经降级的副本不会因为升级而变好。 如果某个应用在 1.9.3 之前就被迁到 exFAT 上,它当时就已经缺了 ACL、硬链接、资源叉和权限位。还原前会提示这一点;依赖权限或访问控制的应用,重新安装官方版本比还原这份副本更合适。
修复
大应用迁移耗时数小时,最后还可能失败 — 应用包迁移一直在用为数据目录设计的内容寻址契约:扫描时给每个文件算 SHA-256,复制时每个文件 fsync,校验时再反复把整棵树读回来。一份 1.2 GB、600 个文件的测试树,扫描从 0.495 秒降到 0.019 秒(26 倍),校验从 0.983 秒降到 0.063 秒(16 倍);真实 Xcode.app 的扫描从 39.47 秒降到 8.85 秒。已签名应用的内容证明改由 SecStaticCodeCheckValidity 承担——这项校验本来就在切换前执行,现在它同时取代了逐文件哈希;时间戳漂移不再单独判失败,但 inode、大小、权限位、链接数、扩展属性、ACL、资源叉摘要和硬链接关系仍然逐项核对。结构性的保护全部保留:不跟随链接的锚定遍历、拒绝嵌套挂载、验证通过前保留原件、原子切换。
迁到 exFAT/FAT32 的应用会在切换阶段失败 — 这类文件系统连空 ACL 都拒绝写入,因此复制阶段可能先失败;即使复制成功,切换阶段依赖的两个原子改名(RENAME_EXCL、RENAME_SWAP)也一律返回「不支持」。这个问题纯复制器测试发现不了,是端到端测试才暴露的。现在遇到明确的「不支持」时降级为「先检查目标不存在,再普通改名」,仍然不覆盖已存在的对象;其他错误码照旧直接抛出。
还原时进度窗口停在 100% 却没有阶段 — 应用还原只发布字节数,所以漫长的扫描、校验和落盘看起来和卡死完全一样。现在单个还原和批量还原都接入了与数据目录相同的阶段对象,每个应用一个实例,阶段在批量中逐项重新开始。
进度窗口第一帧会闪一下旧样式 — 阶段对象原来在窗口显示之后才创建,第一帧画的是旧版字节进度,第二帧才换成阶段视图。现在所有入口都先建对象、再显示窗口。
部分界面语言的「查看完整说明」会打开 404 页面 — 文档站改版后只发布简体中文、繁体中文和英语三种版本,但应用内链接仍然把日语、韩语、德语、法语、西班牙语映射到各自的语言目录,这些目录并不存在。现在只保留实际发布的语言,其余界面语言一律打开英文版。改动同时覆盖了「挂载迁移」「经典模式」「macOS 27」三处说明链接。
应用转移少走一遍整树校验 — copy 已经用扫描时写下的基线证明过源未被改动,之后的每一步只碰候选副本或记录,不会改变源。删除这次重复遍历后,还原的源遍历从四次降到三次、目标两次。切换前的那次源校验保留,因为它守住的是真实的时间窗口。
新功能
迁移前说明目标盘存不住哪些应用信息 — AppPorts 现在会在迁移前实际探测目标盘的能力,而不是靠格式名称猜测:在目标位置临时建条目,真的写一次 ACL、建一个硬链接、写一次资源叉、设一次权限位,然后删掉。同一块盘换了挂载方式也能被正确识别。探测到缺失时会列出具体缺哪几项,说明迁移后应用仍可正常打开、签名校验仍然通过,但权限会变成磁盘的固定默认值、应用内部共用的文件会各存一份(占用更多空间)、少数应用的自定义图标等资源可能缺失,并提供「仍要迁移」和文档链接。提示只说明代价,不阻断;探测不到时直接继续,不凭空警告。
还原前提醒外部副本可能已降级 — 还原读取的是外盘副本。如果那块盘本来就存不住这四项,副本在迁出时就已经缺了它们,还原无法补回,而且还原完成后外部原件会被删除、缺失变成永久。现在还原前会先探测外部副本所在的那块盘(不是本机目的地),命中时说明损失发生在迁出时、还原后无法恢复,并给出「仍要还原」。批量还原只提示一次,确认后回到批量路径,不会把一个多应用还原悄悄缩成一个。探测失败时不警告、直接继续。
界面与行为变化
应用迁移和还原用上了和数据目录同一套阶段进度 — 窗口按实际阶段显示:准备、扫描源文件、复制文件、写入磁盘、校验文件、切换本地入口、清理旧副本,每一步都有一句说明。百分比只属于文件复制,并注明「此进度仅表示文件复制」。15 秒没有收到新的文件进度时会显示距离上次进展的时间,而不是让计时器空转;批量操作的「应用 2 / 5」移到了阶段视图之外,切换阶段时不会丢。
exFAT 上的元数据降级不再静默发生 — 存不住的部分会被降级而不是让整次迁移失败:空 ACL 跳过、共享 inode 物化成独立副本、资源叉丢弃、忽略权限位的盘不比较 mode。但源里确实存在的 ACL 写不进去仍然失败,目标出现源里没有的内容仍然失败。硬链接分离和资源叉丢弃的次数会在复制结束时以 WARN 记一次日志(目标存储无法保留部分元数据),事后可以查到这次迁移到底丢了什么。
新增文档页「目标盘存不住哪些应用信息」 — 简体中文、繁体中文、英语三种版本,说明每种能力为什么会丢、丢了有什么影响、怎么判断一块盘合不合适、以及哪些情况仍然会直接拒绝迁移。两个新提示里的「详情请参阅文档」都指向它。
已知限制
- 未签名或签名损坏的应用不再有逐文件哈希证明。 这类应用本来就不会被要求通过签名校验,跳过签名校验后只剩元数据级证明(inode、大小、权限位、链接数、扩展属性)。如果你有来源不明、又必须迁移的应用,这一点需要知情。
- 时间戳漂移不再单独判失败。 一个在复制期间被改写、且大小恰好不变的文件不会被发现;应用包场景下由签名校验兜底,数据目录不受影响。
- 掉电窗口收窄但没有消失。 候选副本只有在自身校验通过后才会被切换,所以掉电留下的是需要重做复制的目标,不是被切换到损坏的目标;但逐文件
fsync已去掉,只剩目录级落盘和根部的设备缓存屏障。 - exFAT/FAT32 上的切换不是原子操作。 检查和改名不再是一个动作,中途崩溃可能留下一个
.appports-swap-<UUID>临时名。只在文件系统明确表示不支持时才降级,其他错误照常抛出。 - 已经丢掉的元数据补不回来。 硬链接被物化后会多占空间;资源叉丢失表现为个别图标缺失或界面文字不完整;ACL 丢失对带特权后台服务的应用可能是致命的。
- 这次放宽不等于 exFAT 适合带特权服务的应用。 存不住 ACL、硬链接、资源叉、权限位的盘,仍然不是这类应用该待的地方。
⚠️ “AppPorts”已损坏,无法打开
如果从 AppPorts 官方发布渠道下载后遇到此提示,可能与 macOS 对未进行 Developer ID 签名和公证的应用进行隔离有关。
确认下载来源后,将 AppPorts 拖入「应用程序」文件夹,再在终端运行:
:::shell
xattr -rd com.apple.quarantine /Applications/AppPorts.app
迁移使用提示
- 升级后建议先试一次之前失败或超时的大应用迁移,看看耗时变化。
- 迁到 exFAT 盘的应用如果出现异常(尤其是带后台服务的),把数据留在本机或换用 APFS 盘;不要靠修改权限去修,这类盘上改权限会被系统静默忽略。
- 如果某个应用仍然迁不动,把错误信息连同「帮助 → 日志 → 导出诊断包」发到 GitHub Issues,诊断包已自动脱敏。
- 迁移后的目录请继续按原来的习惯使用:先连接外置盘,拔盘前先退出应用并正常推出,重要数据另行备份。
English Release Notes
Summary
What this update fixes is the waiting: moving a large app to an external drive should take minutes, yet it could take hours and still fail.
The cause was not the drive's speed but how the copy was proven. App bundles had been given the strict contract designed for data directories, so almost all of the time went into proving the copy; yet a bundle already carries the developer's signed manifest, and checking that is both stricter and far quicker. Signed apps now use that proof; the data-directory contract is unchanged.
Moving the app is only half of it; the copy also has to be right. Migrating an app to a drive that cannot store every attribute (exFAT, for example) used to fail — it now completes, but AppPorts first says what that drive cannot keep and lets you decide. Restoring a copy that was already degraded now warns first: the loss happened at migration time, and restoring cannot undo it. App migration and restore also use the same stage-aware progress window as data directories, so a long verification or flush no longer looks like a bar frozen at 100%.
Upgrade Notes
This update asks you to prepare nothing. Already-migrated apps and data can stay where they are; reconnecting after you plug the drive in and restoring when you need to both follow existing records. The upgrade does not change app signatures and does not ask you to move anything again.
Users with an exFAT or FAT32 drive will notice a difference. Migrating an app to such a drive used to fail; it now completes. Before migrating, AppPorts lists what the drive cannot keep — access control lists (ACL), hard links, resource forks, POSIX permission bits — and you choose "Migrate anyway"; the app still opens and its signature still verifies. Apps with a privileged background service (VPNs, proxies, system utilities) should use an APFS-formatted drive or stay on this Mac.
Data directory and mount migration are not relaxed. Per-file digests, strict timestamp comparison and per-file flushes all remain; only the app bundle path changed. Mount migration still requires an unencrypted APFS external drive and Full Disk Access.
A copy that was already degraded does not improve with this update. If an app was migrated to exFAT before 1.9.3, it was written without ACLs, hard links, resource forks and permission bits at that time. Restoring now warns about this; for apps that depend on permissions or access control, reinstalling the official build is better than restoring that copy.
Fixes
Large app migrations took hours and could fail — App bundle transfers were using the content-addressed contract designed for data directories: SHA-256 for every file during the scan, fsync for every file during the copy, and repeated full reads of the tree during verification. On a 1.2 GB / 600-file test tree, the scan went from 0.495 seconds to 0.019 (26×) and verification from 0.983 to 0.063 (16×); on real Xcode.app the scan went from 39.47 seconds to 8.85. For signed apps the content proof is now SecStaticCodeCheckValidity — a check that already ran before the switch, now standing in for the per-file digests — proving the destination matches the bytes the developer signed. Timestamp drift alone no longer fails, but inode, size, mode, link count, extended attributes, ACLs, resource-fork digests and hard-link relationships are still compared item by item. Every structural guarantee remains: no-follow anchored traversal, nested-mount refusal, the original retained until the destination verifies, and an atomic switch.
Migrating an app to exFAT/FAT32 failed at the switch phase — Such a filesystem rejects even an empty ACL, so the copy could already fail; and even when it succeeded, the switch phase depended on two atomic renames (RENAME_EXCL, RENAME_SWAP) that both return "unsupported" there. Pure copier tests could not reach this; only an end-to-end transfer exposed it. A clearly unsupported rename now degrades to "check the target is absent, then rename", still never replacing an existing object; every other error still propagates.
Restores sat at 100% with no stage — App restores published only byte counts, so a long scan, verification or flush looked exactly like a stall. Both single and batch restore now report through the same stage object data directories use, with one object per app so stages restart per item in a batch.
The progress window flashed the old view for one frame — The stage object was created after the window was already shown, so the first frame drew the byte-only fallback and the second swapped. Every entry point now builds the object before revealing the overlay.
"See the documentation" opened a 404 for some interface languages — After the documentation site was rebuilt it publishes only Simplified Chinese, Traditional Chinese and English, but in-app links still mapped Japanese, Korean, German, French and Spanish to their own directories, which do not exist. Only published editions are mapped now; every other interface language opens English. The same fix covers the links for mount migration, classic mode and macOS 27.
One redundant full-tree verification removed on app transfers — copy already proved the source unchanged against the baseline written during the scan, and nothing afterwards touches the source. With that repeat traversal gone, a restore walks the source three times instead of four and the destination twice. The pre-switch source check stays, because that one guards a real window.
New Features
AppPorts now says what a destination cannot store, before migrating — It probes the destination instead of guessing from the format name: temporary entries are created, an ACL is written, a hard link is made, a resource fork is written, permission bits are set, then the entries are removed. A drive mounted differently is therefore classified correctly. When a capability is missing, the warning lists exactly which ones, explains that the app still opens and its signature still verifies, but that permissions become the drive's fixed default, files shared inside the app are stored as separate copies (using more space), and a few apps may lose custom icons and similar resources. It offers "Migrate anyway" and a documentation link. The warning explains a cost; it never blocks, and an unreadable probe proceeds without a warning.
A warning before restoring a bundle that was already degraded — A restore reads the external copy. If that drive could not store those four attributes, the copy was written without them at migration time, restoring cannot bring them back, and the external original is deleted when the restore completes, making the gap permanent. Restore now probes the volume holding the external copy (not the internal destination), and when it cannot represent them explains that the loss happened during migration, offers "Restore anyway", and links the documentation. A batch warns once and dispatches back to the batch path after confirmation, so a multi-app restore is never silently reduced to one. An unreadable probe proceeds without a warning.
Interface and Behaviour Changes
App migration and restore now use the same stage progress as data directories — The window follows the actual stage: preparing, scanning source files, copying files, writing to disk, verifying files, switching the local entry, removing old copies, each with one sentence of explanation. The percentage belongs to file copying only and says so. When no new file progress arrives for 15 seconds it shows how long it has been waiting instead of spinning a timer. The batch counter ("Apps 2 / 5") sits outside the stage view so it survives stage changes.
Metadata degradation on exFAT is no longer silent — What the drive cannot store is degraded instead of failing the whole migration: an empty ACL is skipped, a shared inode is materialised as an independent copy, a resource fork is dropped, and a drive that ignores permission bits is not compared on mode. An ACL the source really has still fails, and a destination containing content the source does not still fails. Separated hard links and dropped resource forks are logged once per copy as a WARN (目标存储无法保留部分元数据), so you can look up what a given migration lost.
A new documentation page, "What the destination cannot store about an app" — In Simplified Chinese, Traditional Chinese and English: why each capability can be lost, what losing it affects, how AppPorts decides whether a drive is suitable, and which cases still refuse a migration. Both new warnings link to it.
Known Limitations
- Unsigned apps and apps with a broken signature no longer get a per-file digest proof. Such an app was never required to pass the signature check; with that check skipped, only metadata-level proof remains (inode, size, mode, link count, extended attributes). Worth knowing if you migrate apps of unknown origin.
- Timestamp drift alone no longer fails verification. A file rewritten during the copy whose size happens to stay identical would not be noticed; the code signature covers app bundles, and data directories are unaffected.
- The power-loss window is narrower, not gone. A candidate is published only after it verifies, so a power cut leaves a copy that is redone rather than one that is switched to; but per-file
fsyncis gone, leaving directory-level flushes and the device barrier at the root. - The switch on exFAT/FAT32 is no longer atomic. The check and the rename are separate operations, so a crash mid-switch can leave an
.appports-swap-<UUID>name behind. The fallback only engages when the filesystem explicitly says it cannot do the atomic version; other errors still propagate. - Metadata that was already lost cannot be recovered. Materialised hard links take more space; a lost resource fork shows up as a missing icon or incomplete interface text; a lost ACL can be fatal for an app with a privileged background service.
- This relaxation does not make exFAT suitable for apps with privileged services. A drive that cannot store ACLs, hard links, resource forks and permission bits is still the wrong place for them.
⚠️ "AppPorts" is damaged and can't be opened
If this appears after downloading AppPorts from an official release channel, it may be related to macOS quarantine checks for apps without Developer ID signing and notarization.
After confirming the download source, move AppPorts to the Applications folder and run:
:::shell
xattr -rd com.apple.quarantine /Applications/AppPorts.app
Migration Notes
- After upgrading, try one large app migration that previously failed or timed out and compare how long it takes.
- If an app migrated to an exFAT drive misbehaves (especially one with a background service), keep its data on this Mac or use an APFS drive. Do not try to fix it by changing permissions — on such drives the system silently ignores the change.
- If an app still cannot be moved, send the error along with Help → Logs → Export Diagnostics to GitHub Issues; the package is redacted automatically.
- Keep using migrated directories as before: connect the external drive first, quit the app and eject the drive normally before disconnecting it, and keep a separate backup of important data.
遇到问题或有新的建议,欢迎通过 GitHub Issues 向我反馈。 / Share problems or suggestions with me through GitHub Issues.
What's Changed
- feat: relax app bundle migration to a product-integrity copy profile by @wzh4869 in https://github.com/wzh4869/AppPorts/pull/83
- feat: warn when restoring a bundle that was already degraded on its volume by @wzh4869 in https://github.com/wzh4869/AppPorts/pull/85
Full Changelog: https://github.com/wzh4869/AppPorts/compare/1.9.2...1.9.3