feat: add integration test infra and bootstrapper updates

This commit is contained in:
2026-06-03 19:27:34 -05:00
parent 1392a503f1
commit 7f0176cbf5
45 changed files with 5751 additions and 589 deletions
+167
View File
@@ -0,0 +1,167 @@
# Vagrant box build (Packer + Hyper-V + Windows 11 25H2)
Builds a `bootstrap-win11.box` Vagrant box from a stock Windows 11 25H2 ISO,
ready to use with `test/integration/Vagrantfile`.
## Prerequisites
On the host (must be Windows with Hyper-V):
- Hyper-V role enabled; current user in **Hyper-V Administrators**.
- [HashiCorp Packer](https://developer.hashicorp.com/packer/install) 1.15+ in
`PATH`. HashiCorp does not publish a winget manifest, so use the helper:
```powershell
# from an elevated PowerShell
cd test\integration\packer
.\install-packer.ps1 # installs the pinned default version
.\install-packer.ps1 -Version 1.14.0 # or pick a specific release
```
The script downloads
`https://releases.hashicorp.com/packer/<Version>/packer_<Version>_windows_amd64.zip`,
extracts `packer.exe` to `C:\Program Files\Packer\`, and adds that directory
to the machine-scoped `PATH`. It is idempotent — re-running with the same
version just re-asserts the `PATH` entry. See the `param()` block in
`install-packer.ps1` for available flags.
By default the same script also installs **Windows ADK Deployment Tools**
to get `oscdimg.exe`, which Packer's `hyperv-iso` builder needs to build the
unattended-install secondary ISO from `cd_files`. Only the
`OptionId.DeploymentTools` feature is selected, so the install is small
(~70 MB) and unattended. Pass `-SkipOscdimg` to opt out (e.g. if you already
have `xorriso`/`mkisofs` on `PATH`). After install you may need to open a
fresh shell — or run
`$env:Path = [Environment]::GetEnvironmentVariable('Path','Machine')` — for
`oscdimg.exe` to resolve in the current session.
- [HashiCorp Vagrant](https://developer.hashicorp.com/vagrant/install) 2.4+ in
`PATH` (used for the `vagrant` post-processor and, later, the integration
test itself).
- A Windows 11 client ISO somewhere on disk. Its path is read from `.env`
(see below) and is **not** hardcoded in the Packer config.
- Roughly 90 GB free disk and ~60 minutes of patience for a first build.
- An elevated PowerShell session for the build (`packer build` shells out to
Hyper-V management APIs that require admin).
## Configure the ISO path via `.env`
The Packer config has no default ISO path. Copy the template, fill it in, and
load it into the current shell before building:
```powershell
cd test\integration\packer
Copy-Item .env.example .env
# edit .env so PKR_VAR_iso_path points at your Windows 11 ISO
. .\load-env.ps1
```
`load-env.ps1` reads `.env` line-by-line and exports each `KEY=VALUE` into the
process environment. `.env` is gitignored at the repo root, so your local path
never gets committed.
Packer picks the values up automatically through its `PKR_VAR_<name>` env-var
convention; nothing about the `packer build` invocation changes.
## One-time setup
```powershell
packer init .\windows-11.pkr.hcl
```
That downloads the `hyperv` and `vagrant` Packer plugins.
## Compute and pin the ISO checksum (recommended)
```powershell
Get-FileHash $env:PKR_VAR_iso_path -Algorithm SHA256
```
Set `PKR_VAR_iso_checksum=sha256:<digest>` in `.env` and re-run `load-env.ps1`.
Leaving it as `none` skips validation; fine for local builds but loses the "my
ISO has not been swapped" guarantee.
## Build
```powershell
packer build .\windows-11.pkr.hcl
```
Roughly what happens:
1. Packer creates a Gen 2 Hyper-V VM (Secure Boot + TPM + 24 vCPU + 32 GB RAM
+ 80 GB dynamic VHDX) attached to `Default Switch`. The build VM is
throwaway and intentionally claims most of the host for speed; the
runtime integration VM uses smaller defaults set in
`test/integration/Vagrantfile` (8 vCPU / 16 GB).
2. It mounts the Windows ISO and a tiny `PROVISION` ISO containing
`cd/autounattend.xml` and `cd/scripts/oobe-enable-winrm.ps1`.
3. Windows Setup performs an unattended install (Pro edition, generic
activation key) and creates user `vagrant`:`vagrant`.
4. FirstLogonCommands enable WinRM (HTTP, basic auth) and set
`LocalAccountTokenFilterPolicy=1` so remote admin tokens are not filtered.
5. Packer connects over WinRM and runs the provisioners in `scripts/`:
- `configure-os.ps1` — UAC token policy, DiagTrack off, NTP resync.
- `disable-windows-updates.ps1` — fully disables WU and friends.
- `disable-defender-cloud.ps1` — disables MAPS / cloud lookups.
- `install-vagrant-key.ps1` — drops the vagrant insecure public key.
- `compact.ps1` — cleans caches, zeros free space.
6. Packer shuts the guest down cleanly and the `vagrant` post-processor
packages the VHDX into `bootstrap-win11.box`.
## Register the resulting box with Vagrant
```powershell
vagrant box add bootstrap-win11 .\bootstrap-win11.box
```
Then in `test/integration/Vagrantfile` set:
```powershell
$env:BOOTSTRAP_BOX = "bootstrap-win11"
go run ..\..\test\integration
```
(or just edit the default in the Vagrantfile to `bootstrap-win11`).
## Layout
```
test/integration/packer/
├── README.md ← this file
├── .env.example ← template; copy to .env (gitignored)
├── load-env.ps1 ← dot-source to export PKR_VAR_* into shell
├── install-packer.ps1 ← elevated installer for packer.exe
├── windows-11.pkr.hcl ← Packer HCL2 build definition
├── vagrantfile-template.rb ← baked into the output box
├── cd/
│ ├── autounattend.xml ← Windows Setup answer file
│ └── scripts/
│ └── oobe-enable-winrm.ps1
└── scripts/ ← provisioners run after WinRM is up
├── configure-os.ps1
├── disable-windows-updates.ps1
├── disable-defender-cloud.ps1
├── install-vagrant-key.ps1
└── compact.ps1
```
## Notes and gotchas
- **Run elevated.** `packer build` opens Hyper-V management APIs; non-elevated
shells fail with confusing "access denied" messages midway through.
- **First build is slow.** Windows Setup + updates trimming + cipher /w on a
60 GB volume can take 45–90 min. Subsequent rebuilds reuse the parent VHDX
via linked clone and are much faster.
- **License compliance.** The box uses the public KMS client setup key
(W269N-WFGWX-YVC9B-4J6C9-T83GX) to pick the Pro edition during install. The
resulting VM is not activated; for short-lived integration runs the eval
period is more than sufficient, but redistribute the box only under your
own licensing terms.
- **25H2 OOBE.** The autounattend sets `BypassNRO=1` during `specialize`
because 24H2/25H2 removed the `BypassNRO.cmd` helper. If a future Windows
update changes the OOBE flow again, the symptom will be a hung
`vagrant up` waiting for WinRM; check by opening Hyper-V Manager and
looking for an OOBE screen on the VM console.
- **Secure Boot.** Enabled (`MicrosoftWindows` template). Switch off in the
Packer source if you ever need to install unsigned kernel-mode drivers in
the box.