Compare commits

..

51 Commits

Author SHA1 Message Date
e811c7c5d8 README: windows 11 2024-06-11 13:14:36 +08:00
8051ad647a utils.nix: pass all attrs
Fixes passing baseRtc and other parameters
2023-06-26 15:48:34 +02:00
ec1c08956b default.nix: remove superfluous parentheses 2023-06-26 15:48:18 +02:00
7e09796a9b Revert "remove configurable efi flag"
This reverts commit af9218e652.
2023-06-08 18:42:48 +02:00
4dcd3699fe add 'nix run' demo 2023-05-27 15:36:00 +08:00
bf681b20aa remove MSVC from demo image
Usually broken due to Microsoft's war on reproducibility.
2023-05-27 15:15:51 +08:00
3cbddd7218 update nixpkgs 2023-05-27 15:15:33 +08:00
3694b0a9f2 windowsIso: 22H2v1 -> 22H2v2 2023-05-26 00:13:21 +02:00
b9e261de6f layers msvc cache: update outputHash 2023-05-25 15:59:36 +02:00
285b33a674 autounattend: fix installation without productKey 2023-05-25 15:59:36 +02:00
9a92143337 autounattend: disable forced password expiry in autounattended install stage already 2023-05-25 15:59:36 +02:00
16e041282f add enableTpm 2023-05-25 15:59:36 +02:00
bc24fd6a2b autounattend: remove more optional bypasses 2023-05-25 15:59:36 +02:00
af9218e652 remove configurable efi flag
Win11 is EFI-only
2023-05-25 15:59:36 +02:00
fe347240f5 enable secureboot 2023-05-25 15:59:36 +02:00
d2d9c7acf6 layers.msvc: update bootstapper sha256 2023-05-25 15:59:36 +02:00
267b3eec44 autounattend: explicitly set the InstallFrom/Path
Apparently required for Win11.
2023-05-25 15:59:36 +02:00
1550caf442 autounattend: add Bypass*Check
At least the TPM2.0 is missing from our qemu run but Windows 11's error
message doesn't reveal what else is missing. It is therefore hard to
estimate the work to do this properly.
2023-05-25 15:59:36 +02:00
598b311215 windowsIso: Win10_21H2 -> Win11_22H2 2023-05-25 15:59:36 +02:00
79c1685f89 virtioWinIso: 0.1.185-2 -> 0.1.229-1 2023-05-23 21:48:23 +02:00
a3df68b61f README: add flakes section 2022-10-13 21:41:23 +02:00
6542c37863 wfvm/demo-image: make buildable from flake 2022-10-13 21:41:03 +02:00
abd67ce2e6 wfvm/layers: update msvc binary sha256 2022-10-13 21:39:56 +02:00
a6b677f564 wfvm/win: upgrade windowsIso from 21H1 to 21H2 2022-10-13 21:39:33 +02:00
d8e9f9878b wfvm/utils: update baseRtc 2022-10-12 21:29:11 +02:00
5ba57fb502 wfvm/win: pass format of backing image to qemu-img
required since qemu 6.1.0
2022-10-12 21:05:39 +02:00
dfdcf0f19a flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:nixos/nixpkgs/c5924154f000e6306030300592f4282949b2db6c' (2022-10-08)
  → 'github:nixos/nixpkgs/285e77efe87df64105ec14b204de6636fb0a7a27' (2022-10-11)
2022-10-12 19:05:56 +02:00
9845a99863 flake.nix: switch input back to nixos-unstable now that fix PRs have landed 2022-10-12 19:04:51 +02:00
4f7aef8788 wfvm/win: replace libguestfs with guestfs-tools
tools like virt-make-fs have been split off into a separate package.
2022-10-11 00:10:58 +02:00
c124cfc5dc flakify 2022-10-11 00:10:32 +02:00
3b87f787a8 s/runCommandNoCC/runCommand/ 2022-10-05 16:46:27 +02:00
b33b71eec7 wfvm/win: lower wimsplit size to 4070 MB for more headroom 2022-10-05 16:07:06 +02:00
c732c671d9 wfvm/layers: update vs_Community.exe sha256 2022-10-05 16:06:43 +02:00
9d07da799c Merge remote-tracking branch 'origin/master' 2021-12-17 20:33:52 +01:00
a4fe5f0475 update visual studio garbage 2021-11-14 20:15:26 +08:00
c7d9060eee vs: update 2021-09-09 20:48:48 +08:00
50471a28f8 Merge upstream 2021-09-09 00:46:43 +02:00
db995f7d77 layers: add disable-scheduled-defrag 2021-09-09 00:46:01 +02:00
93796f7a71 vs: update output hash
This is only a temporary solution - the source of the non determinism
should be found and fixed.
2021-09-06 09:14:20 +08:00
6d9d9d91f6 update Anaconda 2021-07-05 10:54:24 +08:00
680d70094f update windowsImage to Windows 10-21H1 2021-06-30 23:55:49 +02:00
a84d2d8d90 utils: replace qemu_test with qemu
qemu_test lacks support for QXL VGA
2021-06-30 23:07:40 +02:00
520898c1db bundle: fix for newer go version 2021-06-30 21:36:25 +02:00
11a40de18a utils: fail on sftp errors 2021-06-19 01:15:29 +02:00
110fe11f00 install openssh from github
this removes the need for the windows version's OpenSSH.Server
feature-on-demand package which is not publicly available.

