screenpipe 持续集成实战:在 AWS EC2 Mac 专用宿主机上搭建持久化 macOS 发布 Runner
【免费下载链接】screenpipeYC (S26) | Open Computer History | Record your screen continuously locally and provide context to your agents (Claude, Codex, Openclaw, Hermes, Runner...)项目地址: https://gitcode.com/GitHub_Trending/sc/screenpipe
本指南围绕 infra/release-mac-runner 目录下的基础设施脚本与说明文档展开,完整讲解 screenpipe 项目如何在 AWS 上以"非 Ultra 的 Apple Silicon 专用宿主机 + macOS Tahoe + 自托管 GitHub Actions Runner"的形态,为
Release App与Release Enterprise两条发布流水线提供长期驻留的 macOS 构建能力。读完本文,你将掌握这套栈的选型逻辑、Provision 部署脚本的自动降级策略、CloudFormation 模板的关键参数、Xcode 的引导安装方式,以及通过 AWS Systems Manager 完成 Runner 注册与 launchd 服务化安装的完整链路。
一、为什么需要一台"持久化"的 macOS 发布 Runner
screenpipe 是一个跨平台的本地优先(local-first)录屏 + AI 记忆应用,其桌面端基于 Tauri(Rust + WebView),发布产物需要在 macOS、Windows、Linux 三个平台上分别签名、打包、上传。在 .github/workflows/release-app.yml 和 .github/workflows/release-enterprise.yml 两条手动触发的发布工作流中,macOS 构建任务对环境的稳定性要求远高于常规 CI:
- 构建体积大、耗时长:Rust 编译、Tauri 打包、代码签名与公证(notarization)都是重活,单次执行往往需要数十分钟;
- 依赖缓存敏感:Cargo registry、Rust toolchain、Bun 依赖、原生依赖、编译器缓存等一旦丢失,重建成本极高;
- 发布节点不可随意波动:发布流程从
workflow_dispatch手动触发,要求节点"随叫随到"。
GitHub 官方托管的macos-latest属于按需分配的临时节点,缓存与工具链需要反复预热。因此 screenpipe 在 AWS 上维护了一台常驻 EC2 Mac,作为Release App与Release Enterprise两条工作流中 macOS 任务的专用自托管 Runner,Windows 与 Linux 任务则继续留在 GitHub 托管 Runner 上。与之平行的方案还有 infra/release-linux-runner 中的 Azure Linux 常驻节点,两者共同构成 screenpipe 的"自有发布农场"。
二、硬件选型与安全模型
2.1 实例选型:性能优先的降级顺序
按 README.md 的说明,该栈选用能在美国区域实际分配到的、最快的非 Ultra Apple 芯片专用宿主机,优先级顺序为:
| 优先级 | 实例类型 | 芯片 |
|---|---|---|
| 1 | mac-m4max.metal | M4 Max |
| 2 | mac-m4pro.metal | M4 Pro |
| 3 | mac2-m2pro.metal | M2 Pro |
| 4 | mac-m4.metal | M4 |
| 5 | mac2-m2.metal | M2 |
在 deploy.sh 中,这一顺序被实现为候选类型数组:
for candidate_type in mac-m4max.metal mac-m4pro.metal mac2-m2pro.metal mac-m4.metal mac2-m2.metal; do部署的最终产物是一个带终止保护(termination-protected)的 macOS Tahoe 实例(Tahoe 即 macOS 26),搭配2 TiB 高性能 gp3 根卷。Tahoe AMI 的取值来自 AWS 公共参数/aws/service/ec2-macos/tahoe/arm64_mac/latest/image_id,保证镜像始终跟随最新的 Tahoe Apple Silicon AMI。
2.2 安全模型:零入站 + SSM 管理
该实例的边界非常收敛:
- 无任何入站安全组规则,安全组只放行出站流量(见 template.yml 中
RunnerSecurityGroup的SecurityGroupEgress配置,无任何SecurityGroupIngress); - 管理员通过AWS Systems Manager(Session Manager / Run Command)进行连接与远程执行,无需 SSH 端口暴露;
- 实例 IAM 角色仅挂载
AmazonSSMManagedInstanceCore托管策略,配合实例配置文件的 SSM 通道完成一切管理操作。
这一设计使得发布节点虽然位于公网可达的 VPC 子网(用于出站下载依赖与向 GitHub 拉取任务),却几乎不存在可被扫描攻击的入口面。
三、Provision:一条命令完成宿主机分配与建栈
3.1 部署脚本的自动降级逻辑
执行以下命令即可完成 Provision:
./infra/release-mac-runner/deploy.shdeploy.sh 的核心思路是**"先复用、后新建"**的三段式降级查找,全程使用set -euo pipefail保证失败即中断:
- 复用已存在的发布 Mac:按
Name=screenpipe-release-mac标签在us-east-2、us-east-1、us-west-2三个区域中查询pending/running/stopping/stopped状态的实例,命中即直接输出实例信息并退出; - 复用已分配但未使用的专用宿主机:按同样标签查询
state=available的 Dedicated Host,命中即记录其HostId供后续使用; - 按性能顺序现场分配:依次遍历上文的 5 种实例类型 × 3 个区域 × 各可用区,调用
aws ec2 allocate-hosts尝试分配专用宿主机,首次成功即跳出所有循环。分配时携带--auto-placement off(显式指定部署)、--host-recovery on(宿主机故障自动恢复),并打上Name=screenpipe-release-mac与Workload=screenpipe-release两组标签。
如果所有候选组合都无法分配,脚本会输出明确错误并退出:
No permitted EC2 Mac Dedicated Host is currently allocatable in a US region3.2 可用环境变量覆盖
脚本全程支持显式覆盖,适合在已有宿主机、特定区域或特定实例类型场景下跳过自动查找:
| 环境变量 | 作用 | 默认行为 |
|---|---|---|
AWS_REGION | 指定区域(如us-east-2) | 按us-east-2 → us-east-1 → us-west-2自动探测 |
INSTANCE_TYPE | 显式指定实例类型 | 按性能顺序自动尝试 |
AVAILABILITY_ZONE | 显式指定可用区 | 取该实例类型在该区域的第一个可用区 |
EXISTING_HOST_ID | 复用既有 Dedicated Host | 为空时自动查找/分配 |
STACK_NAME | CloudFormation 栈名 | 默认screenpipe-release-mac |
3.3 CloudFormation 部署
宿主机确定后,脚本调用 CloudFormation 完成资源编排:
aws cloudformation deploy \ --region "$REGION" \ --stack-name "$STACK_NAME" \ --template-file "$(dirname "$0")/template.yml" \ --capabilities CAPABILITY_NAMED_IAM \ --parameter-overrides \ "AvailabilityZone=$AVAILABILITY_ZONE" \ "InstanceType=$INSTANCE_TYPE" \ "ExistingHostId=$EXISTING_HOST_ID" \ --no-fail-on-empty-changeset部署完成后会输出栈的Outputs(包含DedicatedHostId、InstanceId、RunnerName、RunnerLabel),供后续注册脚本使用。
四、CloudFormation 模板拆解:一条龙构建发布节点
template.yml 定义了发布节点所需的全部 AWS 资源,值得逐块拆解:
4.1 参数一览
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
AvailabilityZone | AWS::EC2::AvailabilityZone::Name | 必填 | 提供所选 EC2 Mac 类型的可用区 |
MacOSImageId | AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> | /aws/service/ec2-macos/tahoe/arm64_mac/latest/image_id | Tahoe Apple Silicon AMI 公共参数 |
InstanceType | String | mac-m4pro.metal | 取值限定在mac-m4max.metal/mac-m4pro.metal/mac-m4.metal/mac2-m2pro.metal/mac2-m2.metal |
ExistingHostId | String | 空 | 复用已分配的专用宿主机时传入 |
RootVolumeSize | Number | 2048(最小512) | EBS 根卷容量(GiB),承载持久化工作区与编译缓存 |
模板通过条件CreateRunnerHost: !Equals [!Ref ExistingHostId, ""]控制是否新建 Dedicated Host——传入已有宿主机 ID 时自动跳过 Host 创建。
4.2 网络与实例资源
- VPC / 子网 / 网关:独立 VPC
10.74.0.0/24,公有子网10.74.0.0/27(MapPublicIpOnLaunch: true),经 Internet Gateway + 默认路由0.0.0.0/0出网; - 安全组:仅出站、零入站;
- IAM:
AmazonSSMManagedInstanceCore托管策略 + 实例配置文件; - Dedicated Host:
AutoPlacement: off、HostMaintenance: on、HostRecovery: on,并带DeletionPolicy: RetainExceptOnCreate(防止误删); - 实例:
Affinity: host+Tenancy: host绑定专用宿主机,DisableApiTermination: true开启终止保护; - 监控:CloudWatch 告警监听
StatusCheckFailed指标(周期 60 秒、2 个评估周期),实例状态检查失败即告警。
4.3 启动模板与引导脚本
Launch Template 中,根卷配置为gp3、2048 GiB、16000 IOPS、1000 MB/s 吞吐、加密、DeleteOnTermination: false,保证数据不随实例终止而丢失;同时启用IMDSv2(HttpTokens: required),收紧元数据服务访问。
UserData引导脚本在首次启动时完成大量工作,日志落盘到/var/log/screenpipe-release-runner-bootstrap.log:
- 建立缓存与 Runner 目录:
/Users/ec2-user/screenpipe-cache(持久化工作区/编译缓存)与/Users/ec2-user/actions-runner,并chown给ec2-user; - 导入 AppleDeveloper ID 根证书(
DeveloperIDCA.cer、DeveloperIDG2CA.cer)到系统钥匙串,为后续代码签名铺路; - 通过 Homebrew 安装工具链:
aria2 bun cmake ffmpeg gh git-lfs jq node sccache wget,以及xcodesorg/made/xcodes(Xcode 版本管理工具); - 安装 Rust 工具链(
rustup默认stable); - 下载 GitHub Actions Runnerv2.336.0(osx-arm64)并解压到
actions-runner目录; - 落标记文件
/var/db/screenpipe-release-runner-bootstrap-complete,表示引导完成。
4.4 栈输出
栈完成后输出四个关键值:DedicatedHostId、InstanceId、RunnerName(screenpipe-release-mac)、RunnerLabel(screenpipe-release-macos)。后者正是两条发布工作流通过runs-on请求的自托管标签。
五、安装 Xcode:AMI 之外的必经步骤
AWS 的 macOS AMI只包含 Command Line Tools,不包含完整 Xcode 应用。因此注册 Runner 之前,需要通过 Session Manager 以ec2-user身份连接实例,用引导阶段装好的xcodes安装最新稳定版 Xcode:
xcodes install --latest --experimental-unxip sudo xcodebuild -license accept xcodebuild -runFirstLaunch xcodebuild -downloadComponent MetalToolchain xcodebuild -version各命令的作用:
xcodes install --latest --experimental-unxip:下载并安装最新稳定版 Xcode,--experimental-unxip启用更快的解包路径;sudo xcodebuild -license accept:接受 Xcode 许可协议(签名流程必需);xcodebuild -runFirstLaunch:完成首次启动初始化(安装额外组件、注册 SDK);xcodebuild -downloadComponent MetalToolchain:拉取Metal 工具链——screenpipe 的 macOS 录制依赖 Metal 相关的编译与链接能力,缺失会导致链接失败;xcodebuild -version:确认安装结果。
六、注册 Runner:从 SSH 通道到 launchd 守护服务
6.1 前置条件与触发命令
Xcode 就绪后,先完成gh的认证(需要仓库管理员权限,用于申请 Runner 注册令牌),然后执行:
AWS_REGION=us-east-2 ./infra/release-mac-runner/configure-runner.sh6.2 注册脚本执行流程
configure-runner.sh 内部完成以下工作:
- 申请注册令牌:通过
gh api向repos/screenpipe/screenpipe/actions/runners/registration-token发起 POST,换取一次性REGISTRATION_TOKEN; - 定位实例:优先按
Name=screenpipe-release-mac标签查询实例;查询为空时回退到 CloudFormation 栈输出中的InstanceId;仍可用INSTANCE_ID=i-...环境变量显式指定; - SSM 远程执行:以
AWS-RunShellScript文档向实例发送一串命令,依次执行:xcodebuild -runFirstLaunch与xcodebuild -downloadComponent MetalToolchain(确保签名/链接组件就绪);- 以
ec2-user身份运行 Runner 配置:config.sh --unattended --replace --url https://github.com/screenpipe/screenpipe --token ... --name screenpipe-release-mac --labels screenpipe-release-macos --work /Users/ec2-user/screenpipe-cache/work——注意Runner 名称、标签、工作目录都在此一次性确定; ./svc.sh install安装 Runner 服务,并将生成的 launchd plist 从用户级目录迁移到系统级/Library/LaunchDaemons/,chown root:wheel、chmod 0644,最后launchctl bootstrap system使其成为开机自启的无头(headless)守护服务;- 通过
plutil为 plist 注入EnvironmentVariables.PATH(包含 Homebrew、Cargo 等路径),确保 launchd 环境下的 PATH 完整;
- 等待与校验:
aws ssm wait command-executed等待命令执行完成,再拉取StandardOutputContent/StandardErrorContent检查结果; - 确认注册:调用
gh api repos/screenpipe/screenpipe/actions/runners查询名为screenpipe-release-mac的 Runner,输出其name、status、busy与labels。
6.3 服务化与隔离性
Runner 以launchd LaunchDaemon形式常驻(而非手动常驻进程),具备以下特性:
- 系统级服务随实例启动自动拉起,无需人工登录;
screenpipe-release-mac以仓库级(repository-level)Runner注册,而非组织级,天然限制其只能服务本仓库的工作流;- 标签
screenpipe-release-macos是唯一被Release App与Release Enterprise两条工作流请求的自托管标签;由于 Runner 属于上游仓库私有,Fork 无法访问该 Runner,从机制上杜绝了 Fork 借用发布节点执行代码的风险。
七、与发布工作流的衔接
在 release-app.yml 中,check_commit作业会根据提交类型动态决定 macOS 构建节点:常规路径使用 GitHub 官方macos-latest/macos-26,而发布相关提交(或手动workflow_dispatch触发)则将macos_arm_runner与macos_x64_runner均指向screenpipe-release-macos,随后构建矩阵中的 macOS 平台任务即落在自托管节点上:
- platform: ${{ needs.check_commit.outputs.macos_arm_runner || 'macos-latest' }} - platform: ${{ needs.check_commit.outputs.macos_x64_runner || 'macos-26' }}从源码结构看,这套"动态选择 Runner"的机制让日常开发使用官方节点、发布时刻切换到自托管节点成为可能,既节约成本又保证发布环境的高可用与缓存优势。Release Enterprise工作流复用同一标签,与文档中"该实例专门服务 macOS 发布任务"的描述一致。
八、日常运维要点
- 状态查询:栈输出与
gh api .../actions/runners查询均可确认 Runner 在线状态;CloudWatchStatusCheckFailed告警覆盖实例健康; - 实例管理:实例带终止保护(
DisableApiTermination: true),且 Host 与实例均采用RetainExceptOnCreate/Retain删除策略,普通删除栈操作不会销毁数据卷与宿主机,需在确认迁移完成后显式清理; - 缓存持久化:
/Users/ec2-user/screenpipe-cache位于不随实例终止删除的 gp3 根卷(2048 GiB)上,Runner 的--work目录与各类编译缓存共同复用该卷,这是"持久化 Runner"相比临时节点的核心收益; - 安全边界:无入站规则 + SSM 管理 + IMDSv2 + 仓库级 Runner + 发布类工作流专用标签,形成纵深防御;Fork 与外部仓库均无法触达该节点;
- 区域容量:EC2 Mac Dedicated Host 存在区域容量约束,部署脚本的三区域降级策略与宿主机复用逻辑正是为应对"特定区域暂时无法分配"的场景而设计。
九、适用前提与限制
- 本套栈面向screenpipe 内部发布基础设施,其认证(
gh仓库管理员权限)、区域(美国区域)、实例类型(非 Ultra Apple 芯片)均为该项目的特定配置,直接复用时需按自身账号容量与合规要求调整; - EC2 Mac 按 Dedicated Host 计费,常驻节点意味着持续成本,适合发布频率较高的项目;若发布频率低,官方托管 Runner + 缓存恢复可能是更经济的组合;
- 脚本中的 Runner 版本(v2.336.0)、Xcode 安装方式、AMI 参数均为当前仓库锁定的版本,升级时需同步更新 template.yml 与 configure-runner.sh。
十、小结
screenpipe 的持久化 macOS 发布 Runner 是一套"基础设施即代码"的完整范式:deploy.sh负责在多个美国区域间按性能降级顺序自动完成 Dedicated Host 分配与 CloudFormation 建栈,template.yml用一份模板固化网络、安全、存储、IAM、启动引导与监控的全部细节,configure-runner.sh则借助 SSM 通道完成 Xcode 就绪检查、Runner 注册与 launchd 服务化。三者配合,让Release App与Release Enterprise的 macOS 发布任务始终有一个"随叫随到、缓存常热、无入站攻击面"的专属节点,是自托管 CI 基础设施中颇具参考价值的落地样板。
【免费下载链接】screenpipeYC (S26) | Open Computer History | Record your screen continuously locally and provide context to your agents (Claude, Codex, Openclaw, Hermes, Runner...)项目地址: https://gitcode.com/GitHub_Trending/sc/screenpipe
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考