agent-dev-env 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/assets/bridge/bridge.js +202 -0
- package/dist/assets/guest/guest-agent-mac.js +586 -0
- package/dist/assets/guest/guest-agent-ubuntu.js +481 -0
- package/dist/assets/guest/guest-agent-windows.js +466 -0
- package/dist/assets/images/mac/CHANGELOG.md +265 -0
- package/dist/assets/images/mac/README.md +63 -0
- package/dist/assets/images/mac/sandbox.pkr.hcl +468 -0
- package/dist/assets/images/mac/vars/sandbox-macos-tahoe.pkrvars.hcl +39 -0
- package/dist/assets/images/ubuntu-arm64-vmware/CHANGELOG.md +239 -0
- package/dist/assets/images/ubuntu-arm64-vmware/README.md +177 -0
- package/dist/assets/images/ubuntu-arm64-vmware/autoinstall/meta-data +2 -0
- package/dist/assets/images/ubuntu-arm64-vmware/autoinstall/user-data +34 -0
- package/dist/assets/images/ubuntu-arm64-vmware/sandbox.pkr.hcl +776 -0
- package/dist/assets/images/ubuntu-arm64-vmware/vars/sandbox-ubuntu-24-04-arm64-vmware.pkrvars.hcl +71 -0
- package/dist/assets/images/windows-arm64-qemu/CHANGELOG.md +209 -0
- package/dist/assets/images/windows-arm64-qemu/README.md +142 -0
- package/dist/assets/images/windows-arm64-qemu/autounattend.xml +325 -0
- package/dist/assets/images/windows-arm64-qemu/qemu-with-tpm.sh +170 -0
- package/dist/assets/images/windows-arm64-qemu/sandbox.pkr.hcl +1012 -0
- package/dist/assets/images/windows-arm64-qemu/vars/sandbox-windows-11-arm64-qemu.pkrvars.hcl +83 -0
- package/dist/assets/images/windows-arm64-vmware/CHANGELOG.md +270 -0
- package/dist/assets/images/windows-arm64-vmware/README.md +171 -0
- package/dist/assets/images/windows-arm64-vmware/autounattend.xml +311 -0
- package/dist/assets/images/windows-arm64-vmware/sandbox.pkr.hcl +1036 -0
- package/dist/assets/images/windows-arm64-vmware/vars/sandbox-windows-11-arm64-vmware.pkrvars.hcl +83 -0
- package/dist/assets/rules/agent-rules-linux.md +68 -0
- package/dist/assets/rules/agent-rules.md +71 -0
- package/dist/assets/watchdog/watch-build-ocr.swift +40 -0
- package/dist/assets/watchdog/watch-build.py +275 -0
- package/dist/cli.js +52 -0
- package/dist/commands/delete.js +179 -0
- package/dist/commands/doctor.js +165 -0
- package/dist/commands/list.js +38 -0
- package/dist/commands/not-yet.js +15 -0
- package/dist/commands/register.js +150 -0
- package/dist/commands/run.js +65 -0
- package/dist/commands/status.js +142 -0
- package/dist/commands/stop.js +186 -0
- package/dist/commands/sync.js +98 -0
- package/dist/lib/exec.js +250 -0
- package/dist/lib/ghcr.js +82 -0
- package/dist/lib/git.js +100 -0
- package/dist/lib/logger.js +87 -0
- package/dist/lib/network.js +63 -0
- package/dist/lib/paths.js +116 -0
- package/dist/lib/platform.js +87 -0
- package/dist/lib/prompt.js +66 -0
- package/dist/lib/qemu.js +373 -0
- package/dist/lib/regex.js +9 -0
- package/dist/lib/ssh.js +230 -0
- package/dist/lib/tart.js +210 -0
- package/dist/lib/template.js +35 -0
- package/dist/lib/vars.js +99 -0
- package/dist/lib/vmrun.js +334 -0
- package/dist/lifecycle/build-macos.js +23 -0
- package/dist/lifecycle/build-qemu.js +204 -0
- package/dist/lifecycle/build-shared.js +355 -0
- package/dist/lifecycle/build-ubuntu.js +184 -0
- package/dist/lifecycle/build-watchdog.js +146 -0
- package/dist/lifecycle/build-windows-vmware.js +112 -0
- package/dist/lifecycle/build.js +55 -0
- package/dist/lifecycle/catalog.js +165 -0
- package/dist/lifecycle/deploy.js +161 -0
- package/dist/lifecycle/tag.js +77 -0
- package/dist/lifecycle/watch-build.js +64 -0
- package/dist/runners/bridges.js +153 -0
- package/dist/runners/framework.js +66 -0
- package/dist/runners/macos-bridges.js +143 -0
- package/dist/runners/macos-guest.js +76 -0
- package/dist/runners/macos-rules.js +77 -0
- package/dist/runners/macos-summary.js +145 -0
- package/dist/runners/macos.js +258 -0
- package/dist/runners/openchamber.js +60 -0
- package/dist/runners/options.js +56 -0
- package/dist/runners/qemu-image.js +183 -0
- package/dist/runners/rules.js +49 -0
- package/dist/runners/ubuntu-bridges.js +154 -0
- package/dist/runners/ubuntu-guest.js +136 -0
- package/dist/runners/ubuntu-image.js +26 -0
- package/dist/runners/ubuntu-rules.js +86 -0
- package/dist/runners/ubuntu-shared.js +65 -0
- package/dist/runners/ubuntu-summary.js +141 -0
- package/dist/runners/ubuntu.js +117 -0
- package/dist/runners/vmware-common.js +158 -0
- package/dist/runners/vmware-image.js +242 -0
- package/dist/runners/windows-autologon.js +119 -0
- package/dist/runners/windows-bridges.js +136 -0
- package/dist/runners/windows-guest.js +213 -0
- package/dist/runners/windows-image.js +23 -0
- package/dist/runners/windows-qemu-summary.js +85 -0
- package/dist/runners/windows-qemu.js +189 -0
- package/dist/runners/windows-shared.js +68 -0
- package/dist/runners/windows-summary.js +104 -0
- package/dist/runners/windows.js +96 -0
- package/dist/settings/common.js +107 -0
- package/dist/settings/macos-copy.js +223 -0
- package/dist/settings/macos.js +41 -0
- package/dist/settings/ubuntu-copy.js +223 -0
- package/dist/settings/ubuntu.js +68 -0
- package/package.json +52 -0
|
@@ -0,0 +1,1012 @@
|
|
|
1
|
+
packer {
|
|
2
|
+
required_version = ">= 1.10.0"
|
|
3
|
+
|
|
4
|
+
required_plugins {
|
|
5
|
+
qemu = {
|
|
6
|
+
version = ">= 1.1.0"
|
|
7
|
+
source = "github.com/hashicorp/qemu"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Variables
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
#
|
|
16
|
+
# Most variables mirror the macOS template (images/mac/sandbox.pkr.hcl) so
|
|
17
|
+
# the vars files look and feel the same. The Windows build differs in the
|
|
18
|
+
# mechanics: the OS comes from a bring-your-own ISO instead of a GHCR base
|
|
19
|
+
# image, provisioning happens over WinRM instead of SSH, and the image is a
|
|
20
|
+
# qcow2 disk instead of a Tart VM.
|
|
21
|
+
|
|
22
|
+
variable "windows_version" {
|
|
23
|
+
type = string
|
|
24
|
+
description = "Windows guest version, e.g. '11'; part of the image name (sandbox-windows-<windows_version>-arm64-qemu)."
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
variable "image_version" {
|
|
28
|
+
type = string
|
|
29
|
+
description = "Semantic version this image is published under; bump it + add a CHANGELOG.md entry per release."
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# --- Build layout ---
|
|
33
|
+
#
|
|
34
|
+
# Built images and their scratch land in a top-level build/ directory:
|
|
35
|
+
# build/windows-arm64-qemu/output (Packer's output_directory) and
|
|
36
|
+
# build/windows-arm64-qemu/drivers/ (staged into the unattend CD).
|
|
37
|
+
# The agent-dev-env CLI build flow computes the directory and passes it
|
|
38
|
+
# in; the default keeps a bare `packer build` from the platform dir
|
|
39
|
+
# working.
|
|
40
|
+
|
|
41
|
+
variable "build_dir" {
|
|
42
|
+
type = string
|
|
43
|
+
default = "."
|
|
44
|
+
description = "Per-image build directory: <build_dir>/output holds the built image and <build_dir>/drivers the staged unattend-CD drivers. Set by `agent-dev-env build` to build/windows-arm64-qemu/."
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# --- Windows install ISO (bring-your-own, see images/windows-arm64-qemu/README.md) ---
|
|
48
|
+
|
|
49
|
+
variable "iso_path" {
|
|
50
|
+
type = string
|
|
51
|
+
description = "Absolute path to the Windows 11 ARM64 ISO. Set by the agent-dev-env CLI build flow (PKR_VAR_iso_path); the ISO is not redistributable so it cannot live in the repo. Required — validate with `-var iso_path=/path/to/iso`."
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
variable "iso_sha256" {
|
|
55
|
+
type = string
|
|
56
|
+
default = ""
|
|
57
|
+
description = "SHA256 of the Windows ISO as published on the Microsoft download page. Read by the agent-dev-env CLI build flow for verification, not by Packer itself (iso_checksum is 'none')."
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# --- virtio-win drivers (ARM64) ---
|
|
61
|
+
|
|
62
|
+
variable "virtio_win_iso_path" {
|
|
63
|
+
type = string
|
|
64
|
+
default = ""
|
|
65
|
+
description = "Absolute path to virtio-win.iso (release 0.1.240+). Set by the agent-dev-env CLI build flow; the flow stages the ARM64 driver subset into drivers/staging/ and qemu-with-tpm.sh attaches the full ISO as a CD."
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
variable "virtio_win_url" {
|
|
69
|
+
type = string
|
|
70
|
+
default = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/virtio-win.iso"
|
|
71
|
+
description = "Download URL used by the agent-dev-env CLI build flow when VIRTIO_WIN_ISO_PATH is unset."
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
variable "virtio_win_sha256" {
|
|
75
|
+
type = string
|
|
76
|
+
default = ""
|
|
77
|
+
description = "SHA256 of virtio-win.iso. Read by the agent-dev-env CLI build flow for verification (empty = skip)."
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# --- Toolchain versions (installed via Chocolatey) ---
|
|
81
|
+
|
|
82
|
+
variable "nodejs_version" {
|
|
83
|
+
type = string
|
|
84
|
+
description = "Node.js version, e.g. '26.8.1' (choco package version)."
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
variable "python_version" {
|
|
88
|
+
type = string
|
|
89
|
+
description = "Python version, e.g. '3.13.1' (choco package version)."
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
variable "github_cli_version" {
|
|
93
|
+
type = string
|
|
94
|
+
description = "GitHub CLI version, e.g. '2.97.0' (choco package version)."
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
variable "ripgrep_version" {
|
|
98
|
+
type = string
|
|
99
|
+
description = "ripgrep version, e.g. '15.2.0' (choco package version)."
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
variable "git_version" {
|
|
103
|
+
type = string
|
|
104
|
+
description = "Git version, e.g. '2.55.0.4' (choco package version)."
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
variable "jq_version" {
|
|
108
|
+
type = string
|
|
109
|
+
description = "jq version, e.g. '1.8.1' (choco package version)."
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
variable "open_code_review_version" {
|
|
113
|
+
type = string
|
|
114
|
+
description = "open-code-review (ocr) version installed via npm."
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
variable "chrome_version" {
|
|
118
|
+
type = string
|
|
119
|
+
description = "Google Chrome version (Chrome for Testing snapshot, e.g. '152.0.7977.54')."
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
variable "chrome_sha256" {
|
|
123
|
+
type = string
|
|
124
|
+
description = "SHA256 of the CfT chrome-win64.zip for chrome_version."
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# --- C/C++ + cross-language toolchains (brought over from AdGuard's
|
|
128
|
+
# build-agent-images windows2022-vs2022 image) ---
|
|
129
|
+
|
|
130
|
+
variable "vs_buildtools_version" {
|
|
131
|
+
type = string
|
|
132
|
+
description = "VS2022 Build Tools choco package version, e.g. '117.14.37' (the installer is then finalized with setup.exe + the .NET SDK / VC++ / Win11 SDK components)."
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
variable "go_version" {
|
|
136
|
+
type = string
|
|
137
|
+
description = "Go version (choco package version)."
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
variable "rust_version" {
|
|
141
|
+
type = string
|
|
142
|
+
description = "Rust toolchain version installed via rustup (e.g. '1.95'; rustup resolves the latest patch)."
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
variable "wixtoolset_version" {
|
|
146
|
+
type = string
|
|
147
|
+
description = "WiX Toolset version (choco package version)."
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
variable "protoc_version" {
|
|
151
|
+
type = string
|
|
152
|
+
description = "protobuf compiler (protoc) version (choco package version)."
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
variable "nasm_version" {
|
|
156
|
+
type = string
|
|
157
|
+
description = "NASM assembler version (choco package version)."
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
variable "llvm_version" {
|
|
161
|
+
type = string
|
|
162
|
+
description = "LLVM version (choco package version)."
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
variable "vim_version" {
|
|
166
|
+
type = string
|
|
167
|
+
description = "Vim version (choco package version)."
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
variable "nuget_version" {
|
|
171
|
+
type = string
|
|
172
|
+
description = "NuGet command-line client version (choco package version)."
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
variable "mingw_version" {
|
|
176
|
+
type = string
|
|
177
|
+
description = "MinGW-w64 GCC toolchain version (choco package version; provides make + mingw gcc)."
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
variable "make_version" {
|
|
181
|
+
type = string
|
|
182
|
+
description = "GNU make version (choco package version)."
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# --- VM resources ---
|
|
186
|
+
|
|
187
|
+
variable "disk_size" {
|
|
188
|
+
type = number
|
|
189
|
+
default = 100
|
|
190
|
+
description = "VM disk size in GB."
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
variable "cpu_count" {
|
|
194
|
+
type = number
|
|
195
|
+
default = 4
|
|
196
|
+
description = "CPU count of the VM."
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
variable "memory_gb" {
|
|
200
|
+
type = number
|
|
201
|
+
default = 8
|
|
202
|
+
description = "RAM of the VM in GB."
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# --- WinRM credentials (baked into autounattend.xml — keep in sync) ---
|
|
206
|
+
|
|
207
|
+
variable "winrm_username" {
|
|
208
|
+
type = string
|
|
209
|
+
default = "Administrator"
|
|
210
|
+
description = "User used for WinRM provisioning; must match the account autounattend.xml creates."
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
variable "winrm_password" {
|
|
214
|
+
type = string
|
|
215
|
+
default = "sandbox1"
|
|
216
|
+
sensitive = true
|
|
217
|
+
description = "Password of the provisioning account; must match autounattend.xml."
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# --- OpenChamber web UI ---
|
|
221
|
+
|
|
222
|
+
variable "openchamber_ui_password" {
|
|
223
|
+
type = string
|
|
224
|
+
default = "sandbox"
|
|
225
|
+
description = "Password protecting the OpenChamber web UI; required because the server binds to 0.0.0.0 (host access: http://127.0.0.1:4000)."
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
variable "openchamber_port" {
|
|
229
|
+
type = number
|
|
230
|
+
default = 4000
|
|
231
|
+
description = "TCP port the OpenChamber web UI listens on inside the guest."
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# --- QEMU specifics ---
|
|
235
|
+
|
|
236
|
+
variable "qemu_binary" {
|
|
237
|
+
type = string
|
|
238
|
+
default = "./qemu-with-tpm.sh"
|
|
239
|
+
description = "qemu binary (or wrapper) Packer invokes. The wrapper appends the TPM/ramfb/USB/CD rewrites Packer's qemuargs option cannot express (it replaces the generated args)."
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
variable "efi_firmware_code" {
|
|
243
|
+
type = string
|
|
244
|
+
default = "/opt/homebrew/share/qemu/edk2-aarch64-code.fd"
|
|
245
|
+
description = "UEFI firmware (read-only code image) for the ARM64 guest; ships with Homebrew's qemu."
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
variable "efi_firmware_vars" {
|
|
249
|
+
type = string
|
|
250
|
+
default = "/opt/homebrew/share/qemu/edk2-arm-vars.fd"
|
|
251
|
+
description = "UEFI firmware template for the writable NVRAM store; Packer copies it per build."
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
# ---------------------------------------------------------------------------
|
|
255
|
+
# Source: QEMU (aarch64 + Apple Silicon HVF)
|
|
256
|
+
# ---------------------------------------------------------------------------
|
|
257
|
+
#
|
|
258
|
+
# The recipe is proven on Apple Silicon (HVF can only virtualize ARM64
|
|
259
|
+
# guests, so x86_64 Windows would run under slow TCG emulation):
|
|
260
|
+
# - machine "virt,gic-version=max", cpu "host" (required by HVF; do NOT
|
|
261
|
+
# add virtualization=on — HVF cannot pass nested virt through),
|
|
262
|
+
# - UEFI via edk2 AAVMF firmware + swtpm TPM 2.0 (Win11 system
|
|
263
|
+
# requirements; swtpm is started by the build flow),
|
|
264
|
+
# - all CD-ROMs attached as usb-storage (the virt machine has no
|
|
265
|
+
# IDE/SATA controller and WinPE has in-box xHCI drivers) — done by
|
|
266
|
+
# qemu-with-tpm.sh because the plugin's qemuargs option would replace
|
|
267
|
+
# its auto-generated args,
|
|
268
|
+
# - the ARM64 virtio drivers (viostor/vioscsi/NetKVM) are staged into
|
|
269
|
+
# the same CD as autounattend.xml (drivers/staging/), because WinPE
|
|
270
|
+
# drive-letter enumeration on ARM64 + EFI is non-deterministic.
|
|
271
|
+
#
|
|
272
|
+
# See images/windows-arm64-qemu/README.md and DEVELOPMENT.md for the full flow.
|
|
273
|
+
|
|
274
|
+
source "qemu" "windows" {
|
|
275
|
+
vm_name = "sandbox-windows-${var.windows_version}-arm64-qemu.qcow2"
|
|
276
|
+
output_directory = "${var.build_dir}/output"
|
|
277
|
+
|
|
278
|
+
# The build flow (agent-dev-env build) verifies the ISO against
|
|
279
|
+
# var.iso_sha256 before Packer runs; iso_checksum is "none" so Packer
|
|
280
|
+
# does not re-verify.
|
|
281
|
+
iso_url = var.iso_path
|
|
282
|
+
iso_checksum = "none"
|
|
283
|
+
|
|
284
|
+
cpus = var.cpu_count
|
|
285
|
+
memory = var.memory_gb * 1024
|
|
286
|
+
disk_size = "${var.disk_size}G"
|
|
287
|
+
format = "qcow2"
|
|
288
|
+
disk_interface = "virtio"
|
|
289
|
+
net_device = "virtio-net-pci"
|
|
290
|
+
|
|
291
|
+
machine_type = "virt,gic-version=max"
|
|
292
|
+
accelerator = "hvf"
|
|
293
|
+
cpu_model = "host"
|
|
294
|
+
|
|
295
|
+
efi_boot = true
|
|
296
|
+
efi_firmware_code = var.efi_firmware_code
|
|
297
|
+
efi_firmware_vars = var.efi_firmware_vars
|
|
298
|
+
|
|
299
|
+
qemu_binary = var.qemu_binary
|
|
300
|
+
|
|
301
|
+
# Unattend CD: Windows Setup probes attached media for Autounattend.xml
|
|
302
|
+
# at the root and applies it before the language picker, so no keyboard
|
|
303
|
+
# input is needed — except for the firmware's ~5 s "Press any key to
|
|
304
|
+
# boot from CD" prompt, hence the enter-spam below.
|
|
305
|
+
cd_files = ["./autounattend.xml", "${var.build_dir}/drivers"]
|
|
306
|
+
cd_label = "UNATTEND"
|
|
307
|
+
|
|
308
|
+
# WinRM, not SSH: it comes up via autounattend's FirstLogonCommands
|
|
309
|
+
# before any OpenSSH server exists, and DISM online servicing needs the
|
|
310
|
+
# elevated token the provisioners request.
|
|
311
|
+
communicator = "winrm"
|
|
312
|
+
winrm_username = var.winrm_username
|
|
313
|
+
winrm_password = var.winrm_password
|
|
314
|
+
winrm_timeout = "90m"
|
|
315
|
+
winrm_port = 5985
|
|
316
|
+
|
|
317
|
+
# Headless: no QEMU window, so the cocoa quit-confirmation dialog can
|
|
318
|
+
# never stall a build (it popped up on every windowed run). Progress is
|
|
319
|
+
# still watchable via the plugin's VNC server (e.g. vncdotool captures).
|
|
320
|
+
headless = true
|
|
321
|
+
|
|
322
|
+
# VNC server for the build watchdog (bundled watch-build.py): pinned to a
|
|
323
|
+
# single port so the build flow can start the watchdog before
|
|
324
|
+
# `packer build` without scanning for the port.
|
|
325
|
+
vnc_bind_address = "127.0.0.1"
|
|
326
|
+
vnc_port_min = 5901
|
|
327
|
+
vnc_port_max = 5901
|
|
328
|
+
|
|
329
|
+
# Enter-spam: answers the firmware's "Press ESC in 1 second" shell prompt
|
|
330
|
+
# AND Windows' "Press any key to boot from CD or DVD" prompt on the first
|
|
331
|
+
# boot. Without it, a boot that lands in the EFI shell stalls forever
|
|
332
|
+
# (Windows cannot be booted reliably from the shell). The keys are typed
|
|
333
|
+
# within the first ~15 s, long before Setup's UI appears, so the Cancel
|
|
334
|
+
# dialog they trigger on the "Installing Windows 11" screen is auto-
|
|
335
|
+
# dismissed by the build watchdog (bundled watch-build.py on the host).
|
|
336
|
+
boot_wait = "1s"
|
|
337
|
+
boot_command = [
|
|
338
|
+
"<enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter><wait1><enter>",
|
|
339
|
+
]
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# ---------------------------------------------------------------------------
|
|
343
|
+
# Build
|
|
344
|
+
# ---------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
build {
|
|
347
|
+
sources = ["source.qemu.windows"]
|
|
348
|
+
|
|
349
|
+
# ===== Preamble: settle after OOBE, dump versions =====
|
|
350
|
+
provisioner "powershell" {
|
|
351
|
+
elevated_user = var.winrm_username
|
|
352
|
+
elevated_password = var.winrm_password
|
|
353
|
+
inline = [
|
|
354
|
+
"$ErrorActionPreference = 'Stop'",
|
|
355
|
+
"$ProgressPreference = 'SilentlyContinue'",
|
|
356
|
+
"$os = Get-CimInstance Win32_OperatingSystem",
|
|
357
|
+
"Write-Host \"Windows install complete: $($os.Caption) build $($os.BuildNumber) ($env:PROCESSOR_ARCHITECTURE)\"",
|
|
358
|
+
"Write-Host 'Waiting 30s for post-OOBE first-logon scripts to settle...'",
|
|
359
|
+
"Start-Sleep -Seconds 30",
|
|
360
|
+
]
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
# ===== VirtIO guest tools (full driver suite + qemu guest agent) =====
|
|
364
|
+
# The MSI lives on the virtio-win CD attached by qemu-with-tpm.sh (the
|
|
365
|
+
# last usb-storage device). WinPE got the boot-critical drivers from the
|
|
366
|
+
# unattend CD; this registers everything else in the installed OS.
|
|
367
|
+
provisioner "powershell" {
|
|
368
|
+
elevated_user = var.winrm_username
|
|
369
|
+
elevated_password = var.winrm_password
|
|
370
|
+
inline = [
|
|
371
|
+
"$ErrorActionPreference = 'Stop'",
|
|
372
|
+
# The virtio-win CD may not be mounted yet right after first logon;
|
|
373
|
+
# retry for up to ~2 min before giving up.
|
|
374
|
+
"$msi = $null",
|
|
375
|
+
"for ($try = 0; $try -lt 8 -and -not $msi; $try++) {",
|
|
376
|
+
" $cdroms = Get-WmiObject Win32_CDROMDrive | Select-Object -ExpandProperty Drive",
|
|
377
|
+
" foreach ($drive in $cdroms) {",
|
|
378
|
+
" $msi = Get-ChildItem $drive -Filter 'virtio-win-guest-tools*.msi' -ErrorAction SilentlyContinue | Select-Object -First 1",
|
|
379
|
+
" if ($msi) { break }",
|
|
380
|
+
" }",
|
|
381
|
+
" if (-not $msi) { Start-Sleep -Seconds 15 }",
|
|
382
|
+
"}",
|
|
383
|
+
"if (-not $msi) {",
|
|
384
|
+
" Write-Warning 'virtio-win-guest-tools MSI not found on any CD-ROM; skipping guest tools'",
|
|
385
|
+
"} else {",
|
|
386
|
+
" Write-Host \"Installing $($msi.Name)\"",
|
|
387
|
+
" Start-Process msiexec.exe -ArgumentList \"/i `\"$($msi.FullName)`\" /qn /norestart\" -Wait",
|
|
388
|
+
" Start-Sleep -Seconds 5",
|
|
389
|
+
"}",
|
|
390
|
+
"$ga = Get-Service -Name 'QEMU-GA' -ErrorAction SilentlyContinue",
|
|
391
|
+
"if ($ga) { Set-Service 'QEMU-GA' -StartupType Automatic; Start-Service 'QEMU-GA' -ErrorAction SilentlyContinue }",
|
|
392
|
+
]
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
# ===== Chocolatey + .NET strong crypto =====
|
|
396
|
+
provisioner "powershell" {
|
|
397
|
+
elevated_user = var.winrm_username
|
|
398
|
+
elevated_password = var.winrm_password
|
|
399
|
+
inline = [
|
|
400
|
+
"$ErrorActionPreference = 'Stop'",
|
|
401
|
+
"[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072",
|
|
402
|
+
"iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))",
|
|
403
|
+
"if ($LASTEXITCODE -ne 0) { throw \"chocolatey install failed: $LASTEXITCODE\" }",
|
|
404
|
+
"$chocoInstall = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall','Machine')",
|
|
405
|
+
"if (-not $chocoInstall) { $chocoInstall = 'C:\\ProgramData\\chocolatey' }",
|
|
406
|
+
"$chocoBin = Join-Path $chocoInstall 'bin'",
|
|
407
|
+
"# Persist the Chocolatey bin dir into the machine PATH ourselves: the",
|
|
408
|
+
"# bootstrapper's compiled Install-ChocolateyPath cmdlet asserts UAC",
|
|
409
|
+
"# before writing and can fail to persist the registry value in the",
|
|
410
|
+
"# elevated WinRM context (observed as 'choco' not recognized in the",
|
|
411
|
+
"# toolchain phase after the reboot, even with PATH re-read).",
|
|
412
|
+
"$machinePath = [System.Environment]::GetEnvironmentVariable('Path','Machine').TrimEnd(';')",
|
|
413
|
+
"if (-not (($machinePath -split ';') -contains $chocoBin)) {",
|
|
414
|
+
" [System.Environment]::SetEnvironmentVariable('Path', $machinePath + ';' + $chocoBin, 'Machine')",
|
|
415
|
+
"}",
|
|
416
|
+
"# Run choco by its full path too — this session's PATH is not",
|
|
417
|
+
"# guaranteed to contain the bin dir above.",
|
|
418
|
+
"$choco = Join-Path $chocoBin 'choco.exe'",
|
|
419
|
+
"if (-not (Test-Path $choco)) { throw \"choco.exe not found at $choco\" }",
|
|
420
|
+
"& $choco feature enable -n allowGlobalConfirmation",
|
|
421
|
+
"if ($LASTEXITCODE -ne 0) { throw \"choco feature enable failed: $LASTEXITCODE\" }",
|
|
422
|
+
"# Legacy .NET strong-crypto: without it, older TLS stacks fail against modern hosts",
|
|
423
|
+
"reg add 'HKLM\\SOFTWARE\\Microsoft\\.NETFramework\\v4.0.30319' /v SchUseStrongCrypto /t REG_DWORD /d 1 /f",
|
|
424
|
+
"reg add 'HKLM\\SOFTWARE\\Wow6432Node\\Microsoft\\.NETFramework\\v4.0.30319' /v SchUseStrongCrypto /t REG_DWORD /d 1 /f",
|
|
425
|
+
]
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
# ===== Reboot: clear the tools-install pending reboot =====
|
|
429
|
+
# The virtio-win guest-tools MSI install leaves a pending reboot, which
|
|
430
|
+
# makes `choco install` return 3010 and makes the .NET Framework 4.8
|
|
431
|
+
# Developer Pack installer fail with exit code 1 (it refuses to run
|
|
432
|
+
# while a reboot is pending). Restart once and wait for WinRM, so the
|
|
433
|
+
# toolchain + VS phases run on a cleanly booted machine.
|
|
434
|
+
provisioner "windows-restart" {
|
|
435
|
+
restart_timeout = "30m"
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
# ===== Toolchain via Chocolatey (versions from the vars file) =====
|
|
439
|
+
provisioner "powershell" {
|
|
440
|
+
elevated_user = var.winrm_username
|
|
441
|
+
elevated_password = var.winrm_password
|
|
442
|
+
inline = [<<-END
|
|
443
|
+
$ErrorActionPreference = 'Stop'
|
|
444
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
445
|
+
# Choco is called by its full path, never via PATH: the bootstrapper
|
|
446
|
+
# can fail to persist the machine PATH (see the Chocolatey
|
|
447
|
+
# provisioner), and after the guest-tools reboot a fresh WinRM
|
|
448
|
+
# process can inherit a stale PATH — both observed as 'choco' not
|
|
449
|
+
# recognized. The PATH is still re-read from the registry below for
|
|
450
|
+
# the tools this phase installs.
|
|
451
|
+
$chocoInstall = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall','Machine')
|
|
452
|
+
if (-not $chocoInstall) { $chocoInstall = 'C:\ProgramData\chocolatey' }
|
|
453
|
+
$choco = Join-Path (Join-Path $chocoInstall 'bin') 'choco.exe'
|
|
454
|
+
if (-not (Test-Path $choco)) { throw "choco.exe not found at $choco (Chocolatey install failed?)" }
|
|
455
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')
|
|
456
|
+
& $choco install nodejs --version=${var.nodejs_version} -y
|
|
457
|
+
if ($LASTEXITCODE -ne 0) { throw "choco nodejs failed: $LASTEXITCODE" }
|
|
458
|
+
& $choco install gh --version=${var.github_cli_version} -y
|
|
459
|
+
if ($LASTEXITCODE -ne 0) { throw "choco gh failed: $LASTEXITCODE" }
|
|
460
|
+
& $choco install ripgrep --version=${var.ripgrep_version} -y
|
|
461
|
+
if ($LASTEXITCODE -ne 0) { throw "choco ripgrep failed: $LASTEXITCODE" }
|
|
462
|
+
& $choco install git --version=${var.git_version} -y
|
|
463
|
+
if ($LASTEXITCODE -ne 0) { throw "choco git failed: $LASTEXITCODE" }
|
|
464
|
+
& $choco install jq --version=${var.jq_version} -y
|
|
465
|
+
if ($LASTEXITCODE -ne 0) { throw "choco jq failed: $LASTEXITCODE" }
|
|
466
|
+
& $choco install python --version=${var.python_version} -y
|
|
467
|
+
if ($LASTEXITCODE -ne 0) { throw "choco python failed: $LASTEXITCODE" }
|
|
468
|
+
& $choco install firefox curl docker-cli docker-compose -y
|
|
469
|
+
if ($LASTEXITCODE -ne 0) { throw "choco browsers/docker failed: $LASTEXITCODE" }
|
|
470
|
+
|
|
471
|
+
# C/C++ + cross-language toolchains (brought over from AdGuard's
|
|
472
|
+
# build-agent-images windows2022-vs2022 / windows2022-go images).
|
|
473
|
+
# VS2022 Build Tools (with its .NET/VC++ workloads) and Rust are
|
|
474
|
+
# installed by their own provisioners below.
|
|
475
|
+
& $choco install golang --version=${var.go_version} -y
|
|
476
|
+
if ($LASTEXITCODE -ne 0) { throw "choco golang failed: $LASTEXITCODE" }
|
|
477
|
+
& $choco install mingw --version=${var.mingw_version} -y
|
|
478
|
+
if ($LASTEXITCODE -ne 0) { throw "choco mingw failed: $LASTEXITCODE" }
|
|
479
|
+
& $choco install make --version=${var.make_version} -y
|
|
480
|
+
if ($LASTEXITCODE -ne 0) { throw "choco make failed: $LASTEXITCODE" }
|
|
481
|
+
& $choco install vim --version=${var.vim_version} -y
|
|
482
|
+
if ($LASTEXITCODE -ne 0) { throw "choco vim failed: $LASTEXITCODE" }
|
|
483
|
+
& $choco install nuget.commandline --version=${var.nuget_version} -y
|
|
484
|
+
if ($LASTEXITCODE -ne 0) { throw "choco nuget.commandline failed: $LASTEXITCODE" }
|
|
485
|
+
& $choco install protoc --version=${var.protoc_version} -y
|
|
486
|
+
if ($LASTEXITCODE -ne 0) { throw "choco protoc failed: $LASTEXITCODE" }
|
|
487
|
+
& $choco install nasm --version=${var.nasm_version} -y
|
|
488
|
+
if ($LASTEXITCODE -ne 0) { throw "choco nasm failed: $LASTEXITCODE" }
|
|
489
|
+
& $choco install llvm --version=${var.llvm_version} -y
|
|
490
|
+
if ($LASTEXITCODE -ne 0) { throw "choco llvm failed: $LASTEXITCODE" }
|
|
491
|
+
& $choco install wixtoolset --version=${var.wixtoolset_version} -y
|
|
492
|
+
if ($LASTEXITCODE -ne 0) { throw "choco wixtoolset failed: $LASTEXITCODE" }
|
|
493
|
+
|
|
494
|
+
# ===== Google Chrome (CfT snapshot, hash-pinned) =====
|
|
495
|
+
# choco's googlechrome package downloads the live dl.google.com MSI,
|
|
496
|
+
# whose binary rotates with every Chrome release — the pinned hash
|
|
497
|
+
# breaks between releases (observed with 152.0.7977.54). Instead we
|
|
498
|
+
# fetch the versioned Chrome for Testing archive, verify the SHA256,
|
|
499
|
+
# and extract it into the standard install location.
|
|
500
|
+
$chromeUrl = "https://storage.googleapis.com/chrome-for-testing-public/${var.chrome_version}/win64/chrome-win64.zip"
|
|
501
|
+
$chromeZip = "$env:TEMP\chrome-win64.zip"
|
|
502
|
+
$chromeAppDir = 'C:\Program Files\Google\Chrome\Application'
|
|
503
|
+
$chromeExe = Join-Path $chromeAppDir 'chrome.exe'
|
|
504
|
+
# 202 MB over usermode networking can drop mid-transfer; retry.
|
|
505
|
+
$downloaded = $false
|
|
506
|
+
for ($try = 1; $try -le 3 -and -not $downloaded; $try++) {
|
|
507
|
+
try {
|
|
508
|
+
Invoke-WebRequest -UseBasicParsing -Uri $chromeUrl -OutFile $chromeZip -ErrorAction Stop
|
|
509
|
+
$downloaded = $true
|
|
510
|
+
} catch {
|
|
511
|
+
Write-Host "chrome download attempt $try failed: $($_.Exception.Message)"
|
|
512
|
+
if ($try -lt 3) { Start-Sleep -Seconds 10 }
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (-not $downloaded) { throw "chrome download failed after 3 attempts" }
|
|
516
|
+
$actual = (Get-FileHash -Path $chromeZip -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
517
|
+
if ($actual -ne '${var.chrome_sha256}') {
|
|
518
|
+
throw "chrome checksum mismatch: expected ${var.chrome_sha256}, got $actual"
|
|
519
|
+
}
|
|
520
|
+
New-Item -ItemType Directory -Force -Path "$env:TEMP\chrome-x" | Out-Null
|
|
521
|
+
Expand-Archive -Path $chromeZip -DestinationPath "$env:TEMP\chrome-x" -Force
|
|
522
|
+
New-Item -ItemType Directory -Force -Path $chromeAppDir | Out-Null
|
|
523
|
+
Copy-Item -Path "$env:TEMP\chrome-x\chrome-win64\*" -Destination $chromeAppDir -Recurse -Force
|
|
524
|
+
Remove-Item -Path "$env:TEMP\chrome-x", $chromeZip -Recurse -Force
|
|
525
|
+
# Register App Paths so `start chrome` / shell launchers resolve it
|
|
526
|
+
$appPaths = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe'
|
|
527
|
+
New-Item -Path $appPaths -Force | Out-Null
|
|
528
|
+
New-ItemProperty -Path $appPaths -Name '(Default)' -Value $chromeExe -PropertyType String -Force | Out-Null
|
|
529
|
+
if (-not (Test-Path $chromeExe)) { throw "chrome.exe not found after extraction" }
|
|
530
|
+
$toolPaths = @(
|
|
531
|
+
'C:\Program Files\nodejs',
|
|
532
|
+
'C:\ProgramData\chocolatey\bin',
|
|
533
|
+
'C:\Program Files\Git\cmd',
|
|
534
|
+
'C:\Program Files\Git\bin',
|
|
535
|
+
'C:\Program Files\Google\Chrome\Application',
|
|
536
|
+
'C:\Program Files\Go\bin',
|
|
537
|
+
'C:\Program Files\LLVM\bin',
|
|
538
|
+
'C:\Program Files\NASM',
|
|
539
|
+
# WiX 3.14.x: newer package versions install the MSI under
|
|
540
|
+
# 'Program Files\WiX Toolset v3.14\bin' (older ones put the
|
|
541
|
+
# binaries in the choco lib) — add both, the guards skip absent.
|
|
542
|
+
'C:\Program Files (x86)\WiX Toolset v3.14\bin',
|
|
543
|
+
'C:\ProgramData\chocolatey\lib\wixtoolset\tools',
|
|
544
|
+
'C:\mingw64\bin',
|
|
545
|
+
"$env:APPDATA\npm"
|
|
546
|
+
)
|
|
547
|
+
$machinePath = [System.Environment]::GetEnvironmentVariable('Path','Machine').TrimEnd(';')
|
|
548
|
+
foreach ($toolPath in $toolPaths) {
|
|
549
|
+
if ((Test-Path $toolPath) -and -not (($machinePath -split ';') -contains $toolPath)) {
|
|
550
|
+
$machinePath = $machinePath + ';' + $toolPath
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
[System.Environment]::SetEnvironmentVariable('Path', $machinePath, 'Machine')
|
|
554
|
+
$env:Path = $machinePath + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')
|
|
555
|
+
node --version
|
|
556
|
+
git --version
|
|
557
|
+
gh --version
|
|
558
|
+
rg --version
|
|
559
|
+
jq --version
|
|
560
|
+
python --version
|
|
561
|
+
go version
|
|
562
|
+
END
|
|
563
|
+
]
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
# ===== Visual Studio 2022 Build Tools (MSVC + .NET SDKs + CMake + Win11 SDK) =====
|
|
567
|
+
# Brought over from AdGuard's build-agent-images windows2022-base-vs2022
|
|
568
|
+
# image: the choco package installs the VS bootstrapper, then setup.exe
|
|
569
|
+
# adds the .NET 4.8 + .NET Core SDKs, the VC++ workload (x86/x64/ARM/
|
|
570
|
+
# ARM64 — ARM64 matters for this ARM64 guest) and the Windows 11 SDK.
|
|
571
|
+
# On ARM64 the bootstrapper runs as an x86 binary under emulation, which
|
|
572
|
+
# VS2022 supports.
|
|
573
|
+
provisioner "powershell" {
|
|
574
|
+
elevated_user = var.winrm_username
|
|
575
|
+
elevated_password = var.winrm_password
|
|
576
|
+
timeout = "90m"
|
|
577
|
+
inline = [<<-END
|
|
578
|
+
$ErrorActionPreference = 'Stop'
|
|
579
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
580
|
+
# Choco is called by its full path, never via PATH (see the
|
|
581
|
+
# toolchain provisioner). The PATH is still re-read for the tools
|
|
582
|
+
# this phase installs.
|
|
583
|
+
$chocoInstall = [System.Environment]::GetEnvironmentVariable('ChocolateyInstall','Machine')
|
|
584
|
+
if (-not $chocoInstall) { $chocoInstall = 'C:\ProgramData\chocolatey' }
|
|
585
|
+
$choco = Join-Path (Join-Path $chocoInstall 'bin') 'choco.exe'
|
|
586
|
+
if (-not (Test-Path $choco)) { throw "choco.exe not found at $choco (Chocolatey install failed?)" }
|
|
587
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')
|
|
588
|
+
# Standalone .NET Framework 4.8 Developer Pack (latest), as in the
|
|
589
|
+
# AdGuard image — MSBuild/.NET 4.8 targets need it. 3010 = success,
|
|
590
|
+
# reboot required, which choco also returns for the KB dep.
|
|
591
|
+
& $choco install netfx-4.8-devpack -y --norestart
|
|
592
|
+
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 3010) { throw "choco netfx-4.8-devpack failed: $LASTEXITCODE" }
|
|
593
|
+
& $choco install visualstudio2022buildtools --version=${var.vs_buildtools_version} -y --norestart --wait --nocache --noUpdateInstaller
|
|
594
|
+
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 3010) { throw "choco visualstudio2022buildtools failed: $LASTEXITCODE" }
|
|
595
|
+
$setup = 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe'
|
|
596
|
+
if (-not (Test-Path $setup)) { throw "VS installer not found: $setup" }
|
|
597
|
+
# .NET 4.8 SDK + .NET Core SDK workloads
|
|
598
|
+
& $setup modify --quiet --norestart `
|
|
599
|
+
--installPath 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools' `
|
|
600
|
+
--add Microsoft.Net.Component.4.8.SDK `
|
|
601
|
+
--add Microsoft.NetCore.Component.SDK `
|
|
602
|
+
| Out-Host
|
|
603
|
+
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 3010) { throw "vs .NET workloads failed: $LASTEXITCODE" }
|
|
604
|
+
# VC++ workload (x86/x64/ARM/ARM64) + CMake project support
|
|
605
|
+
& $setup modify --quiet --norestart `
|
|
606
|
+
--installPath 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools' `
|
|
607
|
+
--add Microsoft.VisualStudio.Workload.VCTools `
|
|
608
|
+
--add Microsoft.VisualStudio.Component.VC.CMake.Project `
|
|
609
|
+
--add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
|
|
610
|
+
--add Microsoft.VisualStudio.Component.VC.Tools.ARM `
|
|
611
|
+
--add Microsoft.VisualStudio.Component.VC.Tools.ARM64 `
|
|
612
|
+
| Out-Host
|
|
613
|
+
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 3010) { throw "vs VC++ workloads failed: $LASTEXITCODE" }
|
|
614
|
+
# Windows 11 SDK (the components the Win11 toolchain expects)
|
|
615
|
+
& $setup modify --quiet --norestart `
|
|
616
|
+
--installPath 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools' `
|
|
617
|
+
--add Microsoft.VisualStudio.Component.Windows11SDK.22621 `
|
|
618
|
+
| Out-Host
|
|
619
|
+
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 3010) { throw "vs Windows11SDK failed: $LASTEXITCODE" }
|
|
620
|
+
$vcPath = 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build'
|
|
621
|
+
$machinePath = [System.Environment]::GetEnvironmentVariable('Path','Machine').TrimEnd(';')
|
|
622
|
+
if (-not (($machinePath -split ';') -contains $vcPath)) {
|
|
623
|
+
[System.Environment]::SetEnvironmentVariable('Path', $machinePath + ';' + $vcPath, 'Machine')
|
|
624
|
+
}
|
|
625
|
+
Write-Host 'Visual Studio 2022 Build Tools + .NET SDKs installed'
|
|
626
|
+
END
|
|
627
|
+
]
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
# ===== Rust via rustup (ARM64 host toolchain + MSVC cross targets) =====
|
|
631
|
+
# Brought over from AdGuard's build-agent-images windows2022-vs2022
|
|
632
|
+
# image, adapted for ARM64: win.rustup.rs/aarch64 ships the aarch64
|
|
633
|
+
# rustup-init (win.rustup.rs/x86_64 is the x86_64 host only). The
|
|
634
|
+
# default-toolchain is resolved by rustup (e.g. '1.95' -> 1.95.x), and
|
|
635
|
+
# the MSVC targets cover x86_64/i686/aarch64 linking.
|
|
636
|
+
provisioner "powershell" {
|
|
637
|
+
elevated_user = var.winrm_username
|
|
638
|
+
elevated_password = var.winrm_password
|
|
639
|
+
timeout = "30m"
|
|
640
|
+
inline = [<<-END
|
|
641
|
+
$ErrorActionPreference = 'Stop'
|
|
642
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
643
|
+
$rustupInit = "$env:TEMP\rustup-init.exe"
|
|
644
|
+
$downloaded = $false
|
|
645
|
+
for ($try = 1; $try -le 3 -and -not $downloaded; $try++) {
|
|
646
|
+
try {
|
|
647
|
+
Invoke-WebRequest -UseBasicParsing -Uri 'https://win.rustup.rs/aarch64' -OutFile $rustupInit -ErrorAction Stop
|
|
648
|
+
$downloaded = $true
|
|
649
|
+
} catch {
|
|
650
|
+
Write-Host "rustup-init download attempt $try failed: $($_.Exception.Message)"
|
|
651
|
+
if ($try -lt 3) { Start-Sleep -Seconds 10 }
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (-not $downloaded) { throw "rustup-init download failed after 3 attempts" }
|
|
655
|
+
& $rustupInit -y --default-toolchain ${var.rust_version}
|
|
656
|
+
if ($LASTEXITCODE -ne 0) { throw "rustup-init failed: $LASTEXITCODE" }
|
|
657
|
+
Remove-Item $rustupInit -Force
|
|
658
|
+
$cargoBin = "$env:USERPROFILE\.cargo\bin"
|
|
659
|
+
$machinePath = [System.Environment]::GetEnvironmentVariable('Path','Machine').TrimEnd(';')
|
|
660
|
+
if (-not (($machinePath -split ';') -contains $cargoBin)) {
|
|
661
|
+
[System.Environment]::SetEnvironmentVariable('Path', $machinePath + ';' + $cargoBin, 'Machine')
|
|
662
|
+
}
|
|
663
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')
|
|
664
|
+
rustup target add x86_64-pc-windows-msvc i686-pc-windows-msvc aarch64-pc-windows-msvc
|
|
665
|
+
if ($LASTEXITCODE -ne 0) { throw "rustup target add failed: $LASTEXITCODE" }
|
|
666
|
+
Write-Host "Rust ${var.rust_version} installed with MSVC targets"
|
|
667
|
+
END
|
|
668
|
+
]
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
# ===== Visual Studio Code (native arm64, direct download) =====
|
|
672
|
+
provisioner "powershell" {
|
|
673
|
+
elevated_user = var.winrm_username
|
|
674
|
+
elevated_password = var.winrm_password
|
|
675
|
+
inline = [<<-END
|
|
676
|
+
$ErrorActionPreference = 'Stop'
|
|
677
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
678
|
+
$installer = "$env:TEMP\vscode-arm64-user-setup.exe"
|
|
679
|
+
$downloaded = $false
|
|
680
|
+
for ($try = 1; $try -le 3 -and -not $downloaded; $try++) {
|
|
681
|
+
try {
|
|
682
|
+
Invoke-WebRequest -UseBasicParsing -Uri 'https://update.code.visualstudio.com/latest/win32-arm64-user/stable' -OutFile $installer -ErrorAction Stop
|
|
683
|
+
$downloaded = $true
|
|
684
|
+
} catch {
|
|
685
|
+
Write-Host "vscode download attempt $try failed: $($_.Exception.Message)"
|
|
686
|
+
if ($try -lt 3) { Start-Sleep -Seconds 10 }
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
if (-not $downloaded) { throw "vscode download failed after 3 attempts" }
|
|
690
|
+
Start-Process $installer -ArgumentList '/VERYSILENT /NORESTART /MERGETASKS=!runcode' -Wait
|
|
691
|
+
Remove-Item $installer -Force
|
|
692
|
+
$codeBin = "$env:LOCALAPPDATA\Programs\Microsoft VS Code\bin"
|
|
693
|
+
if (Test-Path $codeBin) {
|
|
694
|
+
$machinePath = [System.Environment]::GetEnvironmentVariable('Path','Machine').TrimEnd(';')
|
|
695
|
+
if (-not (($machinePath -split ';') -contains $codeBin)) {
|
|
696
|
+
[System.Environment]::SetEnvironmentVariable('Path', $machinePath + ';' + $codeBin, 'Machine')
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
END
|
|
700
|
+
]
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
# ===== OpenCode, OpenCodeReview, OpenChamber web UI =====
|
|
704
|
+
provisioner "powershell" {
|
|
705
|
+
elevated_user = var.winrm_username
|
|
706
|
+
elevated_password = var.winrm_password
|
|
707
|
+
inline = [<<-END
|
|
708
|
+
# EAP=Continue, not Stop: this script runs many native commands
|
|
709
|
+
# (schtasks, npm, openchamber) whose stderr lines become terminating
|
|
710
|
+
# errors under Stop in Windows PowerShell 5.1 (the 2>$null / 2>&1
|
|
711
|
+
# native-stderr bug). All failure handling is explicit below.
|
|
712
|
+
$ErrorActionPreference = 'Continue'
|
|
713
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
714
|
+
# The image ships with Windows' default Restricted execution policy —
|
|
715
|
+
# every build-time powershell invocation passes -ExecutionPolicy
|
|
716
|
+
# Bypass, so nothing is persisted. But opencode/ocr are npm shims
|
|
717
|
+
# (opencode.ps1 in %APPDATA%\npm): a Restricted shell refuses to run
|
|
718
|
+
# them ("running scripts is disabled on this system"). Bake in
|
|
719
|
+
# RemoteSigned machine-wide (this provisioner runs elevated, so the
|
|
720
|
+
# LocalMachine scope persists in the image) and keep the runners'
|
|
721
|
+
# runtime set as a fallback for images built before this change.
|
|
722
|
+
# The build passes -ExecutionPolicy Bypass at Process scope, so
|
|
723
|
+
# setting only LocalMachine triggers Set-ExecutionPolicy's "overridden
|
|
724
|
+
# by a more specific scope" notice, which Windows PowerShell 5.1 under
|
|
725
|
+
# WinRM surfaces as a terminating error that aborts the build even
|
|
726
|
+
# though the machine policy was updated fine (observed). Set the
|
|
727
|
+
# Process scope first (no override, no notice), then the machine.
|
|
728
|
+
try {
|
|
729
|
+
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force -ErrorAction SilentlyContinue
|
|
730
|
+
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force -ErrorAction SilentlyContinue
|
|
731
|
+
} catch {
|
|
732
|
+
Write-Warning "Set-ExecutionPolicy failed: $($_.Exception.Message)"
|
|
733
|
+
}
|
|
734
|
+
Write-Host "PowerShell execution policy: $(Get-ExecutionPolicy -Scope LocalMachine)"
|
|
735
|
+
$machinePath = [System.Environment]::GetEnvironmentVariable('Path','Machine').TrimEnd(';')
|
|
736
|
+
$env:Path = $machinePath + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')
|
|
737
|
+
# The openchamber scheduled task launches at logon and locks its
|
|
738
|
+
# node_modules — kill it before reinstalling, and retry npm installs
|
|
739
|
+
# (the guest's usermode networking drops large transfers).
|
|
740
|
+
try { schtasks /End /TN dev.openchamber.web 2>&1 | Out-Null } catch {}
|
|
741
|
+
Stop-Process -Name node -Force -ErrorAction SilentlyContinue
|
|
742
|
+
function Invoke-NpmRetry([string]$argsLine) {
|
|
743
|
+
for ($try = 1; $try -le 3; $try++) {
|
|
744
|
+
cmd /c "npm $argsLine" 2>&1 | Out-Null
|
|
745
|
+
if ($LASTEXITCODE -eq 0) { return }
|
|
746
|
+
Write-Host "npm $argsLine attempt $try failed ($LASTEXITCODE); retrying"
|
|
747
|
+
Start-Sleep -Seconds 10
|
|
748
|
+
}
|
|
749
|
+
throw "npm $argsLine failed after 3 attempts"
|
|
750
|
+
}
|
|
751
|
+
Invoke-NpmRetry "install -g opencode-ai"
|
|
752
|
+
New-Item -ItemType Directory -Force -Path C:\npm-global | Out-Null
|
|
753
|
+
Invoke-NpmRetry "install -g --prefix C:\npm-global --ignore-scripts @alibaba-group/open-code-review@${var.open_code_review_version}"
|
|
754
|
+
foreach ($toolPath in @('C:\npm-global')) {
|
|
755
|
+
if ((Test-Path $toolPath) -and -not (($machinePath -split ';') -contains $toolPath)) {
|
|
756
|
+
$machinePath = $machinePath + ';' + $toolPath
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
[System.Environment]::SetEnvironmentVariable('Path', $machinePath, 'Machine')
|
|
760
|
+
Invoke-NpmRetry "install -g @openchamber/web"
|
|
761
|
+
$env:Path = [System.Environment]::GetEnvironmentVariable('Path','Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path','User')
|
|
762
|
+
opencode --version
|
|
763
|
+
ocr --version
|
|
764
|
+
# Web UI as a native service (Windows: scheduled task). Snapshot the
|
|
765
|
+
# absolute opencode path like the macOS template does.
|
|
766
|
+
$opencodeBin = (Get-Command opencode -ErrorAction Stop).Source
|
|
767
|
+
$env:OPENCODE_BINARY = $opencodeBin
|
|
768
|
+
# openchamber's own `startup enable` writes startup.env but then fails
|
|
769
|
+
# to register the task — its schtasks /TR command exceeds the 261-char
|
|
770
|
+
# limit and the failure aborts the whole provisioner. So: let it write
|
|
771
|
+
# the env file, ignore the failure, and register a compact task that
|
|
772
|
+
# runs a small wrapper script instead of a giant inline command.
|
|
773
|
+
try {
|
|
774
|
+
openchamber startup enable --port ${var.openchamber_port} --lan --ui-password "${var.openchamber_ui_password}" 2>$null | Out-Null
|
|
775
|
+
} catch {}
|
|
776
|
+
$LASTEXITCODE = 0
|
|
777
|
+
$envFile = 'C:\Users\Administrator\.config\openchamber\startup.env'
|
|
778
|
+
if (-not (Test-Path $envFile)) {
|
|
779
|
+
New-Item -ItemType Directory -Force -Path (Split-Path $envFile) | Out-Null
|
|
780
|
+
@"
|
|
781
|
+
OPENCHAMBER_UI_PASSWORD='${var.openchamber_ui_password}'
|
|
782
|
+
OPENCODE_BINARY='$opencodeBin'
|
|
783
|
+
"@ | Set-Content -Path $envFile -Encoding UTF8
|
|
784
|
+
}
|
|
785
|
+
New-Item -ItemType Directory -Force -Path C:\tools | Out-Null
|
|
786
|
+
$wrapper = @'
|
|
787
|
+
$envFile = 'C:\Users\Administrator\.config\openchamber\startup.env'
|
|
788
|
+
if (Test-Path $envFile) {
|
|
789
|
+
Get-Content $envFile | ForEach-Object {
|
|
790
|
+
if ($_ -match '^([^=]+)=(.*)$') {
|
|
791
|
+
$v = $matches[2]
|
|
792
|
+
if ($v.StartsWith("'") -and $v.EndsWith("'")) {
|
|
793
|
+
$v = $v.Substring(1, $v.Length - 2).Replace("'\''", "'")
|
|
794
|
+
}
|
|
795
|
+
[Environment]::SetEnvironmentVariable($matches[1], $v, 'Process')
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
& 'C:\Program Files\nodejs\node.exe' 'C:\Users\Administrator\AppData\Roaming\npm\node_modules\@openchamber\web\bin\cli.js' serve --foreground --port ${var.openchamber_port} --host 0.0.0.0
|
|
800
|
+
'@
|
|
801
|
+
Set-Content -Path C:\tools\openchamber-startup.ps1 -Value $wrapper -Encoding UTF8
|
|
802
|
+
# Register the logon task. schtasks.exe from the elevated WinRM
|
|
803
|
+
# session can be flaky (silent /Create failures, /Run "file not
|
|
804
|
+
# found"); fall back to the PowerShell-native Register-ScheduledTask
|
|
805
|
+
# (same mechanism the build-agent-images repo uses). Never fail the
|
|
806
|
+
# build here — worst case the web UI starts at first login.
|
|
807
|
+
$taskRegistered = $false
|
|
808
|
+
try {
|
|
809
|
+
schtasks /Create /TN dev.openchamber.web /SC ONLOGON /RL LIMITED /F /TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\tools\openchamber-startup.ps1" 2>&1 | Out-Null
|
|
810
|
+
if ($LASTEXITCODE -eq 0) {
|
|
811
|
+
schtasks /Run /TN dev.openchamber.web 2>&1 | Out-Null
|
|
812
|
+
if ($LASTEXITCODE -eq 0) { $taskRegistered = $true }
|
|
813
|
+
}
|
|
814
|
+
$LASTEXITCODE = 0
|
|
815
|
+
} catch {
|
|
816
|
+
Write-Warning "schtasks registration failed ($($_.Exception.Message)); trying Register-ScheduledTask"
|
|
817
|
+
$LASTEXITCODE = 0
|
|
818
|
+
}
|
|
819
|
+
if (-not $taskRegistered) {
|
|
820
|
+
try {
|
|
821
|
+
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument '-NoProfile -ExecutionPolicy Bypass -File C:\tools\openchamber-startup.ps1'
|
|
822
|
+
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
823
|
+
Register-ScheduledTask -TaskName 'dev.openchamber.web' -Action $action -Trigger $trigger -Force | Out-Null
|
|
824
|
+
Start-ScheduledTask -TaskName 'dev.openchamber.web'
|
|
825
|
+
$taskRegistered = $true
|
|
826
|
+
} catch {
|
|
827
|
+
Write-Warning "Register-ScheduledTask also failed: $($_.Exception.Message); the web UI will start at first login instead"
|
|
828
|
+
$LASTEXITCODE = 0
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if ($taskRegistered) { Write-Host "OpenChamber task registered: $taskRegistered" }
|
|
832
|
+
# Windows' firewall "TCP/UDP Query User" rules for node.exe default to
|
|
833
|
+
# BLOCK when the allow prompt goes unanswered (headless build) — the
|
|
834
|
+
# web UI then only answers on the guest loopback. Remove them and
|
|
835
|
+
# allow port 4000 explicitly so the host can reach the UI.
|
|
836
|
+
Get-NetFirewallRule -Direction Inbound -Action Block -ErrorAction SilentlyContinue |
|
|
837
|
+
Where-Object { $_.DisplayName -like '*Query User*node.exe*' } |
|
|
838
|
+
Remove-NetFirewallRule -ErrorAction SilentlyContinue
|
|
839
|
+
New-NetFirewallRule -DisplayName 'OpenChamber' -Direction Inbound -Action Allow -Protocol TCP -LocalPort ${var.openchamber_port} -Profile Any -ErrorAction SilentlyContinue | Out-Null
|
|
840
|
+
$LASTEXITCODE = 0
|
|
841
|
+
END
|
|
842
|
+
]
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
# ===== OpenSSH Server + RDP =====
|
|
846
|
+
# sshd gives the host a management channel (agent-dev-env run windows-qemu
|
|
847
|
+
# forwards guest 22 to host 2222); RDP gives a desktop session. Password
|
|
848
|
+
# auth is enabled for the Administrator account so the fixed sandbox
|
|
849
|
+
# credentials from the vars file work.
|
|
850
|
+
provisioner "powershell" {
|
|
851
|
+
elevated_user = var.winrm_username
|
|
852
|
+
elevated_password = var.winrm_password
|
|
853
|
+
inline = [<<-END
|
|
854
|
+
$ErrorActionPreference = 'Stop'
|
|
855
|
+
# Note: Add-WindowsCapability is a cmdlet — it does NOT set
|
|
856
|
+
# $LASTEXITCODE (a stale $null made the old `if ($LASTEXITCODE -ne 0)`
|
|
857
|
+
# check false-fire and abort the build even on success). Failures
|
|
858
|
+
# surface as terminating errors via -ErrorAction Stop instead.
|
|
859
|
+
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 -ErrorAction Stop
|
|
860
|
+
Set-Service -Name sshd -StartupType Automatic
|
|
861
|
+
$cfg = 'C:\ProgramData\ssh\sshd_config'
|
|
862
|
+
if (Test-Path $cfg) {
|
|
863
|
+
(Get-Content $cfg) -replace '#PasswordAuthentication yes','PasswordAuthentication yes' `
|
|
864
|
+
-replace 'Match Group administrators','#Match Group administrators' `
|
|
865
|
+
-replace ' AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys','# AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys' `
|
|
866
|
+
| Set-Content $cfg
|
|
867
|
+
}
|
|
868
|
+
# PowerShell as the default SSH shell
|
|
869
|
+
New-ItemProperty -Path 'HKLM:\SOFTWARE\OpenSSH' -Name DefaultShell -Value 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' -PropertyType String -Force | Out-Null
|
|
870
|
+
New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 | Out-Null
|
|
871
|
+
Start-Service sshd
|
|
872
|
+
# RDP
|
|
873
|
+
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server' -Name fDenyTSConnections -Value 0
|
|
874
|
+
Enable-NetFirewallRule -DisplayGroup 'Remote Desktop'
|
|
875
|
+
END
|
|
876
|
+
]
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
# ===== Bridge tooling: socat + npiperelay (best-effort) =====
|
|
880
|
+
# Used by the SSH-agent bridge on the host side of the sandbox runner
|
|
881
|
+
# (see docs/windows-qemu.md once the runner lands). Not load-bearing for the
|
|
882
|
+
# image itself, so a download failure only warns.
|
|
883
|
+
provisioner "powershell" {
|
|
884
|
+
elevated_user = var.winrm_username
|
|
885
|
+
elevated_password = var.winrm_password
|
|
886
|
+
inline = [<<-END
|
|
887
|
+
$ErrorActionPreference = 'Stop'
|
|
888
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
889
|
+
try {
|
|
890
|
+
New-Item -ItemType Directory -Force -Path C:\tools | Out-Null
|
|
891
|
+
Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/jstarks/npiperelay/releases/download/v0.1.0/npiperelay_windows_amd64.zip' -OutFile "$env:TEMP\npiperelay.zip"
|
|
892
|
+
Expand-Archive "$env:TEMP\npiperelay.zip" -DestinationPath C:\tools -Force
|
|
893
|
+
Remove-Item "$env:TEMP\npiperelay.zip" -Force
|
|
894
|
+
Invoke-WebRequest -UseBasicParsing -Uri 'https://github.com/valorisa/socat-1.8.0.1_for_Windows/raw/main/socat.exe' -OutFile 'C:\tools\socat.exe'
|
|
895
|
+
$machinePath = [System.Environment]::GetEnvironmentVariable('Path','Machine').TrimEnd(';')
|
|
896
|
+
if (-not (($machinePath -split ';') -contains 'C:\tools')) {
|
|
897
|
+
[System.Environment]::SetEnvironmentVariable('Path', $machinePath + ';C:\tools', 'Machine')
|
|
898
|
+
}
|
|
899
|
+
Write-Host 'Bridge tooling installed: C:\tools\npiperelay.exe, C:\tools\socat.exe'
|
|
900
|
+
} catch {
|
|
901
|
+
Write-Warning "Bridge tooling install failed (bridges will be unavailable): $($_.Exception.Message)"
|
|
902
|
+
}
|
|
903
|
+
END
|
|
904
|
+
]
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
# ===== Performance & privacy hardening =====
|
|
908
|
+
provisioner "powershell" {
|
|
909
|
+
elevated_user = var.winrm_username
|
|
910
|
+
elevated_password = var.winrm_password
|
|
911
|
+
inline = [<<-END
|
|
912
|
+
$ErrorActionPreference = 'Stop'
|
|
913
|
+
# --- Disable Defender realtime scanning (speed + flakiness) ---
|
|
914
|
+
Set-MpPreference -DisableRealtimeMonitoring $true
|
|
915
|
+
reg add 'HKLM\SOFTWARE\Policies\Microsoft\Windows Defender' /v DisableAntiSpyware /t REG_DWORD /d 1 /f
|
|
916
|
+
# --- Disable heavyweight / telemetry services ---
|
|
917
|
+
$disableServices = @(
|
|
918
|
+
'WSearch', # Windows Search indexer
|
|
919
|
+
'SysMain', # Superfetch
|
|
920
|
+
'DiagTrack', # Diagnostics Tracking (telemetry)
|
|
921
|
+
'dmwappushservice',
|
|
922
|
+
'MapsBroker', # Downloaded Maps Manager
|
|
923
|
+
'lfsvc', # Geolocation
|
|
924
|
+
'RetailDemo',
|
|
925
|
+
'wisvc' # Windows Insider
|
|
926
|
+
)
|
|
927
|
+
foreach ($svc in $disableServices) {
|
|
928
|
+
$s = Get-Service $svc -ErrorAction SilentlyContinue
|
|
929
|
+
if ($s) {
|
|
930
|
+
Stop-Service $svc -Force -ErrorAction SilentlyContinue
|
|
931
|
+
Set-Service $svc -StartupType Disabled -ErrorAction SilentlyContinue
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
# --- Scheduled telemetry tasks ---
|
|
935
|
+
@(
|
|
936
|
+
'\\Microsoft\\Windows\\Application Experience\\',
|
|
937
|
+
'\\Microsoft\\Windows\\Customer Experience Improvement Program\\'
|
|
938
|
+
) | ForEach-Object {
|
|
939
|
+
Get-ScheduledTask -TaskPath $_ -ErrorAction SilentlyContinue | Disable-ScheduledTask -ErrorAction SilentlyContinue | Out-Null
|
|
940
|
+
}
|
|
941
|
+
# --- Filesystem + boot + power ---
|
|
942
|
+
fsutil behavior set disablelastaccess 1 | Out-Null
|
|
943
|
+
fsutil behavior set disable8dot3 1 | Out-Null
|
|
944
|
+
bcdedit /timeout 0 | Out-Null
|
|
945
|
+
powercfg /h off | Out-Null
|
|
946
|
+
Set-TimeZone -Id 'UTC'
|
|
947
|
+
END
|
|
948
|
+
]
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
# ===== Final verification & cleanup =====
|
|
952
|
+
provisioner "powershell" {
|
|
953
|
+
elevated_user = var.winrm_username
|
|
954
|
+
elevated_password = var.winrm_password
|
|
955
|
+
inline = [<<-END
|
|
956
|
+
$ErrorActionPreference = 'Stop'
|
|
957
|
+
Write-Host '=== Versions ==='
|
|
958
|
+
$os = Get-CimInstance Win32_OperatingSystem
|
|
959
|
+
Write-Host "Windows: $($os.Caption) build $($os.BuildNumber)"
|
|
960
|
+
Write-Host "PowerShell execution policy: $(Get-ExecutionPolicy -Scope LocalMachine)"
|
|
961
|
+
node --version
|
|
962
|
+
npm --version
|
|
963
|
+
python --version
|
|
964
|
+
git --version
|
|
965
|
+
gh --version
|
|
966
|
+
rg --version
|
|
967
|
+
jq --version
|
|
968
|
+
opencode --version
|
|
969
|
+
ocr --version
|
|
970
|
+
openchamber --version
|
|
971
|
+
docker --version
|
|
972
|
+
docker compose version
|
|
973
|
+
# New toolchains: check-and-warn. A missing helper must never fail
|
|
974
|
+
# the build — e.g. llvm-config is not shipped by every LLVM Windows
|
|
975
|
+
# build, and choco packages do not shim every binary.
|
|
976
|
+
$checkTools = @(
|
|
977
|
+
@('go', 'version'),
|
|
978
|
+
@('rustc', '--version'),
|
|
979
|
+
@('cargo', '--version'),
|
|
980
|
+
@('protoc', '--version'),
|
|
981
|
+
@('nasm', '--version'),
|
|
982
|
+
@('clang', '--version'),
|
|
983
|
+
@('x86_64-w64-mingw32-gcc', '--version')
|
|
984
|
+
)
|
|
985
|
+
foreach ($tool in $checkTools) {
|
|
986
|
+
$cmd = Get-Command $tool[0] -ErrorAction SilentlyContinue
|
|
987
|
+
if ($cmd) {
|
|
988
|
+
& $cmd.Source $tool[1]
|
|
989
|
+
if ($LASTEXITCODE -ne 0) { Write-Warning "$($tool[0]) --version failed ($LASTEXITCODE)" }
|
|
990
|
+
} else {
|
|
991
|
+
Write-Warning "$($tool[0]) not found on PATH"
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
if (Test-Path 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC') {
|
|
995
|
+
Write-Host "MSVC: $('C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\VC\Tools\MSVC')"
|
|
996
|
+
}
|
|
997
|
+
Write-Host "Chrome: $(Test-Path 'C:\Program Files\Google\Chrome\Application\chrome.exe')"
|
|
998
|
+
Write-Host "Firefox: $(Test-Path 'C:\Program Files\Mozilla Firefox\firefox.exe')"
|
|
999
|
+
Write-Host "VS Code: $(Test-Path "$env:LOCALAPPDATA\Programs\Microsoft VS Code\bin\code.cmd")"
|
|
1000
|
+
# Cleanup — must never fail the build: choco cleanup can exit non-zero
|
|
1001
|
+
# (locked files etc.) and that $LASTEXITCODE would otherwise propagate
|
|
1002
|
+
# as the script's exit code even though everything succeeded. The
|
|
1003
|
+
# redirect happens inside cmd so PowerShell 5.1 never turns choco's
|
|
1004
|
+
# stderr into a terminating error under $ErrorActionPreference='Stop'.
|
|
1005
|
+
cmd /c "choco cleanup -y > NUL 2>&1"
|
|
1006
|
+
$LASTEXITCODE = 0
|
|
1007
|
+
Remove-Item -Path $env:TEMP\* -Recurse -Force -ErrorAction SilentlyContinue
|
|
1008
|
+
Write-Host 'Windows sandbox image complete'
|
|
1009
|
+
END
|
|
1010
|
+
]
|
|
1011
|
+
}
|
|
1012
|
+
}
|