fixes gitea issue #6 <M-Labs/wfvm#6>
2021-06-16 15:13:12 +02:00
07813c3c4f remove wimsplit, do imageSelection by name 2021-06-05 20:43:55 +02:00
54d9f41a6d autounattend: use ForceShutdownNow instead of FirstLogonCommands shutdown
this moves the return a little earlier in the installation process.
it is less of a hack, and less problematic with custom `defaultUser`
settings.
2021-04-18 00:55:01 +02:00
ec54c9bf9b install and use virtio drivers
(virtio disk, qxl vga, virtio net)

fixes Gitea issue #7
2021-04-11 10:03:47 +08:00
4771cee64b layers: add collapseLayers 2021-04-10 23:25:29 +02:00
78c9363b64 demo-image: use empty installCommands for impureMode 2021-04-10 23:25:29 +02:00
9f8a1b6e17 layers: add disable-firewall, disable-autosleep, disable-autolock 2021-04-10 23:25:29 +02:00
14 changed files with 353 additions and 93 deletions

View File

@ -6,7 +6,7 @@ WFVM
A Nix library to create and manage virtual machines running Windows, a medieval operating system found on most computers in 2020. The F stands for "Functional" or a four-letter word of your choice. A Nix library to create and manage virtual machines running Windows, a medieval operating system found on most computers in 2020. The F stands for "Functional" or a four-letter word of your choice.
* Reproducible - everything runs in the Nix sandbox with no tricks. * Reproducible - everything runs in the Nix sandbox with no tricks.
* Fully automatic, parameterizable Windows 10 installation. * Fully automatic, parameterizable Windows 11 installation.
* Uses QEMU with KVM. * Uses QEMU with KVM.
* Supports incremental installation (using "layers") of additional software via QEMU copy-on-write backing chains. For example, ``wfvm.makeWindowsImage { installCommands = [ wfvm.layers.anaconda3 ]; };`` gives you a VM image with Anaconda3 installed, and ``wfvm.makeWindowsImage { installCommands = [ wfvm.layers.anaconda3 wfvm.layers.msys2 ]; };`` gives you one with both Anaconda3 and MSYS2 installed. The base Windows installation and the Anaconda3 data are shared between both images, and only the MSYS2 installation is performed when building the second image after the first one has been built. * Supports incremental installation (using "layers") of additional software via QEMU copy-on-write backing chains. For example, ``wfvm.makeWindowsImage { installCommands = [ wfvm.layers.anaconda3 ]; };`` gives you a VM image with Anaconda3 installed, and ``wfvm.makeWindowsImage { installCommands = [ wfvm.layers.anaconda3 wfvm.layers.msys2 ]; };`` gives you one with both Anaconda3 and MSYS2 installed. The base Windows installation and the Anaconda3 data are shared between both images, and only the MSYS2 installation is performed when building the second image after the first one has been built.
* Included layers: Anaconda3, a software installer chock full of bugs that pretends to be a package manager, Visual Studio, a spamming system for Microsoft accounts that includes a compiler, and MSYS2, which is the only sane component in the whole lot. * Included layers: Anaconda3, a software installer chock full of bugs that pretends to be a package manager, Visual Studio, a spamming system for Microsoft accounts that includes a compiler, and MSYS2, which is the only sane component in the whole lot.
@ -52,3 +52,30 @@ Impure/pure mode
Sometimes it can be useful to build the image _outside_ of the Nix sandbox for debugging purposes. Sometimes it can be useful to build the image _outside_ of the Nix sandbox for debugging purposes.
For this purpose we have an attribute called `impureMode` which outputs the shell script used by Nix inside the sandbox to build the image. For this purpose we have an attribute called `impureMode` which outputs the shell script used by Nix inside the sandbox to build the image.
Usage with Nix Flakes
---------------------
Build the demo by running:
```shell
nix build .#demoImage
```
This project's **flake.nix** exposes its functions under `lib`. To use
in your own project, setup your flake like this:
```nix
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
wfvm.url = "git+https://git.m-labs.hk/m-labs/wfvm";
};
outputs = { self, nixpkgs, wfvm }: {
packages."x86_64-linux".flaky-os = wfvm.lib.makeWindowsImage {
# configuration parameters go here
};
};
}
```

27
flake.lock generated Normal file
View File

@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1685004253,
"narHash": "sha256-AbVL1nN/TDicUQ5wXZ8xdLERxz/eJr7+o8lqkIOVuaE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "3e01645c40b92d29f3ae76344a6d654986a91a91",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.05",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

37
flake.nix Normal file
View File

@ -0,0 +1,37 @@
{
description = "WFVM: Windows Functional Virtual Machine";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
};
outputs = { self, nixpkgs }:
let
# only x64 is supported
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in rec {
lib = import ./wfvm {
inherit pkgs;
};
packages.${system} = rec {
demoImage = import ./wfvm/demo-image.nix {
inherit self;
};
default = lib.utils.wfvm-run {
name = "demo";
image = demoImage;
script =
''
echo "Windows booted. Press Enter to terminate VM."
read
'';
display = true;
};
};
};
}

