Screenpipe Windows 开发镜像实战:基于 Azure Compute Gallery 的不可变开发环境与一次性 VM 自主任务流水线
【免费下载链接】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
本指南以 Screenpipe 仓库的 .claude/skills/develop-screenpipe-windows/references/dev-image.md 为骨架,结合 infra/windows-dev-image 目录下的真实构建脚本、分发脚本与验证脚本,完整讲解 Screenpipe 如何为 Windows 原生开发搭建一套"镜像即制品、VM 即任务"的自主开发基础设施。读完本文,你将掌握:镜像契约包含哪些工具与路径约束、如何构建并发布一个不可变的 Azure Compute Gallery 镜像版本、如何通过一次性的 task blob 派发一个完全自治的 Windows 开发 VM,以及这套流水线如何在不开放入站桌面、不烘焙任何密钥的前提下完成 Codex 执行、原生测试、桌面录像、证据上传与 PR 交付。
设计前提:不可变镜像,而非克隆 release runner
Screenpipe 的 Windows 原生开发(编译器、进程、服务、本地 API、桌面捕获、安装器等)需要一个可重复、可验证的 Windows 环境。这套方案的核心决策是:
可复用的单元是 Azure Compute Gallery 中一个不可变的 Windows 11 开发镜像,而不是对实时 release builder 的克隆。
镜像只构建或刷新一次;每个任务从某个已通过验证的镜像版本拉起一台一次性 VM,任务结束后只销毁该 VM。VM 使用自己的自动登录控制台桌面完成交互式验证与录像,不存在操作员会话,也没有入站桌面访问。经过一次性派发后,任务执行与派发电脑完全解耦——派发方可以立即断网或关机。
这套工作流的入口约束记录在 .claude/skills/develop-screenpipe-windows/SKILL.md 中:所有 Windows 原生工作都必须使用该镜像创建的 VM,禁止交叉编译后当作 Windows 证据,也禁止在实时 release builder 上开发或将其克隆作为每任务兜底。若不存在已验证的开发镜像,应停止并报告需要一次性构建镜像。
镜像契约(Image Contract)
镜像必须从受支持的 Windows 11 桌面版基座构建,并满足以下契约:
- 交互式控制台桌面已启用,且 RDP 被禁用;
- 预装完整工具链:Codex CLI、Git、Git LFS、PowerShell 7、Bun、Node.js、Rust/Cargo、sccache、Visual Studio Build Tools、LLVM/libclang、CMake、Ninja、7-Zip、jq、FFmpeg;
- 内置 Screenpipe 的自主 bootstrap/worker/dispatcher、证据录像器与凭据隔离的 Codex 启动器;
- 支持长路径,并配置 Screenpipe 构建所需的机器级环境变量;
- 稳定的源码路径(如
C:\src\screenpipe)与真正短小的CARGO_TARGET_DIR(如C:\spdev); - 由仓库支持的构建命令产出的预热数据:
debug-devRust 缓存、原生依赖缓存、Bun 缓存与 sccache 数据。
VCToolsRedistDir:一个必须提前发现的环境变量
当 Visual Studio Build Tools 安装在非标准位置时,必须在镜像构建期间发现并设置VCToolsRedistDir。文档特别强调:仅仅重定向工作区target目录,无法阻止深层 CMake/MSBuild 路径以MSB6003或缺失.tlog文件失败——有效的 Cargo target 路径本身必须短。
这与 provision.ps1 的实现完全对应:脚本在C:\BuildTools\VC\Redist\MSVC下查找版本号最大的子目录,将其完整路径(带尾部反斜杠)写入机器级环境变量VCToolsRedistDir;若找不到则直接抛出错误中止构建。
镜像内禁止什么
- 不安装、不注册 GitHub Actions runner;
- 不烘焙任何 API Key、GitHub 凭据、签名材料、Azure 控制面凭据、源码分支、密码或用户数据;
- OpenAI 与 GitHub 凭据只允许在运行时通过托管身份(Managed Identity)与 Key Vault 获取。
构建并发布一个镜像版本
镜像构建被当作一次受控的基础设施操作,完整流程如下:
- 基于声明的 Windows 11 基座、使用带版本的 provisioning 脚本构建;
- 安装镜像契约,并在全新 shell 与交互式桌面会话中逐一验证每个工具;
- 预热缓存:在稳定源码路径使用干净的 Screenpipe checkout 预热缓存,随后移除仓库凭据与任务相关的源码状态;
- 清理残留:删除日志、临时安装器、token、凭据、机器特定标识符等 provisioning 残留;
- 设置设备策略
DisablePrivacyExperience,用 Sysprep 与受支持的oobeSystemanswer 文件做 generalize(answer 包含HideEULAPage、HideOnlineAccountScreens、HideWirelessSetupInOOBE、HideOEMRegistrationScreen、ProtectYourPC=3,不得使用SkipMachineOOBE),发布不可变的 Compute Gallery 镜像版本,并将基座版本、provisioning commit、工具版本、缓存预热 commit 记录为镜像元数据; - 从该精确镜像版本拉起一台独立的一次性 VM,派发一个精确不可变的自主可视化 smoke-task blob,验证 OOBE 策略、运行时 Codex 认证、控制台自动登录、受支持的原生测试、桌面录像、私有证据上传、凭据清理与关机,并确认TCP 3389 无监听、VM无入站 NSG 规则;
- 只有在该全新 VM smoke 测试通过后,才将镜像版本标记为可用。绝不把任务移到未验证或部分构建的版本上。
对应仓库实现:
- provision.ps1 是版本化的 provisioning 脚本,内部固定了工具版本:Rust
1.94.0、Bun1.3.10、Node22.23.2、sccache0.16.0、Codex0.149.1,并生成image-manifest.json记录 builtAt、windows 版本、codex/bun/rustc/cargo/sccache/node 版本、warmCommit、sourcePath、cargoTargetDir、vcToolsRedistDir 等元数据; - unattend.xml 正是上述
oobeSystemanswer 文件,包含文档列出的全部五个 OOBE 隐藏项与ProtectYourPC=3; - README.md 给出了入口命令:
./infra/windows-dev-image/build.sh 2026.8.24(以版本号为参数)。若主机在精确打标的构建 VM 创建后、provisioning 开始前失去 Azure 连接,可用RESUME_EXISTING_BUILD=true显式恢复该 VM;正常运行时脚本会拒绝已存在的构建资源组,恢复模式则会先校验 VM 的 project/environment/image-version 标签再使用。
镜像发布后的验收
发布后,从精确镜像版本拉起全新 VM 并运行smoke.ps1 -AutonomousVisualTaskId <task-id>,然后派发该精确的不可变可视化 smoke 任务,验证控制台自动登录、原生验证、录制/证据上传、凭据清理与关机(无入站规则、无操作员会话),最后才设置validated=true。只有这一次"全新镜像验收"运行允许ALLOW_UNVALIDATED_IMAGE_FOR_SMOKE=true,所有开发派发都要求已验证镜像。
smoke.ps1 具体检查:必选路径(image-manifest.json、with-openai-key.ps1、bootstrap/worker/dispatcher 脚本、桌面录像脚本、C:\Tools\bun\bun.exe、C:\Tools\cargo\bin\cargo.exe、C:\Tools\sccache\sccache.exe、C:\spdev);逐条执行codex.cmd --version、git.exe --version、gh.exe --version、bun/cargo/rustc/sccache --version、ffmpeg.exe -version并校验退出码;通过with-openai-key.ps1 -Command codex.cmd -CommandArgs login --with-api-key -PipeKeyToStdin验证运行时 Codex 认证后立即删除.codex;最后断言 TermService 为 Disabled/Stopped、TCP 3389 无监听、DisablePrivacyExperience=1。
镜像刷新与回滚
当 Windows 基座、Visual Studio、Rust、Bun、原生依赖、Codex 或 Screenpipe 构建契约发生变化时,应主动刷新镜像,并至少保留一个此前已验证的版本用于回滚。镜像构建必须不读取、不停机、不修改、不快照、不克隆实时 release builder。
每任务 VM 循环(Per-task VM Loop)
每个 Windows 原生任务都遵循如下 8 步循环:
- 解析并记录不可变的已验证镜像版本;
- dispatcher 创建一台唯一打标的一次性 VM及其独立 OS 磁盘、NIC、deny-inbound 网络控制、运行时托管身份与关机兜底;上传一个精确 task blob 与带版本的运行时脚本,只启动一次 bootstrap 后即退出,不做轮询;
- VM 从
C:\src\screenpipe启动,将预热的源码状态替换为请求的精确 Git 修订,并在开始工作前验证git rev-parse HEAD; - 对
src-tauri运行bun run test:tauri <one-filter> -- --nocapture——绝不在此运行裸 Cargo 或 Tauri 命令; - 将缓冲的 Azure 命令输出重定向到持久 guest 日志,记录显式原生退出码与测试 SHA;在严格 PowerShell 下隔离嘈杂的原生 stderr,避免无害 banner 变成终止错误;
- worker 录制其控制台桌面用于可视化/交互式验收,使用有界的 recorder-readiness 循环,交付前将每个本地与上传的证据文件隔离在不可变任务 ID 之下;
- 当任务授权交付时,worker 推送分支、创建 PR,并在 PR 正文中加入六天只读的 user-delegation SAS 视频链接与精确测试 commit/tree;租约保持在 Azure 七天 user-delegation 限制内并留出时钟偏移余量。VM没有任何签名、发布或发行权限;
- 在
finally块中:上传失败证据、移除环境凭据与 Codex 认证状态、禁用自动登录、关机;稍后删除精确的已停止 VM 资源并验证其不存在,保留共享的不可变镜像。
若镜像缺失、过期或未通过全新 VM smoke 测试,应修复并重新发布镜像,而不是退化为反复 provisioning 冷 VM 或克隆 release runner。
源码级实现:从派发到关机的完整调用链
派发端:dispatch-autonomous.sh
dispatch-autonomous.sh 是整个流程的"一发即忘"入口,核心行为如下:
- 入参校验:
task-id必须匹配[a-z0-9][a-z0-9-]{2,39},base-sha必须为 40 位小写十六进制;RESUME_LOCAL_HEAD(可选的既有 PR head SHA)也必须是 40 位十六进制; - 镜像门禁:用
az sig image-version show --ids <image-id> --query tags.validated读取validated标签,非true且未设置ALLOW_UNVALIDATED_IMAGE_FOR_SMOKE=true时直接拒绝派发; - 任务 blob:用
jq组装task.json(taskId、baseSha、branch、prompt、storage 目标、Key Vault 名称kv-scpwin-aa28、OpenAI 密钥名chatgpt-codex-api-key、GitHub 密钥名screenpipe-github-pr-token、推送远端、PR 标题/正文/commit message),连同autonomous-worker.ps1、autonomous-bootstrap.ps1、dispatch-autonomous.sh一并上传到windows-autonomous/<task-id>/前缀下(--overwrite false); - VM 创建:拒绝已存在的任务资源组;创建 TrustedLaunch VM,默认区域
westus2、大小Standard_D16s_v5(可用AZURE_LOCATION、AZURE_VM_SIZE覆盖),--nsg-rule NONE,Standard SKU 公网 IP; - 网络硬校验:创建后查询 NIC 关联的 NSG,断言入站安全规则数量为 0,断言公网 IP 为 Standard SKU,否则拒绝派发;
- 关机兜底:设置
az vm auto-shutdown(6 小时后); - 一次性执行:将 bootstrap 脚本注入为 RunPowerShellScript,
az vm run-command invoke仅启动一次后即打印派发成功与持久结果前缀,脚本退出。
引导端:autonomous-bootstrap.ps1
VM 开机后,bootstrap 完成"把控制权交给自主 worker"的准备:
- 重设 OOBE
DisablePrivacyExperience=1,并额外设置 Edge 的HideFirstRunExperience=1; - 通过 IMDS(
http://169.254.169.254/metadata/identity/oauth2/token)获取托管身份 token,从任务 blob 前缀拉取 worker/bootstrap/dispatcher 脚本与task.json到C:\screenpipe-worker; - 将本地
screenpipe账户密码重置为随机值,配置 WinlogonAutoAdminLogon=1(自动登录控制台桌面); - 注册计划任务
ScreenpipeAutonomousWorker:登录触发、以交互式最高权限运行autonomous-worker.ps1、执行时限 6 小时、失败重启 2 次; - 清空密码变量后
shutdown /r重启进入 worker 阶段。
执行端:autonomous-worker.ps1
autonomous-worker.ps1 是任务的主体,对应文档第 3~8 步:
- 等待交互桌面:轮询当前会话内 explorer 进程出现且 OOBE/CloudExperienceHost 等进程退出,最多 90 次尝试;
- 启动桌面录像:用
ffmpeg -f gdigrab -framerate 15 -draw_mouse 1 -i desktop录制acceptance.mp4,带 30 次重试的 readiness 循环与FFREPORT日志; - 运行时取密钥:从 Key Vault 拉取 GitHub token 与 OpenAI key(30 次重试),用
codex.cmd login --with-api-key完成 Codex 认证,gh auth setup-git配置 Git; - 代码就位:fetch 精确 base SHA(支持
resumeLocalHead且校验其是 base 的后代),checkout --force -B <branch>,clean -ffd --exclude target,配置 git 身份; - Codex 执行:将任务 prompt 与执行契约写入
prompt.txt,通过codex.cmd exec --ephemeral --sandbox danger-full-access -c approval_policy=never --json运行,输出codex.jsonl与codex-final.md;Codex 退出码非零但留下可评审结果时,由外层确定性校验决定是否交付; - 确定性校验:PowerShell 解析检查
infra\windows-dev-image\*.ps1、bash -n检查 shell 脚本、git diff --check,并断言工作树干净; - 交付:
git push --force-with-lease(含远程分支租约 SHA);以用户委派身份登录(az login --identity)生成六天只读 SAS 视频链接,写入 PR 正文(含精确 tested commit 与 tree),用gh pr create/pr edit创建或更新 PR,并在浏览器打开 PR 留证; - 收尾(finally):停止录像、写
evidence.json(taskId、state、prUrl、testedHead、videoSha256 等)、上传全部证据 blob(视频、日志、jsonl、manifest),移除GH_TOKEN/OPENAI_API_KEY、删除CODEX_HOME与.azure、关闭 AutoAdminLogon 并删除 DefaultPassword,最后Stop-Computer -Force。
安全边界:无入站、无烘焙、运行时取钥
整套设计的信任边界可以概括为三条:
- 无入站面:VM 的 NSG 零自定义入站规则,TermService(RDP,TCP 3389)在 provisioning 阶段即被禁用并停止(见 provision.ps1),smoke 测试强制复核;控制台桌面仅供 VM 自己交互,无操作员会话;
- 无烘焙密钥:镜像内不落任何凭据;provisioning 末尾会删除
.npmrc、.git-credentials与临时目录残留(见 provision.ps1); - 运行时凭据最小化:凭据只通过运行时托管身份 + Key Vault 注入;
with-openai-key.ps1启动器仅在单条命令生命周期内把 OpenAI Key 注入环境并在finally中清除(见 provision.ps1),worker 侧同理用完即清并执行az logout。
此外,任务 VM不具备任何发布权限——不签名、不发布 release、不打 tag、不写 updater 元数据;整个工作流不允许发布应用版本。
操作与排障要点
- 构建/刷新入口:
./infra/windows-dev-image/build.sh <版本号>,恢复模式RESUME_EXISTING_BUILD=true; - 派发入口:
AZURE_IMAGE_VERSION_ID=<gallery版本资源ID> AZURE_WORKER_IDENTITY_RESOURCE_ID=<托管身份资源ID> ./infra/windows-dev-image/dispatch-autonomous.sh <task-id> <base-sha> <prompt-file>;全新镜像验收额外加ALLOW_UNVALIDATED_IMAGE_FOR_SMOKE=true; - 可覆盖的环境变量:
AZURE_SUBSCRIPTION_ID、AZURE_LOCATION(默认westus2)、AZURE_VM_SIZE(默认Standard_D16s_v5)、AZURE_GALLERY_RESOURCE_GROUP、AZURE_BUILD_RESOURCE_GROUP、AZURE_GALLERY_NAME、AZURE_IMAGE_DEFINITION; - 持久结果:派发后从
https://<storage>.blob.core.windows.net/evidence/windows-autonomous/<task-id>/拉取证据;每个任务使用独立资源组与精确 gallery 镜像 ID,任务结束后删除对应资源组并确认资源不存在。
这套"不可变镜像 + 一次性 VM + 一发即忘派发"的模型,把 Windows 原生开发中最难复现的环境问题收敛为"镜像版本即环境真相":只要镜像版本经过 smoke 验收,任何一次任务都在完全相同的环境语义下执行,且不依赖任何常驻控制器进程。对于需要长期维护 Windows 原生代码库的团队,这是一个可直接借鉴的、把开发环境当作受版本控制制品来管理的完整实践。
【免费下载链接】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),仅供参考