拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Tart Guest Agent 技术解析:用自研 gRPC 协议打通无 SSH 命令执行、剪贴板共享与自动磁盘扩容

Tart Guest Agent 技术解析用自研 gRPC 协议打通无 SSH 命令执行、剪贴板共享与自动磁盘扩容【免费下载链接】tartmacOS and Linux VMs on Apple Silicon to use in CI and other automations项目地址: https://gitcode.com/GitHub_Trending/ta/tartTart 是基于 Apple Virtualization.Framework 的 macOS / Linux 虚拟机管理工具本文围绕其 Guest Agent客户机代理展开为什么 Tart 要自研一个 Golang 写的 Guest Agent取代开源社区已有的 SPICE vdagent 方案它如何通过一条运行在虚拟机 control socket 之上的 gRPC 双向流让宿主机在不依赖 SSH 和网络的情况下向客户机注入命令、双向搬运标准输入输出并原样透传退出码以及 launchd 守护进程/代理两种运行形态如何分工实现磁盘自动扩容与剪贴板共享。读完本文你将理解tart exec的完整调用链、--resize-disk与tart set --disk-size的配合方式以及该协议为后续扩展Linux 支持、tart cp文件拷贝预留的接口。背景Virtualization.Framework 缺的那块拼图Tart 的虚拟机运行能力建立在 Apple 的 Virtualization.Framework 之上。这个框架提供了虚拟机生命周期、网络、磁盘、显示等大量能力但有一个关键组件始终缺席运行在客户机Guest内部的代理进程。没有它宿主机与客户机之间就缺少一个内应来执行那些必须发生在客户机操作系统内部的操作——比如自动扩容磁盘、共享剪贴板、注入命令。而 Virtualization.Framework 本身已经实现了 SPICE 客户端用于剪贴板等通道也就是说宿主机一侧的管道是现成的缺的只是管道另一端、跑在客户机里的那个 agent。这正是 Tart 项目要补上的缺口相关讨论可追溯至 Tart 仓库的 issue #14即用户长期期待的剪贴板共享功能。为什么不用现成的 SPICE vdagent在决定自研之前团队认真评估过既有开源方案主要有三个问题上游只支持 LinuxSPICE 官方的vdagent实现freedesktop 项目只支持 Linux 客户机无法直接用于 macOS 客户机。UTM fork 的长期维护风险UTM 项目维护了一个为 macOS 添加支持的 vd_agent fork但该 fork 的修改没有回流上游长期可持续性存在不确定性——一旦上游协议演进fork 可能逐渐失配。多二进制维护成本如果要在 vdagent 之外再叠加额外功能例如命令执行就意味着要同时打包、安装、解释多个 agent 二进制维护复杂也难以向用户解释为什么需要一堆 agent。权衡之下Tart 团队决定自研一个解决方案让协议与实现完全掌握在自己手里便于容纳后续的新功能。自研 Agent为什么选 Golang有两个关键观察促成了自研其一剪贴板共享只是 vdagent 协议的一个小子集。仔细研读vdagent协议后可以发现Tart 需要的剪贴板功能只是整个协议中相对独立的一小部分实现起来并不复杂没有必要为一个子集引入整套重依赖。其二Golang 的开发效率。与需要手动管理内存、编写复杂事件循环的 C 语言相比Golang 让协议实现速度快得多。最终产物是一个单二进制、可按运行上下文定制行为的 agent。gRPC 协议tart exec的心脏命令执行部分Tart 选择了 gRPC 一个相当精简的协议其消息结构如上图所示。核心是一个双向流bi-directional streamExec streamExecRequest宿主机 → 客户机通过oneof承载三种消息command嵌套的Command消息包含name命令名、args参数数组、interactive是否交互式、tty是否分配 PTY以及可选的terminal_sizerows/colsstandard_input嵌套的IOChunk二进制data字段传输标准输入terminal_resize嵌套的TerminalSize在窗口尺寸变化时通知客户机。ExecResponse客户机 → 宿主机同样通过oneof承载standard_output/standard_error嵌套的IOChunk回传命令的标准输出与错误输出exit嵌套的Exitcode字段报告命令退出码。选择 gRPC 的理由很实际代码生成大幅简化了tart exec的客户端实现同时这条 host ↔ guest 的通道形成了一座桥日后引入新功能时协议可以平滑扩展。在 Swift 侧得益于基于 SwiftNIO 构建的 gRPC Swift宿主端代码可以免费获得async/await并发支持进一步简化了tart exec的异步逻辑。宿主机侧实现Exec.swift的完整调用链在 Commands/Exec.swift 中可以看到tart exec的完整实现tart exec [-i] [-t] VM name command...-i/--interactive把宿主机的标准输入附加到远程命令-t/--tty为远程命令分配伪终端PTY命令及参数通过Argument(parsing: .captureForPassthrough)原样透传不经过解析器改写。其执行流程大致如下版本与状态校验tart exec仅支持 macOS 14Sonoma及以上源码中通过#unavailable(macOS 14)检查随后打开 VM 目录并确认 VM 处于运行状态否则抛出VMNotRunning。通过 control socket 建立 gRPC 通道命令会先chdir到 VM 的基础目录以绕开 Unix domain socket 路径 104 字节的长度限制然后调用 GuestAgentChannel.swift 中的withGuestAgentChannel以GRPCChannelPool.with(target: .unixDomainSocket(...), transportSecurity: .plaintext, ...)的方式连接 VM 的 control socket并在成功/失败两条路径上都会正确关闭 channel。发送Command消息将命令名、参数数组、interactive、tty封装为ExecRequest.command发送若请求了 PTY还会用Term.GetSize()读取当前终端尺寸一并下发给客户机。双向流式 I/OwithThrowingTaskGroup并行处理三个任务交互模式下把宿主机标准输入流式发送为standard_input消息。源码还细致处理了一个边界情况当标准输入是普通文件即使用重定向时readabilityHandler不会收到可读事件因此会退化为按 64KB 块同步读取文件读到 EOF 后发送一个空的standard_input数据块显式通知客户机关闭输入。请求 PTY 时通过signal(SIGWINCH, SIG_IGN)DispatchSource监听终端尺寸变化SIGWINCH实时发送terminal_resize消息保证远程命令的伪终端与本地窗口保持一致。响应流处理standard_output写入宿主机标准输出、standard_error写入标准错误收到exit消息后抛出携带退出码的ExecCustomExitCodeError。退出码透传任何 task 抛出错误都会cancelAll()并向上传播。在 Root.swift 的handleError中ExecCustomExitCodeError被特殊识别——它不是错误而是客户机命令的正常退出信号最终以Foundation.exit(execCustomExitCodeError.exitCode)让tart exec进程以与客户机内命令完全一致的退出码结束。这让tart exec在 CI 脚本里可以像本地命令一样使用/||判断成败。连接失败的友好提示若无法连接 control socket即 VM 内没有运行 Guest Agent会抛出 Failed to connect to the VM using its control socket: ..., is the Tart Guest Agent running?提示用户检查 agent 是否已安装启动。客户机侧能力探测Virtio 控制台在 VM.swift 的虚拟机配置构建代码中可以看到一个细节Tart 会为每台 VM 挂载一个名为tart-version-CI.version的VZVirtioConsolePortConfiguration虚拟控制台设备注释明确写道这是 a dummy console device useful for implementing host feature checks in the guest agent software——也就是说Guest Agent 可以通过这个虚拟控制台读取宿主机 Tart 的版本信息从而判断宿主机支持哪些特性这是 host 与 guest 协作协议的一部分。Agent 的两种运行形态与 CLI 参数Tart Guest Agent 最终是一个 Golang 二进制根据运行上下文可以以两种方式部署对应两种 launchd 服务形态运行用户权限/能力相关参数launchd 全局守护进程daemonroot特权无剪贴板访问--resize-disklaunchd 全局代理agentadmin普通用户有剪贴板访问--run-vdagent、--run-rpc--resize-disk守护进程当磁盘末尾存在空闲空间时自动扩容磁盘——前提是之前曾用tart set --disk-size GB增大过磁盘大小上限。宿主机侧的磁盘调整入口在 Commands/Set.swift--disk-size选项以 GB 为单位、只允许增大防止数据丢失最终调用 VMDirectory.swift 的resizeDisk(_:format:)完成底层磁盘镜像操作。agent 在客户机内部检测到宿主机已把磁盘加长后便可以在空闲空间上自动扩充分区与文件系统实现真正的自动磁盘扩容。--run-vdagent代理启用剪贴板共享。协议实现自 SPICE vdagent属于协议子集负责宿主机与 macOS 客户机之间的剪贴板双向同步。--run-rpc代理启用 gRPC RPC 服务端即上文所述的Exec双向流支撑tart exec以及未来新增的远程能力。此外还有两个组合开关简化部署--run-daemon隐式启用--resize-disk适用于特权上下文--run-agent隐式同时启用--run-vdagent和--run-rpc适用于普通用户上下文。这种按上下文取最合适功能组合的设计让镜像维护者只需根据 launchd 服务的身份选择对应开关不必逐个拼接底层参数。落地现状与未来计划目前所有非 vanilla 的 Cirrus Labs 官方镜像都已预装 Tart Guest Agent因此使用这些镜像的用户通常无需任何额外操作即可享受上述体验改进——这一点在tart exec的命令帮助文本Commands/Exec.swift 的discussion字段中也有明确说明。项目计划中还有三个方向Linux 支持让 Linux 客户机也能获得同样无缝的体验新的tart ip解析器为 Linux 客户机提供更稳健的 IP 获取能力——Linux 客户机往往不会主动用网络活动填充宿主机的 ARP 表导致现有基于 ARP 的 IP 探测手段不稳定tart cp命令在宿主机与客户机 VM 之间双向拷贝文件。这些能力都可以复用现有的 gRPC 通道--run-rpc与 control socket 基础设施这也正是当初选择自研 gRPC而非维护多个 fork 的长期回报。如果需要深入了解协议的宿主机侧实现细节可以继续阅读 Commands/Exec.swift 与 GuestAgentChannel.swift本博文的原始版本位于 docs/blog/posts/2025-06-01-tart-guest-agent.md。【免费下载链接】tartmacOS and Linux VMs on Apple Silicon to use in CI and other automations项目地址: https://gitcode.com/GitHub_Trending/ta/tart创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门