diff --git a/tools/devboxes/README.md b/tools/devboxes/README.md new file mode 100644 index 00000000000..e340ba7a397 --- /dev/null +++ b/tools/devboxes/README.md @@ -0,0 +1,32 @@ +# Devboxes + +This directory contains tools to create virtual machine images to help you test workerd on different machines/OSes. + +Right now there is only a Windows devbox. + +## Pre-requisites + +This directory has been tested with: + +- QEMU 6.2.0 +- libvirt 8.0.0 +- aria2c 1.36.0 +- Packer 1.11.2 + +### Ubuntu 22.04 + +On Ubuntu 22.04, [install Packer manually](https://developer.hashicorp.com/packer/install). The version in APT is too old. + +The rest you can install from APT: + +```sh +sudo apt-get install qemu-system-x86 libvirt-daemon-system aria2 +``` + +You'll need to add yourself to the `kvm` and `libvirt` groups in order to use QEMU with KVM acceleration, and to communicate with libvirtd. + +```sh +sudo adduser $(whoami) kvm +sudo adduser $(whoami) libvirt +# Remember to log out and in again for the groups to take effect! +``` diff --git a/tools/devboxes/windows-2022/.gitignore b/tools/devboxes/windows-2022/.gitignore new file mode 100644 index 00000000000..2829e042f06 --- /dev/null +++ b/tools/devboxes/windows-2022/.gitignore @@ -0,0 +1,2 @@ +deps/ +deps.auto.pkrvars.hcl diff --git a/tools/devboxes/windows-2022/README.md b/tools/devboxes/windows-2022/README.md new file mode 100644 index 00000000000..ece929d26c0 --- /dev/null +++ b/tools/devboxes/windows-2022/README.md @@ -0,0 +1,165 @@ +# Windows Server 2022 development box + +This directory contains installation and provisioning Packer templates for Windows Server 2022 Desktop. + +Note: Up until recently, Microsoft used to publish "dev VMs". As of Octoboer 23, 2024, they are unavailable ["due to ongoing technical issues"](https://developer.microsoft.com/en-us/windows/downloads/virtual-machines/). If they ever come back, we could use them instead of this manually-built VM image. + +Note: [Winget does not currently work on Windows Server 2022 Core](https://github.com/microsoft/winget-cli/issues/4319), which is why we use the Desktop Experience instead. + +This directory is split into two separate Packer templates: +- `windows-2022/base` (installation, based on https://github.com/StefanScherer/packer-windows) +- `windows-2022` (provisioning, this directory) + +The reason for this split into two Packer builds is to make it easier to hack on the provisioning scripts without having to wait for a full re-installation. The `windows-2022` template is configured with a copy-on-write image using the `windows-2022/base` template's read-only image as a backing file, so the `windows-2022` image can be relatively quickly recreated. + +Steps to build and use this devbox: +1. Install pre-requisites +1. Fetch dependencies +1. Configure custom root CA (if applicable) +1. Run `packer build` +1. Import the resulting .qcow2 file into a VM with `virt-manager` +1. Make an SSH config on host OS +1. Configure Git on guest OS +1. (Optional) Set up a shared drive + +## Install pre-requisites + +As described in the parent directory's [README.md](../README.md). + +## Fetch dependencies + +There's a script in this directory, `fetch-deps.sh`, which uses `aria2c` to download some binary dependencies for the Packer builds. You can run it from any directory, and the dependencies will end up in this directory (and the `base/` subdirectory). + +```sh +./fetch-deps.sh +``` + +The script also creates a couple of `deps.auto.pkrvars.hcl` files, which tell our Packer templates the absolute paths to the dependency directories. Without these files, our Packer builds will produce errors nudging you to run `fetch-deps.sh`. + +## Configure custom root CA (if applicable) + +If you need to use a custom root CA (e.g., you use Cloudflare One / Warp), create a file in this directory like so: + +``` +# custom-root-ca.auto.pkrvars.hcl +custom_root_ca = "/path/to/my/ca.pem" +``` + +If you do not need to use a custom root CA, you will still need to configure this variable. Set it to the empty string to indicate no custom root CA is required. + +``` +# custom-root-ca.auto.pkrvars.hcl +custom_root_ca = "" # intentionally empty +``` + +The reason this variable is required is to make it harder to accidentally forget to embed a custom root CA if you do need one, which leads to lots of follow-on troubleshooting. + +## Run `packer build` + +Run `packer build` against first this directory's `base/` subdirectory, then this directory itself. + +```sh +packer build ./base +packer build . +``` + +This will produce two output directories in your current working directory, named `output-windows-2022-base/` and `output-windows-2022/`. Each of these directories has a QCOW2 image inside it, named `windows-2022-base.qcow2` and `windows-2022.qcow2`. The first image is a read-only snapshot of a fresh installation of Windows. The second image is a copy-on-write overlay using the first as a backing file, and contains Windows plus various utilities provisioned: your custom root CA, virtiofs, Winget, SSH, Git, MSYS, etc. + +Changing the first, base image (`windows-2022-base.qcow2`) in any way will corrupt the overlay (`windows-2022.qcow2`). If you would like a standalone QCOW2 image, you can use `qemu-img` to convert it. This takes a while, which is why the Packer template doesn't do this for you. + +## Import the resulting .qcow2 file into a VM with `virt-manager` + +Run `virt-manager`, and create a new virtual machine, importing `output-windows-2022/windows-2022.qcow2`. Give the machine plenty of RAM and CPU cores (I use 16G RAM and 16 cores). + +You can now develop directly inside of the Windows desktop via virt-manager's VM console, if you wish. + +## Make an SSH config on host OS + +Once your VM boots, take note of its IP address so you can SSH into it. You can find the VM's IP address in `virt-manager` by looking at the VM's info page and selecting its NIC after it has started up for the first time. + +Add the following to your `~/.ssh/config`: + +``` +Host winbox + User vagrant + HostName +``` + +You should now be able to use VSCode's Remote-SSH extension running on your host OS to connect to the `winbox` SSH host, and develop remotely. + +Note that if you ever delete and recreate the VM, it will likely be assigned a new IP address. + +## Configure Git on guest OS + +Remember to configure your contact information: + +```sh +git config --global user.name "My Name" +git config --global user.email "my@example.com" +``` + +You'll also need some way of authenticating with GitHub. One easy way is to generate a (classic) GitHub Personal Access Token with access to `repos`. First, configure a simple credential store, like DPAPI, on the guest OS: + +```sh +git config --global credential.credentialStore dpapi +``` + +Then create your access token in the GitHub developer settings page, and add it like so: + +```sh +git credential approve +# Enter the following on stdin: +protocol=https +host=github.com +username=your_github_username +password=your_github_personal_access_token +# Press enter twice +``` + +Now you can push from your Windows guest OS to repos you have write access to. + +## Set up a shared drive + +To configure a shared drive for your VM, first shut the VM down if it is already running. + +Next, go to the VM's info page in `virt-manager`, click on the "Memory" panel, check "Enable shared memory", and click "Apply". + +Lastly, from the VM's info page, click "Add Hardware" -> "Filesystem" -> ensure "Driver is "virtiofs", and fill in the "Source path" to point to your desired host-side shared directory. Put whatever identifier you want in the "Target path" textbox, it doesn't matter. + +The next time you start the VM, you should see your shared directory available as drive Z:\. + +# Options + +## Graphical output, headless mode + +By default, the build displays the QEMU console, which shows the VM's graphical output. This allows you to observe the Windows installation process directly. + +To inhibit this behavior, add `--var headless=true` to your `packer build` command. + +# Troubleshooting + +## Observability tools + +Your two basic tools for gaining visibility onto what is going on in the Packer build is setting the `PACKER_LOG=1` environment variable, and viewing the VM's graphical output, via the QEMU console (`--var headless=false`). (It's also possible to connect via VNC, even in headless mode.) + +## `qemu: Waiting for WinRM to become available...` hangs forever + +Check the QEMU console to see what the VM's graphical output tells you. + +## Packer fails with a qemu-img error "Backing file specified without backing format" + +You are using QEMU version >= 6.1 and Packer QEMU Plugin < 1.1.0. Upgrade your plugin with `packer init`. + +## QEMU fails with" /usr/bin/qemu-system-x86_64: symbol lookup error: /snap/core20/current/lib/x86_64-linux-gnu/libpthread.so.0: undefined symbol: __libc_pthread_init, version GLIBC_PRIVATE" + +You are probably running Packer inside a VS Code terminal. Try running Packer in a regular terminal. + +I don't know the exact cause of this failure, but it affects multiple programs, not just QEMU. + +## Windows shows a BSOD during boot with stop code 0x0000007b / INACCESSIBLE_BOOT_DEVICE. + +This can be caused if you change the size of the hard drive after installation. In particular, I encountered it when `windows-2022-base`'s Packer template used a different hard disk size than `windows-2022`'s template. + +It may be possible to set up a post-login script which hacks around this issue, as described here: https://superuser.com/a/1439626 + +There can be many other causes of this BSOD as well, unfortunately. diff --git a/tools/devboxes/windows-2022/base/answer_files/2022/Autounattend.xml b/tools/devboxes/windows-2022/base/answer_files/2022/Autounattend.xml new file mode 100644 index 00000000000..30d100c6a8d --- /dev/null +++ b/tools/devboxes/windows-2022/base/answer_files/2022/Autounattend.xml @@ -0,0 +1,312 @@ + + + + + + + + + E:\viostor\2k22\amd64 + + + + E:\NetKVM\2k22\amd64 + + + + E:\Balloon\2k22\amd64 + + + + E:\pvpanic\2k22\amd64 + + + + E:\qemupciserial\2k22\amd64 + + + + + + + E:\vioinput\2k22\amd64 + + + + E:\viorng\2k22\amd64 + + + + E:\vioscsi\2k22\amd64 + + + + E:\vioserial\2k22\amd64 + + + + + + + en-US + + en-US + en-US + en-US + en-US + en-US + + + + + + + Primary + 1 + 350 + + + 2 + Primary + true + + + + + true + NTFS + + 1 + 1 + + + NTFS + + C + 2 + 2 + + + 0 + true + + + + + + + /IMAGE/NAME + + Windows Server 2022 SERVERDATACENTER + + + + 0 + 2 + + + + + + + + + + OnError + + true + Vagrant + Vagrant + + + + + + + false + + vagrant-2022 + + Pacific Standard Time + + + + true + + + false + false + + + true + + + true + + + + + 1 + Set Execution Policy 64 Bit + cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force" + + + 2 + Set Execution Policy 32 Bit + cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force" + + + 3 + Disable WinRM + C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\disable-winrm.ps1 + + + + + + + + + vagrant + true</PlainText> + </Password> + <Enabled>true</Enabled> + <Username>vagrant</Username> + </AutoLogon> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <Description>Set Execution Policy 64 Bit</Description> + <Order>1</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>C:\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <Description>Set Execution Policy 32 Bit</Description> + <Order>2</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\disable-winrm.ps1</CommandLine> + <Description>Disable WinRM</Description> + <Order>3</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine> + <Order>4</Order> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\Console /v QuickEdit /t REG_DWORD /d 1 /f</CommandLine> + <Order>5</Order> + <Description>Enable QuickEdit mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v Start_ShowRun /t REG_DWORD /d 1 /f</CommandLine> + <Order>6</Order> + <Description>Show Run command in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v StartMenuAdminTools /t REG_DWORD /d 1 /f</CommandLine> + <Order>7</Order> + <Description>Show Administrative Tools in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateFileSizePercent /t REG_DWORD /d 0 /f</CommandLine> + <Order>8</Order> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateEnabled /t REG_DWORD /d 0 /f</CommandLine> + <Order>9</Order> + <Description>Disable Hibernation Mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c wmic useraccount where "name='vagrant'" set PasswordExpires=FALSE</CommandLine> + <Order>10</Order> + <Description>Disable password expiration for vagrant user</Description> + </SynchronousCommand> + <!-- TODO: Can this be moved to a Packer provisioner stage? It seems to hang when run there. --> + <SynchronousCommand wcm:action="add"> + <CommandLine>E:\virtio-win-guest-tools.exe /passive /norestart</CommandLine> + <Order>11</Order> + <Description>Install VirtIO drivers</Description> + </SynchronousCommand> + <!-- WITHOUT WINDOWS UPDATES --> + + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\enable-winrm.ps1</CommandLine> + <Description>Enable WinRM</Description> + <Order>99</Order> + </SynchronousCommand> + + <!-- END WITHOUT WINDOWS UPDATES --> + <!-- WITH WINDOWS UPDATES --> + <!-- <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c a:\microsoft-updates.bat</CommandLine> + <Order>98</Order> + <Description>Enable Microsoft Updates</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\disable-screensaver.ps1</CommandLine> + <Description>Disable Screensaver</Description> + <Order>99</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\win-updates.ps1</CommandLine> + <Description>Install Windows Updates</Description> + <Order>100</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> --> + <!-- END WITH WINDOWS UPDATES --> + </FirstLogonCommands> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Home</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + </OOBE> + <UserAccounts> + <AdministratorPassword> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Password> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </Password> + <Group>administrators</Group> + <DisplayName>Vagrant</DisplayName> + <Name>vagrant</Name> + <Description>Vagrant User</Description> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <RegisteredOwner /> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <!-- TODO: Change to Server 2022? --> + <cpi:offlineImage cpi:source="wim:c:/wim/install.wim#Windows Server 2012 R2 SERVERSTANDARD" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> +</unattend> diff --git a/tools/devboxes/windows-2022/base/scripts/dis-updates.bat b/tools/devboxes/windows-2022/base/scripts/dis-updates.bat new file mode 100644 index 00000000000..3ff0c8f34a9 --- /dev/null +++ b/tools/devboxes/windows-2022/base/scripts/dis-updates.bat @@ -0,0 +1,20 @@ +rem http://www.windows-commandline.com/disable-automatic-updates-command-line/ +reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" /v AUOptions /t REG_DWORD /d 1 /f + +rem remove optional WSUS server settings +reg delete "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" /f + +rem even harder, disable windows update service +rem sc config wuauserv start= disabled +rem net stop wuauserv +set logfile=C:\Windows\Temp\win-updates.log + +if exist %logfile% ( + echo Show Windows Updates log file %logfile% + dir %logfile% + type %logfile% + rem output of type command is not fully shown in packer/ssh session, so try PowerShell + rem but it will hang if log file is about 22 KByte + rem powershell -command "Get-Content %logfile%" + echo End of Windows Updates log file %logfile% +) diff --git a/tools/devboxes/windows-2022/base/scripts/disable-screensaver.ps1 b/tools/devboxes/windows-2022/base/scripts/disable-screensaver.ps1 new file mode 100644 index 00000000000..dd0a02900d2 --- /dev/null +++ b/tools/devboxes/windows-2022/base/scripts/disable-screensaver.ps1 @@ -0,0 +1,4 @@ +Write-Output "Disabling Screensaver" +Set-ItemProperty "HKCU:\Control Panel\Desktop" -Name ScreenSaveActive -Value 0 -Type DWord +& powercfg -x -monitor-timeout-ac 0 +& powercfg -x -monitor-timeout-dc 0 diff --git a/tools/devboxes/windows-2022/base/scripts/disable-winrm.ps1 b/tools/devboxes/windows-2022/base/scripts/disable-winrm.ps1 new file mode 100644 index 00000000000..28b92bd5ba7 --- /dev/null +++ b/tools/devboxes/windows-2022/base/scripts/disable-winrm.ps1 @@ -0,0 +1,8 @@ +netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=block +netsh advfirewall firewall set rule group="Windows Remote Management" new enable=yes +$winrmService = Get-Service -Name WinRM +if ($winrmService.Status -eq "Running") { + Disable-PSRemoting -Force +} +Stop-Service winrm +Set-Service -Name winrm -StartupType Disabled diff --git a/tools/devboxes/windows-2022/base/scripts/enable-rdp.bat b/tools/devboxes/windows-2022/base/scripts/enable-rdp.bat new file mode 100644 index 00000000000..f7dcaab5c54 --- /dev/null +++ b/tools/devboxes/windows-2022/base/scripts/enable-rdp.bat @@ -0,0 +1,2 @@ +netsh advfirewall firewall add rule name="Open Port 3389" dir=in action=allow protocol=TCP localport=3389 +reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f diff --git a/tools/devboxes/windows-2022/base/scripts/enable-winrm.ps1 b/tools/devboxes/windows-2022/base/scripts/enable-winrm.ps1 new file mode 100644 index 00000000000..251b3eba72b --- /dev/null +++ b/tools/devboxes/windows-2022/base/scripts/enable-winrm.ps1 @@ -0,0 +1,15 @@ +Get-NetConnectionProfile | Set-NetConnectionProfile -NetworkCategory Private + +Enable-PSRemoting -Force +winrm quickconfig -q +winrm quickconfig -transport:http +winrm set winrm/config '@{MaxTimeoutms="1800000"}' +winrm set winrm/config/winrs '@{MaxMemoryPerShellMB="800"}' +winrm set winrm/config/service '@{AllowUnencrypted="true"}' +winrm set winrm/config/service/auth '@{Basic="true"}' +winrm set winrm/config/client/auth '@{Basic="true"}' +winrm set winrm/config/listener?Address=*+Transport=HTTP '@{Port="5985"}' +netsh advfirewall firewall set rule group="Windows Remote Administration" new enable=yes +netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" new enable=yes action=allow remoteip=any +Set-Service winrm -startuptype "auto" +Restart-Service winrm diff --git a/tools/devboxes/windows-2022/base/scripts/microsoft-updates.bat b/tools/devboxes/windows-2022/base/scripts/microsoft-updates.bat new file mode 100644 index 00000000000..edb849f4920 --- /dev/null +++ b/tools/devboxes/windows-2022/base/scripts/microsoft-updates.bat @@ -0,0 +1,12 @@ +net stop wuauserv + +reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" /v EnableFeaturedSoftware /t REG_DWORD /d 1 /f + +reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update" /v IncludeRecommendedUpdates /t REG_DWORD /d 1 /f + +echo Set ServiceManager = CreateObject("Microsoft.Update.ServiceManager") > A:\temp.vbs +echo Set NewUpdateService = ServiceManager.AddService2("7971f918-a847-4430-9279-4a52d1efe18d",7,"") >> A:\temp.vbs + +cscript A:\temp.vbs + +net start wuauserv diff --git a/tools/devboxes/windows-2022/base/scripts/set-winrm-automatic.bat b/tools/devboxes/windows-2022/base/scripts/set-winrm-automatic.bat new file mode 100644 index 00000000000..fba5809de01 --- /dev/null +++ b/tools/devboxes/windows-2022/base/scripts/set-winrm-automatic.bat @@ -0,0 +1,2 @@ +echo Set WinRM start type to auto +sc config winrm start= auto diff --git a/tools/devboxes/windows-2022/base/scripts/uac-enable.bat b/tools/devboxes/windows-2022/base/scripts/uac-enable.bat new file mode 100644 index 00000000000..278ac00089a --- /dev/null +++ b/tools/devboxes/windows-2022/base/scripts/uac-enable.bat @@ -0,0 +1 @@ +reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /f /v EnableLUA /t REG_DWORD /d 1 diff --git a/tools/devboxes/windows-2022/base/scripts/win-updates.ps1 b/tools/devboxes/windows-2022/base/scripts/win-updates.ps1 new file mode 100644 index 00000000000..2091a69e173 --- /dev/null +++ b/tools/devboxes/windows-2022/base/scripts/win-updates.ps1 @@ -0,0 +1,252 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] +param($global:RestartRequired = 0, + $global:MoreUpdates = 0, + $global:MaxCycles = 5, + $MaxUpdatesPerCycle = 500, + $BeginWithRestart = 0) + +$Logfile = "C:\Windows\Temp\win-updates.log" + +function LogWrite { + Param ([string]$logstring) + $now = Get-Date -format s + Add-Content $Logfile -value "$now $logstring" + Write-Output $logstring +} + +function Check-ContinueRestartOrEnd() { + $RegistryKey = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" + $RegistryEntry = "InstallWindowsUpdates" + switch ($global:RestartRequired) { + 0 { + $prop = (Get-ItemProperty $RegistryKey).$RegistryEntry + if ($prop) { + LogWrite "Restart Registry Entry Exists - Removing It" + Remove-ItemProperty -Path $RegistryKey -Name $RegistryEntry -ErrorAction SilentlyContinue + } + + LogWrite "No Restart Required" + Check-WindowsUpdates + + if (($global:MoreUpdates -eq 1) -and ($script:Cycles -le $global:MaxCycles)) { + Install-WindowsUpdates + } + elseif ($script:Cycles -gt $global:MaxCycles) { + LogWrite "Exceeded Cycle Count - Stopping" + & "a:\enable-winrm.ps1" + } + else { + LogWrite "Done Installing Windows Updates" + & "a:\enable-winrm.ps1" + } + } + 1 { + $prop = (Get-ItemProperty $RegistryKey).$RegistryEntry + if (-not $prop) { + LogWrite "Restart Registry Entry Does Not Exist - Creating It" + Set-ItemProperty -Path $RegistryKey -Name $RegistryEntry -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File $($script:ScriptPath) -MaxUpdatesPerCycle $($MaxUpdatesPerCycle)" + } + else { + LogWrite "Restart Registry Entry Exists Already" + } + + LogWrite "Restart Required - Restarting..." + Restart-Computer + } + default { + LogWrite "Unsure If A Restart Is Required" + break + } + } +} + +function Install-WindowsUpdates() + { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')] + param() + $script:Cycles++ + LogWrite "Evaluating Available Updates with limit of $($MaxUpdatesPerCycle):" + $UpdatesToDownload = New-Object -ComObject 'Microsoft.Update.UpdateColl' + $script:i = 0; + $CurrentUpdates = $SearchResult.Updates + while ($script:i -lt $CurrentUpdates.Count -and $script:CycleUpdateCount -lt $MaxUpdatesPerCycle) { + $Update = $CurrentUpdates.Item($script:i) + if ($null -ne $Update) { + [bool]$addThisUpdate = $false + if ($Update.InstallationBehavior.CanRequestUserInput) { + LogWrite "> Skipping: $($Update.Title) because it requires user input" + } + else { + if (!($Update.EulaAccepted)) { + LogWrite "> Note: $($Update.Title) has a license agreement that must be accepted. Accepting the license." + $Update.AcceptEula() + [bool]$addThisUpdate = $true + $script:CycleUpdateCount++ + } + else { + [bool]$addThisUpdate = $true + $script:CycleUpdateCount++ + } + } + + if ([bool]$addThisUpdate) { + LogWrite "Adding: $($Update.Title)" + $UpdatesToDownload.Add($Update) | Out-Null + } + } + $script:i++ + } + + if ($UpdatesToDownload.Count -eq 0) { + LogWrite "No Updates To Download..." + } + else { + LogWrite 'Downloading Updates...' + $ok = 0; + while (! $ok) { + try { + $Downloader = $UpdateSession.CreateUpdateDownloader() + $Downloader.Updates = $UpdatesToDownload + $Downloader.Download() + $ok = 1; + } + catch { + LogWrite $_.Exception | Format-List -force + LogWrite "Error downloading updates. Retrying in 30s." + $script:attempts = $script:attempts + 1 + Start-Sleep -s 30 + } + } + } + + $UpdatesToInstall = New-Object -ComObject 'Microsoft.Update.UpdateColl' + [bool]$rebootMayBeRequired = $false + LogWrite 'The following updates are downloaded and ready to be installed:' + foreach ($Update in $SearchResult.Updates) { + if (($Update.IsDownloaded)) { + LogWrite "> $($Update.Title)" + $UpdatesToInstall.Add($Update) | Out-Null + + if ($Update.InstallationBehavior.RebootBehavior -gt 0) { + [bool]$rebootMayBeRequired = $true + } + } + } + + if ($UpdatesToInstall.Count -eq 0) { + LogWrite 'No updates available to install...' + $global:MoreUpdates = 0 + $global:RestartRequired = 0 + & "a:\enable-winrm.ps1" + break + } + + if ($rebootMayBeRequired) { + LogWrite 'These updates may require a reboot' + $global:RestartRequired = 1 + } + + LogWrite 'Installing updates...' + + $Installer = $script:UpdateSession.CreateUpdateInstaller() + $Installer.Updates = $UpdatesToInstall + $InstallationResult = $Installer.Install() + + LogWrite "Installation Result: $($InstallationResult.ResultCode)" + LogWrite "Reboot Required: $($InstallationResult.RebootRequired)" + LogWrite 'Listing of updates installed and individual installation results:' + if ($InstallationResult.RebootRequired) { + $global:RestartRequired = 1 + } + else { + $global:RestartRequired = 0 + } + + for ($i = 0; $i -lt $UpdatesToInstall.Count; $i++) { + New-Object -TypeName PSObject -Property @{ + Title = $UpdatesToInstall.Item($i).Title + Result = $InstallationResult.GetUpdateResult($i).ResultCode + } + LogWrite "Item: $($UpdatesToInstall.Item($i).Title)" + LogWrite "Result: $($InstallationResult.GetUpdateResult($i).ResultCode)" + } + + Check-ContinueRestartOrEnd +} + +function Check-WindowsUpdates() { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '')] + param() + LogWrite "Checking For Windows Updates" + $Username = $env:USERDOMAIN + "\" + $env:USERNAME + LogWrite "Script: $script:ScriptPath `nScript User: $Username `nStarted: $(Get-Date)" + + $script:UpdateSearcher = $script:UpdateSession.CreateUpdateSearcher() + $script:successful = $FALSE + $script:attempts = 0 + $script:maxAttempts = 12 + while (-not $script:successful -and $script:attempts -lt $script:maxAttempts) { + try { + $script:SearchResult = $script:UpdateSearcher.Search("IsInstalled=0 and Type='Software' and IsHidden=0") + $script:successful = $TRUE + } + catch { + LogWrite $_.Exception | Format-List -force + LogWrite "Search call to UpdateSearcher was unsuccessful. Retrying in 10s." + $script:attempts = $script:attempts + 1 + Start-Sleep -s 10 + } + } + + if ($SearchResult.Updates.Count -ne 0) { + $Message = "There are " + $SearchResult.Updates.Count + " more updates." + LogWrite $Message + try { + for ($i = 0; $i -lt $script:SearchResult.Updates.Count; $i++) { + LogWrite $script:SearchResult.Updates.Item($i).Title + LogWrite $script:SearchResult.Updates.Item($i).Description + LogWrite $script:SearchResult.Updates.Item($i).RebootRequired + LogWrite $script:SearchResult.Updates.Item($i).EulaAccepted + } + $global:MoreUpdates = 1 + } + catch { + LogWrite $_.Exception | Format-List -force + LogWrite "Showing SearchResult was unsuccessful. Rebooting." + $global:RestartRequired = 1 + $global:MoreUpdates = 0 + Check-ContinueRestartOrEnd + LogWrite "Show never happen to see this text!" + Restart-Computer + } + } + else { + LogWrite 'There are no applicable updates' + $global:RestartRequired = 0 + $global:MoreUpdates = 0 + } +} + +$script:ScriptName = $MyInvocation.MyCommand.ToString() +$script:ScriptPath = $MyInvocation.MyCommand.Path +$script:UpdateSession = New-Object -ComObject 'Microsoft.Update.Session' +$script:UpdateSession.ClientApplicationID = 'Packer Windows Update Installer' +$script:UpdateSearcher = $script:UpdateSession.CreateUpdateSearcher() +$script:SearchResult = New-Object -ComObject 'Microsoft.Update.UpdateColl' +$script:Cycles = 0 +$script:CycleUpdateCount = 0 + +if ($BeginWithRestart) { + $global:RestartRequired = 1 + Check-ContinueRestartOrEnd +} + +Check-WindowsUpdates +if ($global:MoreUpdates -eq 1) { + Install-WindowsUpdates +} +else { + Check-ContinueRestartOrEnd +} diff --git a/tools/devboxes/windows-2022/base/windows-2022-base.pkr.hcl b/tools/devboxes/windows-2022/base/windows-2022-base.pkr.hcl new file mode 100644 index 00000000000..d069eccf2ea --- /dev/null +++ b/tools/devboxes/windows-2022/base/windows-2022-base.pkr.hcl @@ -0,0 +1,160 @@ +# Windows Server 2022 Desktop installation template +# +# This was originally bootstrapped by copying +# https://github.com/StefanScherer/packer-windows/blob/main/windows_2022.json. I then converted it +# to HCL, de-Vagranted it, and removed several scripts which weren't useful for our use case. + +packer { + required_plugins { + qemu = { + source = "github.com/hashicorp/qemu" + version = "~> 1.1.0" + } + } +} + +variables { + cpus = 2 + # Disk size must match the disk size in windows-2022.pkr.hcl, or else you will get a BSOD! + disk_size = 61440 + headless = false + memory = 8192 +} + +# The `installer_iso` variable contains a link to the current Windows Server 2022 +# English (United States) ISO image. +# +# To update the link: +# +# 1. Visit https://www.microsoft.com/en-us/evalcenter/download-windows-server-2022. +# +# 2. Use `curl` to GET the URL for the English (United States) ISO. Expect to receive a +# 301 Moved Permanently response from the origin. +# +# 3. Copy the response's Location header value to this variable's default value below. +# +# 4. Run `packer build`. When the checksum fails, make note of the new checksum, and update the +# `installer_iso_checksum` variable's default value below. +variable "installer_iso" { + type = string + default = "https://software-static.download.prss.microsoft.com/sg/download/888969d5-f34g-4e03-ac9d-1f9786c66749/SERVER_EVAL_x64FRE_en-us.iso" +} + +variable "installer_iso_checksum" { + type = string + default = "sha256:3e4fa6d8507b554856fc9ca6079cc402df11a8b79344871669f0251535255325" +} + +# The `virtio_win_iso` variable identifies the path to the VirtIO Windows drivers ISO. It is +# mandatory. +# +# To set this variable, run the `fetch-deps.sh` script next to the main `windows-2022.pkr.hcl` +# Packer template. The script will download the VirtIO ISO file into the right place, and create +# a deps.auto.pkrvars.hcl file next to _this_ Packer template which sets this variable. +variable "virtio_win_iso" { + description = "Path to virtio-win.iso" + type = string + default = "" + + validation { + condition = length(var.virtio_win_iso) > 0 + error_message = "Run `fetch-deps.sh` to set this variable." + } + + validation { + condition = fileexists(var.virtio_win_iso) + error_message = "`virtio_win_iso` file does not exist. Run `fetch-deps.sh` to set this variable." + } +} + +# The `output_qcow2` variable is the name of the generated VM snapshot, ready for importing into +# virt-manager, or running directly with QEMU. +variable "output_qcow2" { + type = string + default = "windows-2022-base.qcow2" +} + +locals { + output_directory = "output-windows-2022-base" +} + +source "qemu" "windows-2022-base" { + # Hardware + accelerator = "kvm" + headless = var.headless + cpus = var.cpus + memory = var.memory + disk_size = var.disk_size + + # Input files + floppy_files = [ + # The Autounattend.xml chooses whether to install 2022 Desktop or Core. + "${path.root}/answer_files/2022/Autounattend.xml", + "${path.root}/scripts/disable-screensaver.ps1", + "${path.root}/scripts/disable-winrm.ps1", + "${path.root}/scripts/enable-winrm.ps1", + "${path.root}/scripts/microsoft-updates.bat", + "${path.root}/scripts/win-updates.ps1" + ] + + # Input image + iso_url = var.installer_iso + iso_checksum = var.installer_iso_checksum + + # Output image + output_directory = local.output_directory + vm_name = var.output_qcow2 + format = "qcow2" + + # Since we need to attach multiple drives, we must override `qemuargs`. This ends up overriding + # a lot of other disk-related configuration properties in the `qemu` data source. + qemuargs = [ + ["-drive", "file=${local.output_directory}/${var.output_qcow2},if=virtio,cache=writeback,discard=ignore,format=qcow2,index=1"], + ["-drive", "file=${var.installer_iso},media=cdrom,index=2"], + ["-drive", "file=${var.virtio_win_iso},media=cdrom,index=3"] + ] + + # Give a little time for QEMU's VNC server to start. + boot_wait = "1s" + + communicator = "winrm" + winrm_password = "vagrant" + winrm_username = "vagrant" + # We have to wait for Windows to install itself before we can connect with WinRM. This typically + # takes 10 to 30 minutes on my machine. + winrm_timeout = "2h" + + shutdown_command = "shutdown /s /t 10 /f /d p:4:1 /c \"Packer Shutdown\"" +} + +build { + sources = ["source.qemu.windows-2022-base"] + + provisioner "windows-shell" { + scripts = ["${path.root}/scripts/enable-rdp.bat"] + } + + provisioner "windows-shell" { + scripts = [ + "${path.root}/scripts/set-winrm-automatic.bat", + "${path.root}/scripts/uac-enable.bat", + "${path.root}/scripts/dis-updates.bat", + ] + } + + # We make the resulting image read-only, with the idea that you should use it as a backing file + # for a new copy-on-write image. This allows you to easily revert to a pristine snapshot of a + # freshly-installed Windows at any time. + post-processor "shell-local" { + inline = [ + "chmod 0444 ${local.output_directory}/${var.output_qcow2}", + # Run qemu-img to make backing file. + # Print something out. + ] + } + + post-processor "checksum" { + checksum_types = ["sha256"] + output = "${local.output_directory}/${var.output_qcow2}.{{.ChecksumType}}" + } +} diff --git a/tools/devboxes/windows-2022/deps.aria2-input b/tools/devboxes/windows-2022/deps.aria2-input new file mode 100644 index 00000000000..84df0d67e55 --- /dev/null +++ b/tools/devboxes/windows-2022/deps.aria2-input @@ -0,0 +1,49 @@ +# Input file for aria2c CLI download tool. Run like so: +# aria2c --check-integrity=true --input-file <this file> +# +# The --check-integrity=true parameter tells aria2c not to re-download a file if it exists and its +# checksum matches the one in this file. + +# ---------------------------------------------------------- +# windows-2022/base dependencies + +# Virtio driver ISO. These drivers allow guests to use more performant virtualized hardware that +# QEMU has to offer. The ISO also includes virtiofsd, which, when installed, allows you to configure +# a shared folder with the Windows guest OS, as well as QEMU and SPICE guest agents. +# +# Depending on how you construct the link, you may: +# +# - Un-pin, using whatever the latest stable version is: .../stable-virtio/virtio-win.iso +# - Pin to the latest stable version, breaking on update: .../stable-virtio/virtio-win-x.y.z.iso +# - Pin to a specific version: .../archive-virtio/virtio-win-0.1.266-1/virtio-win-0.1.266.iso +# +# You can find links to the latest stable version, and version archives, here: +# - https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/stable-virtio/ +# - https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/ +https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.266-1/virtio-win-0.1.266.iso + dir=base/deps + out=virtio-win.iso + checksum=sha-256=57b0f6dc8dc92dc2ae8621f8b1bfbd8a873de9bedc788c4c4b305ea28acc77cd + +# ---------------------------------------------------------- +# windows-2022 dependencies + +# Virtiofs service dependencies +https://github.com/winfsp/winfsp/releases/download/v2.0/winfsp-2.0.23075.msi + dir=deps + out=winfsp.msi + checksum=sha-256=6324dc81194a6a08f97b6aeca303cf5c2325c53ede153bae9fc4378f0838c101 + +# Winget dependencies +https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx + dir=deps + out=Microsoft.VCLibs.x64.14.Desktop.zip + checksum=sha-256=b56a9101f706f9d95f815f5b7fa6efbac972e86573d378b96a07cff5540c5961 +https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.7.1 + dir=deps + out=Microsoft.UI.Xaml.zip + checksum=sha-256=4ffd18beb1e26dff9f5bdfc8762882efbda3c241952efadbb7d1280e4796f85d +https://github.com/microsoft/winget-cli/releases/download/v1.9.25200/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle + dir=deps + out=Microsoft.DesktopAppInstaller.zip + checksum=sha-256=46d46bb5deacef0fd8ac30a223072b45ac2d5d5262d1591f2c08fb6ee15e4b22 diff --git a/tools/devboxes/windows-2022/fetch-deps.sh b/tools/devboxes/windows-2022/fetch-deps.sh new file mode 100755 index 00000000000..7af2b40476e --- /dev/null +++ b/tools/devboxes/windows-2022/fetch-deps.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +# This is a helper script various binary depdenecies for the windows-2022 and windows-2022/base +# Packer templates. + +set -euo pipefail + +# Download relative to this script. +ABS_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Remove any old Packer configuration for these deps. +rm "$ABS_SCRIPT_DIR/base/deps.auto.pkrvars.hcl" 2> /dev/null || true +rm "$ABS_SCRIPT_DIR/deps.auto.pkrvars.hcl" 2> /dev/null || true + +# Download. +(cd "$ABS_SCRIPT_DIR" && aria2c --check-integrity --input-file deps.aria2-input) + +# Configure windows-2022/base +echo "Writing base/deps.auto.pkrvars.hcl" +cat << EOF > "$ABS_SCRIPT_DIR/base/deps.auto.pkrvars.hcl" +virtio_win_iso = "$ABS_SCRIPT_DIR/base/deps/virtio-win.iso" +EOF + +# Configure windows-2022 +echo "Writing deps.auto.pkrvars.hcl" +cat << EOF > "$ABS_SCRIPT_DIR/deps.auto.pkrvars.hcl" +deps_dir = "$ABS_SCRIPT_DIR/deps" +EOF + +printf "Packer templates now configured. Build with \`packer build $ABS_SCRIPT_DIR/base && packer build $ABS_SCRIPT_DIR\`.\n" diff --git a/tools/devboxes/windows-2022/scripts/fix-virtiofs.ps1 b/tools/devboxes/windows-2022/scripts/fix-virtiofs.ps1 new file mode 100644 index 00000000000..f74d38030a3 --- /dev/null +++ b/tools/devboxes/windows-2022/scripts/fix-virtiofs.ps1 @@ -0,0 +1,5 @@ +# VirtioFsSvc depends on WinFsp being installed. +cmd /c msiexec /qb /i E:\deps\winfsp.msi + +# VirtioFsSvc should already have been installed by virtio-win-guest-tools.exe. +Set-Service -Name VirtioFsSvc -StartupType Automatic diff --git a/tools/devboxes/windows-2022/scripts/install-custom-root-ca.ps1 b/tools/devboxes/windows-2022/scripts/install-custom-root-ca.ps1 new file mode 100644 index 00000000000..50af13622f8 --- /dev/null +++ b/tools/devboxes/windows-2022/scripts/install-custom-root-ca.ps1 @@ -0,0 +1,3 @@ +if ($env:CUSTOM_ROOT_CA.length -gt 0) { + Import-Certificate -FilePath "E:\$env:CUSTOM_ROOT_CA" -CertStoreLocation Cert:\LocalMachine\Root +} diff --git a/tools/devboxes/windows-2022/scripts/install-git.ps1 b/tools/devboxes/windows-2022/scripts/install-git.ps1 new file mode 100644 index 00000000000..32db26f3341 --- /dev/null +++ b/tools/devboxes/windows-2022/scripts/install-git.ps1 @@ -0,0 +1,15 @@ +winget install -e --id Git.Git --version 2.47.1 --accept-source-agreements --accept-package-agreements + +$gitPath = "C:\Program Files\Git\bin" + +$systemEnvPath = [System.Environment]::GetEnvironmentVariable('PATH', [System.EnvironmentVariableTarget]::Machine) +$systemEnvPath = "$gitPath;$systemEnvPath" +[System.Environment]::SetEnvironmentVariable('PATH', $systemEnvPath, [System.EnvironmentVariableTarget]::Machine) + +Write-Output "Added $gitPath to the system PATH." + +$env:PATH = "$gitPath;$env:PATH" + +# Configure git to use the Windows Trust Store, to pick up any custom root CA we installed. +Write-Output "Configuring Git to use the Windows Trust Store (schannel)" +git config --global http.sslBackend schannel diff --git a/tools/devboxes/windows-2022/scripts/install-msys.ps1 b/tools/devboxes/windows-2022/scripts/install-msys.ps1 new file mode 100644 index 00000000000..52863d791c1 --- /dev/null +++ b/tools/devboxes/windows-2022/scripts/install-msys.ps1 @@ -0,0 +1,19 @@ +winget install -e --id MSYS2.MSYS2 --version 20241116 --accept-source-agreements --accept-package-agreements + +$msysPath = "C:\msys64\usr\bin" + +$systemEnvPath = [System.Environment]::GetEnvironmentVariable('PATH', [System.EnvironmentVariableTarget]::Machine) +$systemEnvPath = "$msysPath;$systemEnvPath" +[System.Environment]::SetEnvironmentVariable('PATH', $systemEnvPath, [System.EnvironmentVariableTarget]::Machine) + +Write-Output "Added $msysPath to the system PATH." + +$env:PATH = "$msysPath;$env:PATH" + +# MSYS has its own trust store, separate from Windows. +# https://www.msys2.org/docs/faq/ +if ($env:CUSTOM_ROOT_CA.length -gt 0) { + Write-Output "Adding custom root CA to MSYS /etc/pki/ca-trust/source/anchors/" + Copy-Item "E:\$env:CUSTOM_ROOT_CA" "C:\msys64\etc\pki\ca-trust\source\anchors\" + C:\msys64\usr\bin\bash.exe -c /usr/bin/update-ca-trust +} diff --git a/tools/devboxes/windows-2022/scripts/install-ssh.ps1 b/tools/devboxes/windows-2022/scripts/install-ssh.ps1 new file mode 100644 index 00000000000..ef90138a220 --- /dev/null +++ b/tools/devboxes/windows-2022/scripts/install-ssh.ps1 @@ -0,0 +1,4 @@ +# https://github.com/PowerShell/Win32-OpenSSH/wiki/Install-Win32-OpenSSH +winget install -e --id Microsoft.OpenSSH.Beta --version 9.5.0.0 --accept-source-agreements --accept-package-agreements + +New-NetFirewallRule -Name sshd -DisplayName 'OpenSSH Server (sshd)' -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22 diff --git a/tools/devboxes/windows-2022/scripts/install-winget-the-crappy-way.ps1 b/tools/devboxes/windows-2022/scripts/install-winget-the-crappy-way.ps1 new file mode 100644 index 00000000000..f47655dd93a --- /dev/null +++ b/tools/devboxes/windows-2022/scripts/install-winget-the-crappy-way.ps1 @@ -0,0 +1,37 @@ +# This script appears to be the only way to install Winget in such a way that Packer can run it over +# WinRM. Original source: https://github.com/microsoft/winget-cli/issues/256#issuecomment-1416929101 + +Write-Output "Extracting WinGet and dependencies..." + +New-Item -Path "$ENV:USERPROFILE" -Name "build" -ItemType "directory" -Force | Out-Null + +Expand-Archive -Path "E:/deps/Microsoft.VCLibs.x64.14.Desktop.zip" -DestinationPath "$ENV:USERPROFILE/build/WinGet" -Force + +Expand-Archive -Path "E:/deps/Microsoft.UI.Xaml.zip" -DestinationPath "$ENV:USERPROFILE/build/Microsoft.UI.Xaml" +Rename-Item -Path "$ENV:USERPROFILE/build/Microsoft.UI.Xaml/tools/AppX/x64/Release/Microsoft.UI.Xaml.2.7.appx" -NewName "Microsoft.UI.Xaml.2.7.zip" +Expand-Archive -Path "$ENV:USERPROFILE/build/Microsoft.UI.Xaml/tools/AppX/x64/Release/Microsoft.UI.Xaml.2.7.zip" -DestinationPath "$ENV:USERPROFILE/build/WinGet" -Force + +Expand-Archive -Path "E:/deps/Microsoft.DesktopAppInstaller.zip" -DestinationPath "$ENV:USERPROFILE/build/Microsoft.DesktopAppInstaller" +Rename-Item -Path "$ENV:USERPROFILE/build/Microsoft.DesktopAppInstaller/AppInstaller_x64.msix" -NewName "AppInstaller_x64.zip" +Expand-Archive -Path "$ENV:USERPROFILE/build/Microsoft.DesktopAppInstaller/AppInstaller_x64.zip" -DestinationPath "$ENV:USERPROFILE/build/WinGet" -Force + +Move-Item -Path "$ENV:USERPROFILE/build/WinGet" -Destination "$ENV:PROGRAMFILES" -PassThru +cmd /c setx /M PATH "%PATH%;$ENV:PROGRAMFILES\WinGet" + +# I originally tried to install Winget "the right way" ... +# +# The most robust way to install Winget seems to be use this script: +# https://github.com/asheroto/winget-install +# +# On Windows Server 2022, the above winget-install script boils down to the method described here: +# https://github.com/microsoft/winget-cli/issues/4390 +# +# But there are a couple catches: +# - The above methods use the Microsoft Store to install Winget for all users. Winget is not +# available for a user until the user has logged in interactively for the first time, at which +# point an asynchronous registration process starts, and Winget.exe eventually appears in the +# user's PATH. To start using it immediately, you can force registration with a PowerShell +# cmdlet: Add-AppxPackage -RegisterByFamilyName -MainPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +# See: https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget +# - Installed this way, Winget is not usable over WinRM: +# https://github.com/microsoft/winget-cli/issues/256 diff --git a/tools/devboxes/windows-2022/windows-2022.pkr.hcl b/tools/devboxes/windows-2022/windows-2022.pkr.hcl new file mode 100644 index 00000000000..d72db4849b3 --- /dev/null +++ b/tools/devboxes/windows-2022/windows-2022.pkr.hcl @@ -0,0 +1,123 @@ +# Windows Server 2022 Desktop provisioning template +# +# This template takes the output image of the `base/` template (relative to this directory), and +# configures a custom root CA and installs various tools useful for workerd development. + +packer { + required_plugins { + qemu = { + version = "~> 1.1.0" + source = "github.com/hashicorp/qemu" + } + } +} + +variables { + cpus = 2 + # Disk size must match the disk size in windows-2022-base.pkr.hcl, or else you will get a BSOD! + disk_size = 61440 + headless = false + memory = 8192 +} + +# The `custom_root_ca` variable allows you to add a custom trusted root CA to the Windows guest's +# trust store. +# +# If your organization uses a custom root CA to inspect traffic, such as can be done with +# [Cloudflare One](https://developers.cloudflare.com/cloudflare-one/connections/connect-devices/user-side-certificates/), +# you'll need to populate this variable with the path to the CA's PEM-encoded certificate. +# +# You must explicitly set this feature, because if you need a custom root CA, it's a pain if you +# accidentally build the image without one. +variable "custom_root_ca" { + type = string +} + +variable "deps_dir" { + description = "Path to deps/ with winfsp.msi, etc. inside" + type = string + default = "" + + validation { + condition = length(var.deps_dir) > 0 + error_message = "Run `fetch-deps.sh` to set this variable." + } +} + +variable "input_qcow2" { + type = string + default = "output-windows-2022-base/windows-2022-base.qcow2" +} + +variable "output_qcow2" { + type = string + default = "windows-2022.qcow2" +} + +locals { + # Read the SHA256 from the file produced by Packer's Checksum post-processor. + input_qcow2_sha256 = split("\t", file("${path.cwd}/${var.input_qcow2}.sha256"))[0] + output_directory = "output-windows-2022" +} + +source "qemu" "windows-2022" { + # Hardware + accelerator = "kvm" + headless = var.headless + cpus = var.cpus + memory = var.memory + disk_size = var.disk_size + + cd_files = flatten([ + # Optionally include a custom root CA. We do not check fileexists(), to guard against misspelled + # filenames. + length(var.custom_root_ca) > 0 ? [var.custom_root_ca] : [], + var.deps_dir + ]) + + # Input image + iso_url = var.input_qcow2 + iso_checksum = local.input_qcow2_sha256 + + # Output image + output_directory = local.output_directory + vm_name = var.output_qcow2 + format = "qcow2" + + # Use the input image as a backing file for the output image. + disk_image = true + use_backing_file = true + skip_resize_disk = true + skip_compaction = true + disk_compression = false + + # Give a little time for QEMU's VNC server to start. + boot_wait = "1s" + + communicator = "winrm" + winrm_username = "vagrant" + winrm_password = "vagrant" + # We expect the machine to boot reasonably quickly. + winrm_timeout = "10m" + + shutdown_command = "shutdown /s /t 10 /f /d p:4:1 /c \"Packer Shutdown\"" +} + +build { + sources = ["source.qemu.windows-2022"] + + provisioner "powershell" { + environment_vars = ["CUSTOM_ROOT_CA=${basename(var.custom_root_ca)}"] + scripts = [ + "${path.root}/scripts/install-custom-root-ca.ps1", + "${path.root}/scripts/fix-virtiofs.ps1", + "${path.root}/scripts/install-winget-the-crappy-way.ps1", + "${path.root}/scripts/install-ssh.ps1", + # Git and MSYS both come with Bash environments. Each install script prepends their /bin/ to + # the existing PATH, so earlier scripts' environments will be found later in the PATH. + "${path.root}/scripts/install-git.ps1", + "${path.root}/scripts/install-msys.ps1" + # TODO: Bazel custom root CA config + ] + } +}