一行命令创建 macOS 虚拟机

我正在构建一个 macOS 的菜单栏应用程序。在我的 Mac 上运行完美。现在我需要知道它是否能在干净的 macOS 环境中正常工作:没有我的配置、没有我的权限、没有我的数据。一个全新安装的用户环境。 如何测试这种情况?你需要一个虚拟机。 “简单”,我想。“我安装了 UTM。打开向导,创建一个 macOS 虚拟机,然后运行。” 事情并没有那么简单。 UTM:漂亮但难以驯服 UTM 是一个很棒的应用程序。精心设计的界面,支持在 Apple Silicon 上运行 macOS 客户机,全屏显示,共享剪贴板。手动使用确实很棒。 当你试图自动化时问题就出现了。 UTM 有一个叫做 utmctl 的命令行界面。可以列出虚拟机、启动它们、停止它们、克隆它们。它不能做的是创建虚拟机。对于 macOS 客户机,甚至 UTM 的 AppleScript 也不允许创建它们——操作系统字段被硬编码为 Linux。 简单来说:如果你想在 UTM 中创建 macOS 虚拟机,你必须通过向导手动创建。每次都是。需要点击、下载 IPSW(Apple Silicon 的 macOS 安装映像——相当于传统的 ISO,但由 Apple 打包)、等待安装。 对于需要在质量保证流程中频繁创建和销毁虚拟机的开发者来说,这真是个麻烦事。 Tart:为开发者设计的 macOS 虚拟机 Tart 是当有人在设计虚拟化工具时考虑开发者而不是最终用户的结果。 它使用与 UTM 完全相同的 Apple Virtualization.framework。相同的技术,相同的功能,相同的原生速度。区别在于界面:Tart 是命令行优先的。 1 brew install cirruslabs/cli/tart 就这样。没有图形界面配置,没有向导。只是在你的 PATH 中的一个二进制文件。 一个命令统治一切 创建一个使用最新可用版本的 macOS 虚拟机: ...

2026年2月21日 · Fernando

当安全工具频繁索取权限时,你就不再仔细查看了

敲敲敲。谁?Touch ID。又来了。 想象一下:你正在终端工作,用op read查询1Password的密钥。需要Linear的API密钥。Touch ID。OpenRouter的。Touch ID。Gitea的。Touch ID。 半小时内它要求我验证指纹十四次。 你知道当一个安全工具在三十分钟内打断你十四次会发生什么吗?第五次时你就不再看它在要求什么了。你下意识地放上手指。“是的,随便什么,让我工作吧。” 而这正是安全性完全崩溃的地方。 授权疲劳:没人愿意正视的问题 这在安全领域有个名词:授权疲劳。这不是什么新概念。这与MFA疲劳攻击使用的原理相同:用授权请求轰炸用户,直到他们纯粹因为疲惫而接受一个。 2022年,一个17岁的孩子正是这样进入Uber内部系统的。他反复向员工发送认证推送通知,深夜时分,直到那个人为了能睡觉而接受了一个。 显然,1Password要求Touch ID不是攻击。但心理效应是相同的:它训练你不假思索地批准。 这就像那些多年来出现在每个网站上的cookie横幅。一开始你会阅读它们。现在你不看就点击"全部接受"。恭喜:一个设计用来保护你隐私的机制教会了你更快地放弃你的隐私。 为什么1Password每次都要我的手指 我的设置:我使用op read从终端读取1Password的密钥。运行得很好。问题是我使用Claude Code(一个终端AI助手),它执行的每个命令都是一个新进程。 1Password的生物识别会话超时是10分钟不活动,并在每次使用时刷新。理论上,不应该这么频繁地要求手指。但Claude Code不重用进程:每次需要密钥时,它启动一个新的shell,1Password将其解释为新会话。 结果:每次Claude需要密钥时都要Touch ID。这是持续性的。 解决方案:40行缓存 想法很简单:一个包装器在PATH中排在op前面。当你执行op read时,它检查是否已经缓存了新鲜的结果。如果有,直接返回而不接触1Password。如果没有,调用真正的op,缓存结果,完成。 对于任何其他子命令(op signin、op item list等),直接传递给真正的op而不干预。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #!/bin/bash # ~/.local/bin/op — 1Password CLI的缓存包装器 # 仅缓存'op read'。其他所有内容直接传递给真正的op。 # 可使用OP_CACHE_TTL配置缓存TTL(默认:3600s = 1h) REAL_OP="/opt/homebrew/bin/op" CACHE_DIR="${HOME}/.cache/op-cache" CACHE_TTL="${OP_CACHE_TTL:-3600}" # 仅缓存'op read' if [[ "$1" == "read" ]]; then mkdir -p "$CACHE_DIR" && chmod 700 "$CACHE_DIR" # 所有参数的哈希作为缓存键 CACHE_KEY=$(printf '%s\0' "$@" | shasum -a 256 | cut -d' ' -f1) CACHE_FILE="${CACHE_DIR}/${CACHE_KEY}" # 缓存命中:文件存在且未过期 if [[ -f "$CACHE_FILE" ]]; then FILE_AGE=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE") )) if [[ $FILE_AGE -lt $CACHE_TTL ]]; then cat "$CACHE_FILE" exit 0 fi fi # 缓存未命中或过期:调用真正的op RESULT=$("$REAL_OP" "$@") EXIT_CODE=$? # 仅在op成功时缓存 if [[ $EXIT_CODE -eq 0 ]]; then printf '%s' "$RESULT" > "$CACHE_FILE" chmod 600 "$CACHE_FILE" fi printf '%s' "$RESULT" exit $EXIT_CODE else # 任何其他子命令:直接传递 exec "$REAL_OP" "$@" fi 将它保存在~/.local/bin/op,给予执行权限,由于~/.local/bin在PATH中排在/opt/homebrew/bin之前,你的包装器会拦截调用。 ...

2026年2月12日 · Fernando