View File

@ -15,6 +15,8 @@
, impureShellCommands ? [] , impureShellCommands ? []
, driveLetter ? "D:" , driveLetter ? "D:"
, efi ? true , efi ? true
, imageSelection ? "Windows 11 Pro N"
, enableTpm
, ... , ...
}: }:
@ -49,34 +51,28 @@ let
# mkDirsDesc ++ writeKeysDesc ++ # mkDirsDesc ++ writeKeysDesc ++
[ [
{ {
Path = ''powershell.exe Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 -Source ${driveLetter}\fod -LimitAccess''; Path = ''powershell.exe ${driveLetter}\install-ssh.ps1'';
Description = "Add OpenSSH service."; Description = "Install OpenSSH service.";
}
{
Path = ''powershell.exe Set-Service -Name sshd -StartupType Automatic'';
Description = "Enable SSH by default.";
} }
]; ];
assertCommand = c: builtins.typeOf c == "string" || builtins.typeOf c == "set" && builtins.hasAttr "Path" c && builtins.hasAttr "Description" c; assertCommand = c: builtins.typeOf c == "string" || builtins.typeOf c == "set" && builtins.hasAttr "Path" c && builtins.hasAttr "Description" c;
commands = builtins.map (x: assert assertCommand x; if builtins.typeOf x == "string" then { Path = x; Description = x; } else x) ( commands = builtins.map (x: assert assertCommand x; if builtins.typeOf x == "string" then { Path = x; Description = x; } else x) (
[ [ {
{
Path = "powershell.exe Set-ExecutionPolicy -Force Unrestricted"; Path = "powershell.exe Set-ExecutionPolicy -Force Unrestricted";
Description = "Allow unsigned powershell scripts."; Description = "Allow unsigned powershell scripts.";
} } {
]
++ [
{
Path = ''powershell.exe ${driveLetter}\win-bundle-installer.exe''; Path = ''powershell.exe ${driveLetter}\win-bundle-installer.exe'';
Description = "Install any declared packages."; Description = "Install any declared packages.";
} } {
] Path = "net accounts /maxpwage:unlimited";
Description = "Disable forced password expiry.";
} ]
++ setupCommands ++ setupCommands
++ [ ++ [
{ {
Path = ''powershell.exe ${driveLetter}\ssh-setup.ps1''; Path = ''powershell.exe ${driveLetter}\setup.ps1'';
Description = "Setup SSH and keys"; Description = "Setup SSH and keys";
} }
] ]
@ -124,6 +120,9 @@ let
# Windows expects a flat list of users while we want to manage them as a set # Windows expects a flat list of users while we want to manage them as a set
flatUsers = builtins.attrValues (builtins.mapAttrs (name: s: s // { inherit name; }) users); flatUsers = builtins.attrValues (builtins.mapAttrs (name: s: s // { inherit name; }) users);
diskId =
if efi then 2 else 1;
autounattendXML = pkgs.writeText "autounattend.xml" '' autounattendXML = pkgs.writeText "autounattend.xml" ''
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend"> <unattend xmlns="urn:schemas-microsoft-com:unattend">
@ -136,9 +135,26 @@ let
<PathAndCredentials wcm:action="add" wcm:keyValue="2"> <PathAndCredentials wcm:action="add" wcm:keyValue="2">
<Path>E:\</Path> <Path>E:\</Path>
</PathAndCredentials> </PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="3">
<Path>C:\virtio\amd64\w10</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="4">
<Path>C:\virtio\NetKVM\w10\amd64</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="5">
<Path>C:\virtio\qxldod\w10\amd64</Path>
</PathAndCredentials>
</DriverPaths> </DriverPaths>
</component> </component>
<component name="Microsoft-Windows-Setup" 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"> <component name="Microsoft-Windows-Setup" 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">
${lib.optionalString (!enableTpm) ''
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Order>1</Order>
<Path>reg add HKLM\System\Setup\LabConfig /v BypassTPMCheck /t reg_dword /d 0x00000001 /f</Path>
</RunSynchronousCommand>
</RunSynchronous>
''}
<DiskConfiguration> <DiskConfiguration>
<Disk wcm:action="add"> <Disk wcm:action="add">
@ -178,7 +194,7 @@ let
<PartitionID>3</PartitionID> <PartitionID>3</PartitionID>
</ModifyPartition> </ModifyPartition>
</ModifyPartitions> </ModifyPartitions>
<DiskID>0</DiskID> <DiskID>${toString diskId}</DiskID>
<WillWipeDisk>true</WillWipeDisk> <WillWipeDisk>true</WillWipeDisk>
</Disk> </Disk>
</DiskConfiguration> </DiskConfiguration>
@ -186,13 +202,14 @@ let
<ImageInstall> <ImageInstall>
<OSImage> <OSImage>
<InstallTo> <InstallTo>
<DiskID>0</DiskID> <DiskID>${toString diskId}</DiskID>
<PartitionID>3</PartitionID> <PartitionID>3</PartitionID>
</InstallTo> </InstallTo>
<InstallFrom> <InstallFrom>
<Path>\install.swm</Path>
<MetaData wcm:action="add"> <MetaData wcm:action="add">
<Key>/IMAGE/INDEX</Key> <Key>/IMAGE/NAME</Key>
<Value>1</Value> <Value>${imageSelection}</Value>
</MetaData> </MetaData>
</InstallFrom> </InstallFrom>
</OSImage> </OSImage>
@ -200,7 +217,7 @@ let
<UserData> <UserData>
<ProductKey> <ProductKey>
${if productKey != null then "<Key>${productKey}</Key>" else ""} ${if productKey != null then "<Key>${productKey}</Key>" else "<Key/>"}
<WillShowUI>OnError</WillShowUI> <WillShowUI>OnError</WillShowUI>
</ProductKey> </ProductKey>
<AcceptEula>true</AcceptEula> <AcceptEula>true</AcceptEula>
@ -263,14 +280,12 @@ let
</AutoLogon> </AutoLogon>
''} ''}
<FirstLogonCommands> </component>
<SynchronousCommand wcm:action="add"> <component name="Microsoft-Windows-Deployment" 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">
<Order>1</Order> <Reseal>
<CommandLine>cmd /C shutdown /s /f /t 00</CommandLine> <ForceShutdownNow>true</ForceShutdownNow>
<Description>ChangeHideFiles</Description> <Mode>OOBE</Mode>
</SynchronousCommand> </Reseal>
</FirstLogonCommands>
</component> </component>
</settings> </settings>
@ -292,18 +307,18 @@ let
</component> </component>
</settings> </settings>
<cpi:offlineImage cpi:source="wim:c:/wim/windows-10/install.wim#Windows 10 Enterprise LTSC 2019 Evaluation" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> <cpi:offlineImage cpi:source="wim:c:/wim/windows-11/install.wim#${imageSelection}" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
</unattend> </unattend>
''; '';
in { in {
# Lint and format as a sanity check # Lint and format as a sanity check
autounattendXML = pkgs.runCommandNoCC "autounattend.xml" {} '' autounattendXML = pkgs.runCommand "autounattend.xml" {} ''
${pkgs.libxml2}/bin/xmllint --format ${autounattendXML} > $out ${pkgs.libxml2}/bin/xmllint --format ${autounattendXML} > $out
''; '';
# autounattend.xml is _super_ picky about quotes and other things # autounattend.xml is _super_ picky about quotes and other things
setupScript = pkgs.writeText "ssh-setup.ps1" ( setupScript = pkgs.writeText "setup.ps1" (
'' ''
# Setup SSH and keys # Setup SSH and keys
'' + '' +

View File

@ -1,7 +1,10 @@
{ pkgs }: { pkgs }:
pkgs.runCommandNoCC "win-bundle-installer.exe" {} '' pkgs.runCommand "win-bundle-installer.exe" {} ''
mkdir bundle
cd bundle
cp ${./go.mod} go.mod
cp ${./main.go} main.go cp ${./main.go} main.go
env HOME=$(mktemp -d) GOOS=windows GOARCH=amd64 ${pkgs.go}/bin/go build env HOME=$(mktemp -d) GOOS=windows GOARCH=amd64 ${pkgs.go}/bin/go build
mv build.exe $out mv bundle.exe $out
'' ''

3
wfvm/bundle/go.mod Normal file
View File

@ -0,0 +1,3 @@
module bundle
go 1.11

View File

@ -2,6 +2,6 @@
{ {
makeWindowsImage = attrs: import ./win.nix ({ inherit pkgs; } // attrs); makeWindowsImage = attrs: import ./win.nix ({ inherit pkgs; } // attrs);
layers = (import ./layers { inherit pkgs; }); layers = import ./layers { inherit pkgs; };
utils = (import ./utils.nix { inherit pkgs; }); utils = import ./utils.nix { inherit pkgs; };
} }

View File

@ -1,16 +1,27 @@
{ pkgs ? import <nixpkgs> {}, impureMode ? false }: { pkgs ? import <nixpkgs> {}
# Whether to generate just a script to start and debug the windows installation
, impureMode ? false
# Flake input `self`
, self ? null
}:
let let
wfvm = (import ./default.nix { inherit pkgs; }); wfvm =
if self == null
# nix-build
then (import ./default.nix { inherit pkgs; })
# built from flake.nix
else self.lib;
in in
wfvm.makeWindowsImage { wfvm.makeWindowsImage {
# Build install script & skip building iso # Build install script & skip building iso
inherit impureMode; inherit impureMode;
# Custom base iso # Custom base iso
# windowsImage = pkgs.fetchurl { # windowsImage = pkgs.requireFile rec {
# url = "https://software-download.microsoft.com/download/sg/17763.107.101029-1455.rs5_release_svc_refresh_CLIENT_LTSC_EVAL_x64FRE_en-us.iso"; # name = "Win11_22H2_English_x64v1.iso";
# sha256 = "668fe1af70c2f7416328aee3a0bb066b12dc6bbd2576f40f812b95741e18bc3a"; # sha256 = "08mbppsm1naf73z8fjyqkf975nbls7xj9n4fq0yp802dv1rz3whd";
# message = "Get disk image ${name} from https://www.microsoft.com/en-us/software-download/windows11/";
# }; # };
# impureShellCommands = [ # impureShellCommands = [
@ -37,7 +48,17 @@ wfvm.makeWindowsImage {
# administratorPassword = "12345"; # administratorPassword = "12345";
# Imperative installation commands, to be installed incrementally # Imperative installation commands, to be installed incrementally
installCommands = with wfvm.layers; [ anaconda3 msys2 msvc msvc-ide-unbreak ]; installCommands =
if impureMode
then []
else with wfvm.layers; [
(collapseLayers [
disable-autosleep
disable-autolock
disable-firewall
])
anaconda3 msys2
];
# services = { # services = {
# # Enable remote management # # Enable remote management
@ -47,8 +68,10 @@ wfvm.makeWindowsImage {
# }; # };
# }; # };
# License key # License key (required)
# productKey = "iboughtthisone"; # productKey = throw "Search the f* web"
imageSelection = "Windows 11 Pro N";
# Locales # Locales
# uiLanguage = "en-US"; # uiLanguage = "en-US";

42
wfvm/install-ssh.ps1 Normal file
View File

@ -0,0 +1,42 @@
Write-Host "Expanding OpenSSH"
Expand-Archive D:\OpenSSH-Win64.zip C:\
Push-Location C:\OpenSSH-Win64
Write-Host "Installing OpenSSH"
& .\install-sshd.ps1
Write-Host "Generating host keys"
.\ssh-keygen.exe -A
Write-Host "Fixing host file permissions"
& .\FixHostFilePermissions.ps1 -Confirm:$false
Write-Host "Fixing user file permissions"
& .\FixUserFilePermissions.ps1 -Confirm:$false
Pop-Location
$newPath = 'C:\OpenSSH-Win64;' + [Environment]::GetEnvironmentVariable("PATH", [EnvironmentVariableTarget]::Machine)
[Environment]::SetEnvironmentVariable("PATH", $newPath, [EnvironmentVariableTarget]::Machine)
#Write-Host "Adding public key to authorized_keys"
#$keyPath = "~\.ssh\authorized_keys"
#New-Item -Type Directory ~\.ssh > $null
#$sshKey | Out-File $keyPath -Encoding Ascii
Write-Host "Opening firewall port 22"
New-NetFirewallRule -Protocol TCP -LocalPort 22 -Direction Inbound -Action Allow -DisplayName SSH
Write-Host "Setting sshd service startup type to 'Automatic'"
Set-Service sshd -StartupType Automatic
Set-Service ssh-agent -StartupType Automatic
Write-Host "Setting sshd service restart behavior"
sc.exe failure sshd reset= 86400 actions= restart/500
#Write-Host "Configuring sshd"
#(Get-Content C:\ProgramData\ssh\sshd_config).replace('#TCPKeepAlive yes', 'TCPKeepAlive yes').replace('#ClientAliveInterval 0', 'ClientAliveInterval 300').replace('#ClientAliveCountMax 3', 'ClientAliveCountMax 3') | Set-Content C:\ProgramData\ssh\sshd_config
Write-Host "Starting sshd service"
Start-Service sshd
Start-Service ssh-agent

View File

@ -8,8 +8,8 @@ in
script = let script = let
Anaconda3 = pkgs.fetchurl { Anaconda3 = pkgs.fetchurl {
name = "Anaconda3.exe"; name = "Anaconda3.exe";
url = "https://repo.anaconda.com/archive/Anaconda3-2020.02-Windows-x86_64.exe"; url = "https://repo.anaconda.com/archive/Anaconda3-2021.05-Windows-x86_64.exe";
sha256 = "0n31l8l89jrjrbzbifxbjnr3g320ly9i4zfyqbf3l9blf4ygbhl3"; sha256 = "1lpk7k4gydyk524z1nk4rrninrwi20g2ias2njc9w0a40hwl5nwk";
}; };
in in
'' ''
@ -71,8 +71,8 @@ in
script = let script = let
bootstrapper = pkgs.fetchurl { bootstrapper = pkgs.fetchurl {
name = "RESTRICTDIST-vs_Community.exe"; name = "RESTRICTDIST-vs_Community.exe";
url = "https://download.visualstudio.microsoft.com/download/pr/ac05c4f5-0da1-429f-8701-ce509ac69926/cc9556137c66a373670376d6db2fc5c5c937b2b0bf7b3d3cac11c69e33615511/vs_Community.exe"; url = "https://aka.ms/vs/16/release/vs_community.exe";
sha256 = "04amc4rrxihimhy3syxzn2r3gjf5qlpxpmkn0dkp78v6gh9md5fc"; sha256 = "sha256-l4ZKFZTgHf3BmD0eFWyGwsvb4lqB/LiQYizAABOs3gg=";
}; };
# This touchy-feely "community" piece of trash seems deliberately crafted to break Wine, so we use the VM to run it. # This touchy-feely "community" piece of trash seems deliberately crafted to break Wine, so we use the VM to run it.
download-vs = wfvm.utils.wfvm-run { download-vs = wfvm.utils.wfvm-run {
@ -93,7 +93,7 @@ in
outputHashAlgo = "sha256"; outputHashAlgo = "sha256";
outputHashMode = "recursive"; outputHashMode = "recursive";
outputHash = "0fp7a6prjp8n8sirwday13wis3xyzhmrwi377y3x89nxzysp0mnv"; outputHash = "sha256-GoOKzln8DXVMx52jWGEjwkOFkpSW+wEffAVmBVugIyk=";
phases = [ "buildPhase" ]; phases = [ "buildPhase" ];
buildInputs = [ download-vs ]; buildInputs = [ download-vs ];
@ -118,8 +118,61 @@ in
name = "MSVC-ide-unbreak"; name = "MSVC-ide-unbreak";
script = script =
'' ''
win-exec 'cd "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE" && devenv /ResetSettings' win-exec 'cd "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE" && devenv /ResetSettings'
sleep 40 sleep 40
''; '';
}; };
# Disable the Windows firewall
disable-firewall = {
name = "disable-firewall";
script = ''
echo Disabling firewall
win-exec "netsh advfirewall set allprofiles state off"
'';
};
# Disable automatic power management which causes the machine to go
# into standby after periods without mouse wiggling.
disable-autosleep = {
name = "disable-autosleep";
script = ''
echo Disabling autosleep
win-exec "powercfg /x -hibernate-timeout-ac 0"
win-exec "powercfg /x -hibernate-timeout-dc 0"
win-exec "powercfg /x -disk-timeout-ac 0"
win-exec "powercfg /x -disk-timeout-dc 0"
win-exec "powercfg /x -monitor-timeout-ac 0"
win-exec "powercfg /x -monitor-timeout-dc 0"
win-exec "powercfg /x -standby-timeout-ac 0"
win-exec "powercfg /x -standby-timeout-dc 0"
'';
};
# Turn off automatic locking of idle user sessions
disable-autolock = {
name = "disable-autolock";
script = ''
echo Disabling autolock
win-exec "reg add HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\Personalization /v NoLockScreen /t REG_DWORD /d 1"
'';
};
# Don't let Windows start completely rewriting gigabytes of disk
# space. Defragmentation increases the size of our qcow layers
# needlessly.
disable-scheduled-defrag = {
name = "disable-scheduled-defrag";
script = ''
echo Disabling scheduled defragmentation service
win-exec 'schtasks /Change /DISABLE /TN "\Microsoft\Windows\Defrag\ScheduledDefrag"'
'';
};
# Chain together layers that are quick to run so that the VM does
# not have to be started/shutdown for each.
collapseLayers = scripts: {
name = pkgs.lib.concatMapStringsSep "-" ({ name, ... }: name) scripts;
script = builtins.concatStringsSep "\n" (
map ({ script, ... }: script) scripts
);
buildInputs =
builtins.concatMap ({ buildInputs ? [], ... }: buildInputs) scripts;
};
} }

View File

@ -1 +0,0 @@
This file is not publicaly acessible anywhere so had to be extracted from a connected instance

Binary file not shown.

View File

@ -1,22 +1,45 @@
{ pkgs, baseRtc ? "2020-04-20T14:21:42", cores ? "4", qemuMem ? "4G", efi ? true }: { pkgs
, baseRtc ? "2022-10-10T10:10:10"
, cores ? "4"
, qemuMem ? "4G"
, efi ? true
, enableTpm ? false
, ...
}:
rec { rec {
# qemu_test is a smaller closure only building for a single system arch # qemu_test is a smaller closure only building for a single system arch
qemu = pkgs.qemu_test; qemu = pkgs.qemu;
OVMF = pkgs.OVMF.override {
secureBoot = true;
};
mkQemuFlags = extraFlags: [ mkQemuFlags = extraFlags: [
"-enable-kvm" "-enable-kvm"
"-cpu host" "-cpu host"
"-smp ${cores}" "-smp ${cores}"
"-m ${qemuMem}" "-m ${qemuMem}"
"-M q35" "-M q35,smm=on"
"-vga qxl"
"-rtc base=${baseRtc}" "-rtc base=${baseRtc}"
"-device qemu-xhci" "-device qemu-xhci"
"-device e1000,netdev=n1" "-device virtio-net-pci,netdev=n1"
] ++ pkgs.lib.optionals efi [ ] ++ pkgs.lib.optionals efi [
"-bios ${pkgs.OVMF.fd}/FV/OVMF.fd" "-bios ${OVMF.fd}/FV/OVMF.fd"
] ++ pkgs.lib.optionals enableTpm [
"-chardev" "socket,id=chrtpm,path=tpm.sock"
"-tpmdev" "emulator,id=tpm0,chardev=chrtpm"
"-device" "tpm-tis,tpmdev=tpm0"
] ++ extraFlags; ] ++ extraFlags;
tpmStartCommands = pkgs.lib.optionalString enableTpm ''
mkdir -p tpmstate
${pkgs.swtpm}/bin/swtpm socket \
--tpmstate dir=tpmstate \
--ctrl type=unixio,path=tpm.sock &
'';
# Pass empty config file to prevent ssh from failing to create ~/.ssh # Pass empty config file to prevent ssh from failing to create ~/.ssh
sshOpts = "-F /dev/null -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=1"; sshOpts = "-F /dev/null -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=1";
win-exec = pkgs.writeShellScriptBin "win-exec" '' win-exec = pkgs.writeShellScriptBin "win-exec" ''
@ -59,8 +82,10 @@ rec {
echo win-put $1 -\> $2 echo win-put $1 -\> $2
${pkgs.sshpass}/bin/sshpass -p1234 -- \ ${pkgs.sshpass}/bin/sshpass -p1234 -- \
${pkgs.openssh}/bin/sftp -r -P 2022 ${sshOpts} \ ${pkgs.openssh}/bin/sftp -r -P 2022 ${sshOpts} \
wfvm@localhost <<< "cd $2 wfvm@localhost -b- << EOF
put $1" cd $2
put $1
EOF
''; '';
win-get = pkgs.writeShellScriptBin "win-get" '' win-get = pkgs.writeShellScriptBin "win-get" ''
set -e set -e
@ -90,6 +115,7 @@ rec {
]); ]);
in pkgs.writeShellScriptBin "wfvm-run-${name}" '' in pkgs.writeShellScriptBin "wfvm-run-${name}" ''
set -e -m set -e -m
${tpmStartCommands}
${qemu}/bin/qemu-system-x86_64 ${pkgs.lib.concatStringsSep " " qemuParams} & ${qemu}/bin/qemu-system-x86_64 ${pkgs.lib.concatStringsSep " " qemuParams} &
${win-wait}/bin/win-wait ${win-wait}/bin/win-wait

View File

@ -5,16 +5,17 @@
, impureMode ? false , impureMode ? false
, installCommands ? [] , installCommands ? []
, users ? {} , users ? {}
, enableTpm ? true
# autounattend always installs index 1, so this default is backward-compatible # autounattend always installs index 1, so this default is backward-compatible
, imageSelection ? "1" , imageSelection ? "Windows 11 Pro N"
, efi ? true , efi ? true
, ... , ...
}@attrs: }@attrs:
let let
lib = pkgs.lib; lib = pkgs.lib;
utils = import ./utils.nix { inherit pkgs efi; }; utils = import ./utils.nix ({ inherit pkgs efi enableTpm; } // attrs);
libguestfs = pkgs.libguestfs-with-appliance; inherit (pkgs) guestfs-tools;
# p7zip on >20.03 has known vulns but we have no better option # p7zip on >20.03 has known vulns but we have no better option
p7zip = pkgs.p7zip.overrideAttrs(old: { p7zip = pkgs.p7zip.overrideAttrs(old: {
@ -24,7 +25,7 @@ let
}); });
runQemuCommand = name: command: ( runQemuCommand = name: command: (
pkgs.runCommandNoCC name { buildInputs = [ p7zip utils.qemu libguestfs ]; } pkgs.runCommand name { buildInputs = [ p7zip utils.qemu guestfs-tools ]; }
( (
'' ''
if ! test -f; then if ! test -f; then
@ -35,17 +36,25 @@ let
) )
); );
windowsIso = if windowsImage != null then windowsImage else pkgs.fetchurl { windowsIso = if windowsImage != null then windowsImage else pkgs.requireFile rec {
name = "RESTRICTDIST-release_svc_refresh_CLIENT_LTSC_EVAL_x64FRE_en-us.iso"; name = "Win11_22H2_English_x64v2.iso";
url = "https://software-download.microsoft.com/download/sg/17763.107.101029-1455.rs5_release_svc_refresh_CLIENT_LTSC_EVAL_x64FRE_en-us.iso"; sha256 = "0xhhxy47yaf1jsfmskym5f65hljw8q0aqs70my86m402i6dsjnc0";
sha256 = "668fe1af70c2f7416328aee3a0bb066b12dc6bbd2576f40f812b95741e18bc3a"; message = "Get disk image ${name} from https://www.microsoft.com/en-us/software-download/windows11/";
}; };
openSshServerPackage = ./openssh/server-package.cab; virtioWinIso = pkgs.fetchurl {
url = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.229-1/virtio-win.iso";
sha256 = "1q5vrcd70kya4nhlbpxmj7mwmwra1hm3x7w8rzkawpk06kg0v2n8";
};
openSshServerPackage = pkgs.fetchurl {
url = "https://github.com/PowerShell/Win32-OpenSSH/releases/download/V8.6.0.0p1-Beta/OpenSSH-Win64.zip";
sha256 = "1dw6n054r0939501dpxfm7ghv21ihmypdx034van8cl21gf1b4lz";
};
autounattend = import ./autounattend.nix ( autounattend = import ./autounattend.nix (
attrs // { attrs // {
inherit pkgs; inherit pkgs enableTpm;
users = users // { users = users // {
wfvm = { wfvm = {
password = "1234"; password = "1234";
@ -63,15 +72,16 @@ let
# Packages required to drive installation of other packages # Packages required to drive installation of other packages
bootstrapPkgs = bootstrapPkgs =
runQemuCommand "bootstrap-win-pkgs.img" '' runQemuCommand "bootstrap-win-pkgs.img" ''
mkdir -p pkgs/fod 7z x -y ${virtioWinIso} -opkgs/virtio
cp ${bundleInstaller} pkgs/"$(stripHash "${bundleInstaller}")" cp ${bundleInstaller} pkgs/"$(stripHash "${bundleInstaller}")"
# Install optional windows features # Install optional windows features
cp ${openSshServerPackage} pkgs/fod/OpenSSH-Server-Package~31bf3856ad364e35~amd64~~.cab cp ${openSshServerPackage} pkgs/OpenSSH-Win64.zip
# SSH setup script goes here because windows XML parser sucks # SSH setup script goes here because windows XML parser sucks
cp ${autounattend.setupScript} pkgs/ssh-setup.ps1 cp ${./install-ssh.ps1} pkgs/install-ssh.ps1
cp ${autounattend.setupScript} pkgs/setup.ps1
virt-make-fs --partition --type=fat pkgs/ $out virt-make-fs --partition --type=fat pkgs/ $out
''; '';
@ -91,7 +101,7 @@ let
"usb-storage,drive=win-install" "usb-storage,drive=win-install"
# Output image # Output image
"-drive" "-drive"
"file=c.img,index=0,media=disk,cache=unsafe" "file=c.img,index=0,media=disk,if=virtio,cache=unsafe"
# Network # Network
"-netdev user,id=n1,net=192.168.1.0/24,restrict=on" "-netdev user,id=n1,net=192.168.1.0/24,restrict=on"
]); ]);
@ -99,7 +109,7 @@ let
'' ''
#!${pkgs.runtimeShell} #!${pkgs.runtimeShell}
set -euxo pipefail set -euxo pipefail
export PATH=${lib.makeBinPath [ p7zip utils.qemu libguestfs pkgs.wimlib ]}:$PATH export PATH=${lib.makeBinPath [ p7zip utils.qemu guestfs-tools pkgs.wimlib ]}:$PATH
# Create a bootable "USB" image # Create a bootable "USB" image
# Booting in USB mode circumvents the "press any key to boot from cdrom" prompt # Booting in USB mode circumvents the "press any key to boot from cdrom" prompt
@ -109,19 +119,9 @@ let
mkdir -p win/nix-win mkdir -p win/nix-win
7z x -y ${windowsIso} -owin 7z x -y ${windowsIso} -owin
# Extract desired variant from install.wim
# This is useful if the install.wim contains multiple Windows
# versions (e.g., Home, Pro, ..), because the autounattend file
# will always select index 1. With this mechanism, a variant different
# from the first one can be automatically selected.
# imageSelection can be either an index (1-N) or the image name
# wiminfo can list all images contained in a given WIM file
wimexport win/sources/install.wim "${imageSelection}" win/sources/install_selected.wim
rm win/sources/install.wim
# Split image so it fits in FAT32 partition # Split image so it fits in FAT32 partition
wimsplit win/sources/install_selected.wim win/sources/install.swm 4096 wimsplit win/sources/install.wim win/sources/install.swm 4070
rm win/sources/install_selected.wim rm win/sources/install.wim
cp ${autounattend.autounattendXML} win/autounattend.xml cp ${autounattend.autounattendXML} win/autounattend.xml
@ -132,18 +132,20 @@ let
''} ''}
rm -rf win rm -rf win
${utils.tpmStartCommands}
# Qemu requires files to be rw # Qemu requires files to be rw
qemu-img create -f qcow2 c.img ${diskImageSize} qemu-img create -f qcow2 c.img ${diskImageSize}
qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams}
'' ''
); );
baseImage = pkgs.runCommandNoCC "RESTRICTDIST-windows.img" {} '' baseImage = pkgs.runCommand "RESTRICTDIST-windows.img" {} ''
${installScript} ${installScript}
mv c.img $out mv c.img $out
''; '';
finalImage = builtins.foldl' (acc: v: pkgs.runCommandNoCC "RESTRICTDIST-${v.name}.img" { finalImage = builtins.foldl' (acc: v: pkgs.runCommand "RESTRICTDIST-${v.name}.img" {
buildInputs = with utils; [ buildInputs = with utils; [
qemu win-wait win-exec win-put qemu win-wait win-exec win-put
] ++ (v.buildInputs or []); ] ++ (v.buildInputs or []);
@ -152,14 +154,17 @@ let
qemuParams = utils.mkQemuFlags (lib.optional (!impureMode) "-display none" ++ [ qemuParams = utils.mkQemuFlags (lib.optional (!impureMode) "-display none" ++ [
# Output image # Output image
"-drive" "-drive"
"file=c.img,index=0,media=disk,cache=unsafe" "file=c.img,index=0,media=disk,if=virtio,cache=unsafe"
# Network - enable SSH forwarding # Network - enable SSH forwarding
"-netdev user,id=n1,net=192.168.1.0/24,restrict=on,hostfwd=tcp::2022-:22" "-netdev user,id=n1,net=192.168.1.0/24,restrict=on,hostfwd=tcp::2022-:22"
]); ]);
in '' in ''
set -x
${utils.tpmStartCommands}
# Create an image referencing the previous image in the chain # Create an image referencing the previous image in the chain
qemu-img create -f qcow2 -b ${acc} c.img qemu-img create -F qcow2 -f qcow2 -b ${acc} c.img
set -m set -m
qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} & qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} &