背景
Cornerstone 是 macOS 上体验最好的 SVN 客户端之一,但试用期一到就要付费。
研究了一下它的试用机制,发现试用状态存储在一个隐藏的加密缓存文件中:
~/Library/Containers/com.zennaware.cornerstone3/Data/Library/Application Support/com.zennaware.cornerstone3/.hdhhoix5h2sozule6zx7ydxoa75d24gu
该文件具有以下特征:
- 文件名为随机字符串,每次可能不同
- 内容为加密的二进制数据,大小约 323 字节
- 属于隐藏文件(以
.开头),不会在 Finder 中直接显示
只要在 Cornerstone 每次退出后自动清空该目录,下次启动时试用期即可重置。
方案选型
方案一:Automator 启动器(已排除)
最初思路是创建一个 Automator 应用作为启动器,在打开 Cornerstone 之前先执行清理脚本。
问题:
- Automator 应用访问其他应用的沙盒目录(
~/Library/Containers/)时,macOS 每次都会弹出权限确认弹窗,无法静默运行。 - 将 Automator 启动器固定在 Dock 后,Cornerstone 启动时会在 Dock 中额外显示自身图标,导致同时出现两个图标。
方案二:修改 app 内部可执行文件(已排除)
将 Cornerstone.app/Contents/MacOS/Cornerstone 重命名为 Cornerstone_real,替换为一个 shell 脚本,在脚本中执行清理后再启动真正的二进制文件。
问题:
修改 app bundle 内容后,代码签名失效,macOS Gatekeeper 拒绝运行,应用直接闪退。使用 codesign --force --deep --sign - 重新签名为 ad-hoc 签名后,部分情况下仍因沙盒权限问题无法正常启动。
方案三:launchd + 进程监控脚本(最终方案)
在应用关闭后执行清理,完全规避启动时的权限和签名问题。使用 launchd 在后台持续运行一个监控脚本,检测到 Cornerstone 进程退出时触发清理操作。
实现步骤
第一步:创建进程监控脚本
cat > ~/cornerstone_watcher.sh << 'EOF'
#!/bin/bash
CACHE_DIR="$HOME/Library/Containers/com.zennaware.cornerstone3/Data/Library/Application Support/com.zennaware.cornerstone3"
was_running=false
while true; do
if pgrep -x "Cornerstone" > /dev/null; then
was_running=true
else
if [ "$was_running" = true ]; then
rm -rf "$CACHE_DIR"/.[!.]*
rm -rf "$CACHE_DIR"/*
was_running=false
fi
fi
sleep 2
done
EOF
chmod +x ~/cornerstone_watcher.sh
脚本逻辑说明:
| 关键点 | 说明 |
|---|---|
pgrep -x "Cornerstone" | 精确匹配进程名,-x 参数要求全名匹配,避免误伤其他进程 |
was_running 标志位 | 用于区分"进程从未运行"和"进程刚刚退出"两种状态,确保只在退出时触发清理 |
.[!.]* | 匹配隐藏文件,但排除 . 和 ..,比 .* 更安全 |
sleep 2 | 每 2 秒轮询一次,CPU 占用极低 |
$HOME | 在 plist 中 ~ 不会被展开,脚本内部使用 $HOME 保证路径正确 |
第二步:创建 launchd plist 配置文件
将以下内容保存为 ~/Library/LaunchAgents/com.user.cornerstone.cleancache.plist,注意将路径中的用户名替换为实际值:
cat > ~/Library/LaunchAgents/com.user.cornerstone.cleancache.plist << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.user.cornerstone.cleancache</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/你的用户名/cornerstone_watcher.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
EOF
配置项说明:
| Key | 说明 |
|---|---|
Label | launchd Job 的唯一标识符,建议使用反向域名格式 |
RunAtLoad | 配置文件加载后立即启动(含开机自启) |
KeepAlive | 脚本退出后自动重启,保证持续监控 |
⚠️ plist 文件中不支持
~路径展开,ProgramArguments中必须使用绝对路径。
第三步:加载配置并启动
launchctl load ~/Library/LaunchAgents/com.user.cornerstone.cleancache.plist
验证脚本是否正在运行:
pgrep -fl cornerstone_watcher
输出示例:
7106 /bin/bash /Users/fatto/cornerstone_watcher.sh
有输出则表示监控脚本已在后台运行,重启 Mac 后会自动恢复。
验证效果
- 打开 Cornerstone,确认缓存文件已生成:
ls -la ~/Library/Containers/com.zennaware.cornerstone3/Data/Library/Application\ Support/com.zennaware.cornerstone3/
- 正常关闭 Cornerstone,等待约 2 秒后再次执行上述命令,确认目录已清空。
注意事项
- Cornerstone 更新不受影响:本方案不修改 app bundle,更新后无需重新操作。
- 缓存目录会被完整清空:如果 Cornerstone 在该目录下还存储了其他重要数据(如仓库配置),需要先确认清空范围,必要时改为只删除特定文件。
- launchd 与 cron 的区别:launchd 是 macOS 推荐的任务调度机制,支持按需启动、依赖管理和日志收集,优于传统 cron。
扩展应用
本方案的核心模式是"进程退出后触发动作",可以推广到其他场景:
- 应用关闭后自动备份配置文件
- 应用关闭后清理临时文件或日志
- 应用关闭后执行数据同步脚本
只需修改脚本中的进程名(pgrep -x 的参数)和要执行的操作即可复用。