Compare commits
No commits in common. "master" and "large-image" have entirely different histories.
master
...
large-imag
29
README.md
29
README.md
@ -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.
|
||||
|
||||
* Reproducible - everything runs in the Nix sandbox with no tricks.
|
||||
* Fully automatic, parameterizable Windows 11 installation.
|
||||
* Fully automatic, parameterizable Windows 10 installation.
|
||||
* 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.
|
||||
* 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,30 +52,3 @@ Impure/pure mode
|
||||
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.
|
||||
|
||||
|
||||
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
27
flake.lock
generated
@ -1,27 +0,0 @@
|
||||
{
|
||||
"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
37
flake.nix
@ -1,37 +0,0 @@
|
||||
{
|
||||
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;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -15,8 +15,6 @@
|
||||
, impureShellCommands ? []
|
||||
, driveLetter ? "D:"
|
||||
, efi ? true
|
||||
, imageSelection ? "Windows 11 Pro N"
|
||||
, enableTpm
|
||||
, ...
|
||||
}:
|
||||
|
||||
@ -51,28 +49,34 @@ let
|
||||
# mkDirsDesc ++ writeKeysDesc ++
|
||||
[
|
||||
{
|
||||
Path = ''powershell.exe ${driveLetter}\install-ssh.ps1'';
|
||||
Description = "Install OpenSSH service.";
|
||||
Path = ''powershell.exe Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 -Source ${driveLetter}\fod -LimitAccess'';
|
||||
Description = "Add 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;
|
||||
|
||||
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";
|
||||
Description = "Allow unsigned powershell scripts.";
|
||||
} {
|
||||
}
|
||||
]
|
||||
++ [
|
||||
{
|
||||
Path = ''powershell.exe ${driveLetter}\win-bundle-installer.exe'';
|
||||
Description = "Install any declared packages.";
|
||||
} {
|
||||
Path = "net accounts /maxpwage:unlimited";
|
||||
Description = "Disable forced password expiry.";
|
||||
} ]
|
||||
}
|
||||
]
|
||||
++ setupCommands
|
||||
++ [
|
||||
{
|
||||
Path = ''powershell.exe ${driveLetter}\setup.ps1'';
|
||||
Path = ''powershell.exe ${driveLetter}\ssh-setup.ps1'';
|
||||
Description = "Setup SSH and keys";
|
||||
}
|
||||
]
|
||||
@ -120,9 +124,6 @@ let
|
||||
# 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);
|
||||
|
||||
diskId =
|
||||
if efi then 2 else 1;
|
||||
|
||||
autounattendXML = pkgs.writeText "autounattend.xml" ''
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<unattend xmlns="urn:schemas-microsoft-com:unattend">
|
||||
@ -135,26 +136,9 @@ let
|
||||
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
|
||||
<Path>E:\</Path>
|
||||
</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>
|
||||
</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">
|
||||
${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>
|
||||
<Disk wcm:action="add">
|
||||
@ -194,7 +178,7 @@ let
|
||||
<PartitionID>3</PartitionID>
|
||||
</ModifyPartition>
|
||||
</ModifyPartitions>
|
||||
<DiskID>${toString diskId}</DiskID>
|
||||
<DiskID>0</DiskID>
|
||||
<WillWipeDisk>true</WillWipeDisk>
|
||||
</Disk>
|
||||
</DiskConfiguration>
|
||||
@ -202,14 +186,13 @@ let
|
||||
<ImageInstall>
|
||||
<OSImage>
|
||||
<InstallTo>
|
||||
<DiskID>${toString diskId}</DiskID>
|
||||
<DiskID>0</DiskID>
|
||||
<PartitionID>3</PartitionID>
|
||||
</InstallTo>
|
||||
<InstallFrom>
|
||||
<Path>\install.swm</Path>
|
||||
<MetaData wcm:action="add">
|
||||
<Key>/IMAGE/NAME</Key>
|
||||
<Value>${imageSelection}</Value>
|
||||
<Key>/IMAGE/INDEX</Key>
|
||||
<Value>1</Value>
|
||||
</MetaData>
|
||||
</InstallFrom>
|
||||
</OSImage>
|
||||
@ -217,7 +200,7 @@ let
|
||||
|
||||
<UserData>
|
||||
<ProductKey>
|
||||
${if productKey != null then "<Key>${productKey}</Key>" else "<Key/>"}
|
||||
${if productKey != null then "<Key>${productKey}</Key>" else ""}
|
||||
<WillShowUI>OnError</WillShowUI>
|
||||
</ProductKey>
|
||||
<AcceptEula>true</AcceptEula>
|
||||
@ -280,12 +263,14 @@ let
|
||||
</AutoLogon>
|
||||
''}
|
||||
|
||||
</component>
|
||||
<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">
|
||||
<Reseal>
|
||||
<ForceShutdownNow>true</ForceShutdownNow>
|
||||
<Mode>OOBE</Mode>
|
||||
</Reseal>
|
||||
<FirstLogonCommands>
|
||||
<SynchronousCommand wcm:action="add">
|
||||
<Order>1</Order>
|
||||
<CommandLine>cmd /C shutdown /s /f /t 00</CommandLine>
|
||||
<Description>ChangeHideFiles</Description>
|
||||
</SynchronousCommand>
|
||||
</FirstLogonCommands>
|
||||
|
||||
</component>
|
||||
</settings>
|
||||
|
||||
@ -307,18 +292,18 @@ let
|
||||
</component>
|
||||
</settings>
|
||||
|
||||
<cpi:offlineImage cpi:source="wim:c:/wim/windows-11/install.wim#${imageSelection}" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
|
||||
<cpi:offlineImage cpi:source="wim:c:/wim/windows-10/install.wim#Windows 10 Enterprise LTSC 2019 Evaluation" xmlns:cpi="urn:schemas-microsoft-com:cpi" />
|
||||
</unattend>
|
||||
'';
|
||||
|
||||
in {
|
||||
# Lint and format as a sanity check
|
||||
autounattendXML = pkgs.runCommand "autounattend.xml" {} ''
|
||||
autounattendXML = pkgs.runCommandNoCC "autounattend.xml" {} ''
|
||||
${pkgs.libxml2}/bin/xmllint --format ${autounattendXML} > $out
|
||||
'';
|
||||
|
||||
# autounattend.xml is _super_ picky about quotes and other things
|
||||
setupScript = pkgs.writeText "setup.ps1" (
|
||||
setupScript = pkgs.writeText "ssh-setup.ps1" (
|
||||
''
|
||||
# Setup SSH and keys
|
||||
'' +
|
||||
|
@ -1,10 +1,7 @@
|
||||
{ pkgs }:
|
||||
|
||||
pkgs.runCommand "win-bundle-installer.exe" {} ''
|
||||
mkdir bundle
|
||||
cd bundle
|
||||
cp ${./go.mod} go.mod
|
||||
pkgs.runCommandNoCC "win-bundle-installer.exe" {} ''
|
||||
cp ${./main.go} main.go
|
||||
env HOME=$(mktemp -d) GOOS=windows GOARCH=amd64 ${pkgs.go}/bin/go build
|
||||
mv bundle.exe $out
|
||||
mv build.exe $out
|
||||
''
|
||||
|
@ -1,3 +0,0 @@
|
||||
module bundle
|
||||
|
||||
go 1.11
|
@ -2,6 +2,6 @@
|
||||
|
||||
{
|
||||
makeWindowsImage = attrs: import ./win.nix ({ inherit pkgs; } // attrs);
|
||||
layers = import ./layers { inherit pkgs; };
|
||||
utils = import ./utils.nix { inherit pkgs; };
|
||||
layers = (import ./layers { inherit pkgs; });
|
||||
utils = (import ./utils.nix { inherit pkgs; });
|
||||
}
|
||||
|
@ -1,27 +1,16 @@
|
||||
{ pkgs ? import <nixpkgs> {}
|
||||
# Whether to generate just a script to start and debug the windows installation
|
||||
, impureMode ? false
|
||||
# Flake input `self`
|
||||
, self ? null
|
||||
}:
|
||||
{ pkgs ? import <nixpkgs> {}, impureMode ? false }:
|
||||
|
||||
let
|
||||
wfvm =
|
||||
if self == null
|
||||
# nix-build
|
||||
then (import ./default.nix { inherit pkgs; })
|
||||
# built from flake.nix
|
||||
else self.lib;
|
||||
wfvm = (import ./default.nix { inherit pkgs; });
|
||||
in
|
||||
wfvm.makeWindowsImage {
|
||||
# Build install script & skip building iso
|
||||
inherit impureMode;
|
||||
|
||||
# Custom base iso
|
||||
# windowsImage = pkgs.requireFile rec {
|
||||
# name = "Win11_22H2_English_x64v1.iso";
|
||||
# sha256 = "08mbppsm1naf73z8fjyqkf975nbls7xj9n4fq0yp802dv1rz3whd";
|
||||
# message = "Get disk image ${name} from https://www.microsoft.com/en-us/software-download/windows11/";
|
||||
# windowsImage = pkgs.fetchurl {
|
||||
# url = "https://software-download.microsoft.com/download/sg/17763.107.101029-1455.rs5_release_svc_refresh_CLIENT_LTSC_EVAL_x64FRE_en-us.iso";
|
||||
# sha256 = "668fe1af70c2f7416328aee3a0bb066b12dc6bbd2576f40f812b95741e18bc3a";
|
||||
# };
|
||||
|
||||
# impureShellCommands = [
|
||||
@ -48,17 +37,7 @@ wfvm.makeWindowsImage {
|
||||
# administratorPassword = "12345";
|
||||
|
||||
# Imperative installation commands, to be installed incrementally
|
||||
installCommands =
|
||||
if impureMode
|
||||
then []
|
||||
else with wfvm.layers; [
|
||||
(collapseLayers [
|
||||
disable-autosleep
|
||||
disable-autolock
|
||||
disable-firewall
|
||||
])
|
||||
anaconda3 msys2
|
||||
];
|
||||
installCommands = with wfvm.layers; [ anaconda3 msys2 msvc msvc-ide-unbreak ];
|
||||
|
||||
# services = {
|
||||
# # Enable remote management
|
||||
@ -68,10 +47,8 @@ wfvm.makeWindowsImage {
|
||||
# };
|
||||
# };
|
||||
|
||||
# License key (required)
|
||||
# productKey = throw "Search the f* web"
|
||||
imageSelection = "Windows 11 Pro N";
|
||||
|
||||
# License key
|
||||
# productKey = "iboughtthisone";
|
||||
|
||||
# Locales
|
||||
# uiLanguage = "en-US";
|
||||
|
@ -1,42 +0,0 @@
|
||||
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
|
@ -8,8 +8,8 @@ in
|
||||
script = let
|
||||
Anaconda3 = pkgs.fetchurl {
|
||||
name = "Anaconda3.exe";
|
||||
url = "https://repo.anaconda.com/archive/Anaconda3-2021.05-Windows-x86_64.exe";
|
||||
sha256 = "1lpk7k4gydyk524z1nk4rrninrwi20g2ias2njc9w0a40hwl5nwk";
|
||||
url = "https://repo.anaconda.com/archive/Anaconda3-2020.02-Windows-x86_64.exe";
|
||||
sha256 = "0n31l8l89jrjrbzbifxbjnr3g320ly9i4zfyqbf3l9blf4ygbhl3";
|
||||
};
|
||||
in
|
||||
''
|
||||
@ -71,8 +71,8 @@ in
|
||||
script = let
|
||||
bootstrapper = pkgs.fetchurl {
|
||||
name = "RESTRICTDIST-vs_Community.exe";
|
||||
url = "https://aka.ms/vs/16/release/vs_community.exe";
|
||||
sha256 = "sha256-l4ZKFZTgHf3BmD0eFWyGwsvb4lqB/LiQYizAABOs3gg=";
|
||||
url = "https://download.visualstudio.microsoft.com/download/pr/ac05c4f5-0da1-429f-8701-ce509ac69926/cc9556137c66a373670376d6db2fc5c5c937b2b0bf7b3d3cac11c69e33615511/vs_Community.exe";
|
||||
sha256 = "04amc4rrxihimhy3syxzn2r3gjf5qlpxpmkn0dkp78v6gh9md5fc";
|
||||
};
|
||||
# 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 {
|
||||
@ -93,7 +93,7 @@ in
|
||||
|
||||
outputHashAlgo = "sha256";
|
||||
outputHashMode = "recursive";
|
||||
outputHash = "sha256-GoOKzln8DXVMx52jWGEjwkOFkpSW+wEffAVmBVugIyk=";
|
||||
outputHash = "0fp7a6prjp8n8sirwday13wis3xyzhmrwi377y3x89nxzysp0mnv";
|
||||
|
||||
phases = [ "buildPhase" ];
|
||||
buildInputs = [ download-vs ];
|
||||
@ -118,61 +118,8 @@ in
|
||||
name = "MSVC-ide-unbreak";
|
||||
script =
|
||||
''
|
||||
win-exec 'cd "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE" && devenv /ResetSettings'
|
||||
win-exec 'cd "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\IDE" && devenv /ResetSettings'
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
1
wfvm/openssh/README.md
Normal file
1
wfvm/openssh/README.md
Normal file
@ -0,0 +1 @@
|
||||
This file is not publicaly acessible anywhere so had to be extracted from a connected instance
|
BIN
wfvm/openssh/server-package.cab
Normal file
BIN
wfvm/openssh/server-package.cab
Normal file
Binary file not shown.
@ -1,45 +1,22 @@
|
||||
{ pkgs
|
||||
, baseRtc ? "2022-10-10T10:10:10"
|
||||
, cores ? "4"
|
||||
, qemuMem ? "4G"
|
||||
, efi ? true
|
||||
, enableTpm ? false
|
||||
, ...
|
||||
}:
|
||||
{ pkgs, baseRtc ? "2020-04-20T14:21:42", cores ? "4", qemuMem ? "4G", efi ? true }:
|
||||
|
||||
rec {
|
||||
# qemu_test is a smaller closure only building for a single system arch
|
||||
qemu = pkgs.qemu;
|
||||
|
||||
OVMF = pkgs.OVMF.override {
|
||||
secureBoot = true;
|
||||
};
|
||||
qemu = pkgs.qemu_test;
|
||||
|
||||
mkQemuFlags = extraFlags: [
|
||||
"-enable-kvm"
|
||||
"-cpu host"
|
||||
"-smp ${cores}"
|
||||
"-m ${qemuMem}"
|
||||
"-M q35,smm=on"
|
||||
"-vga qxl"
|
||||
"-M q35"
|
||||
"-rtc base=${baseRtc}"
|
||||
"-device qemu-xhci"
|
||||
"-device virtio-net-pci,netdev=n1"
|
||||
"-device e1000,netdev=n1"
|
||||
] ++ pkgs.lib.optionals efi [
|
||||
"-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"
|
||||
"-bios ${pkgs.OVMF.fd}/FV/OVMF.fd"
|
||||
] ++ 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
|
||||
sshOpts = "-F /dev/null -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ConnectTimeout=1";
|
||||
win-exec = pkgs.writeShellScriptBin "win-exec" ''
|
||||
@ -82,10 +59,8 @@ rec {
|
||||
echo win-put $1 -\> $2
|
||||
${pkgs.sshpass}/bin/sshpass -p1234 -- \
|
||||
${pkgs.openssh}/bin/sftp -r -P 2022 ${sshOpts} \
|
||||
wfvm@localhost -b- << EOF
|
||||
cd $2
|
||||
put $1
|
||||
EOF
|
||||
wfvm@localhost <<< "cd $2
|
||||
put $1"
|
||||
'';
|
||||
win-get = pkgs.writeShellScriptBin "win-get" ''
|
||||
set -e
|
||||
@ -115,7 +90,6 @@ rec {
|
||||
]);
|
||||
in pkgs.writeShellScriptBin "wfvm-run-${name}" ''
|
||||
set -e -m
|
||||
${tpmStartCommands}
|
||||
${qemu}/bin/qemu-system-x86_64 ${pkgs.lib.concatStringsSep " " qemuParams} &
|
||||
|
||||
${win-wait}/bin/win-wait
|
||||
|
67
wfvm/win.nix
67
wfvm/win.nix
@ -5,17 +5,16 @@
|
||||
, impureMode ? false
|
||||
, installCommands ? []
|
||||
, users ? {}
|
||||
, enableTpm ? true
|
||||
# autounattend always installs index 1, so this default is backward-compatible
|
||||
, imageSelection ? "Windows 11 Pro N"
|
||||
, imageSelection ? "1"
|
||||
, efi ? true
|
||||
, ...
|
||||
}@attrs:
|
||||
|
||||
let
|
||||
lib = pkgs.lib;
|
||||
utils = import ./utils.nix ({ inherit pkgs efi enableTpm; } // attrs);
|
||||
inherit (pkgs) guestfs-tools;
|
||||
utils = import ./utils.nix { inherit pkgs efi; };
|
||||
libguestfs = pkgs.libguestfs-with-appliance;
|
||||
|
||||
# p7zip on >20.03 has known vulns but we have no better option
|
||||
p7zip = pkgs.p7zip.overrideAttrs(old: {
|
||||
@ -25,7 +24,7 @@ let
|
||||
});
|
||||
|
||||
runQemuCommand = name: command: (
|
||||
pkgs.runCommand name { buildInputs = [ p7zip utils.qemu guestfs-tools ]; }
|
||||
pkgs.runCommandNoCC name { buildInputs = [ p7zip utils.qemu libguestfs ]; }
|
||||
(
|
||||
''
|
||||
if ! test -f; then
|
||||
@ -36,25 +35,17 @@ let
|
||||
)
|
||||
);
|
||||
|
||||
windowsIso = if windowsImage != null then windowsImage else pkgs.requireFile rec {
|
||||
name = "Win11_22H2_English_x64v2.iso";
|
||||
sha256 = "0xhhxy47yaf1jsfmskym5f65hljw8q0aqs70my86m402i6dsjnc0";
|
||||
message = "Get disk image ${name} from https://www.microsoft.com/en-us/software-download/windows11/";
|
||||
windowsIso = if windowsImage != null then windowsImage else pkgs.fetchurl {
|
||||
name = "RESTRICTDIST-release_svc_refresh_CLIENT_LTSC_EVAL_x64FRE_en-us.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 = "668fe1af70c2f7416328aee3a0bb066b12dc6bbd2576f40f812b95741e18bc3a";
|
||||
};
|
||||
|
||||
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";
|
||||
};
|
||||
openSshServerPackage = ./openssh/server-package.cab;
|
||||
|
||||
autounattend = import ./autounattend.nix (
|
||||
attrs // {
|
||||
inherit pkgs enableTpm;
|
||||
inherit pkgs;
|
||||
users = users // {
|
||||
wfvm = {
|
||||
password = "1234";
|
||||
@ -72,16 +63,15 @@ let
|
||||
# Packages required to drive installation of other packages
|
||||
bootstrapPkgs =
|
||||
runQemuCommand "bootstrap-win-pkgs.img" ''
|
||||
7z x -y ${virtioWinIso} -opkgs/virtio
|
||||
mkdir -p pkgs/fod
|
||||
|
||||
cp ${bundleInstaller} pkgs/"$(stripHash "${bundleInstaller}")"
|
||||
|
||||
# Install optional windows features
|
||||
cp ${openSshServerPackage} pkgs/OpenSSH-Win64.zip
|
||||
cp ${openSshServerPackage} pkgs/fod/OpenSSH-Server-Package~31bf3856ad364e35~amd64~~.cab
|
||||
|
||||
# SSH setup script goes here because windows XML parser sucks
|
||||
cp ${./install-ssh.ps1} pkgs/install-ssh.ps1
|
||||
cp ${autounattend.setupScript} pkgs/setup.ps1
|
||||
cp ${autounattend.setupScript} pkgs/ssh-setup.ps1
|
||||
|
||||
virt-make-fs --partition --type=fat pkgs/ $out
|
||||
'';
|
||||
@ -101,7 +91,7 @@ let
|
||||
"usb-storage,drive=win-install"
|
||||
# Output image
|
||||
"-drive"
|
||||
"file=c.img,index=0,media=disk,if=virtio,cache=unsafe"
|
||||
"file=c.img,index=0,media=disk,cache=unsafe"
|
||||
# Network
|
||||
"-netdev user,id=n1,net=192.168.1.0/24,restrict=on"
|
||||
]);
|
||||
@ -109,7 +99,7 @@ let
|
||||
''
|
||||
#!${pkgs.runtimeShell}
|
||||
set -euxo pipefail
|
||||
export PATH=${lib.makeBinPath [ p7zip utils.qemu guestfs-tools pkgs.wimlib ]}:$PATH
|
||||
export PATH=${lib.makeBinPath [ p7zip utils.qemu libguestfs pkgs.wimlib ]}:$PATH
|
||||
|
||||
# Create a bootable "USB" image
|
||||
# Booting in USB mode circumvents the "press any key to boot from cdrom" prompt
|
||||
@ -119,10 +109,20 @@ let
|
||||
mkdir -p win/nix-win
|
||||
7z x -y ${windowsIso} -owin
|
||||
|
||||
# Split image so it fits in FAT32 partition
|
||||
wimsplit win/sources/install.wim win/sources/install.swm 4070
|
||||
# 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
|
||||
wimsplit win/sources/install_selected.wim win/sources/install.swm 4096
|
||||
rm win/sources/install_selected.wim
|
||||
|
||||
cp ${autounattend.autounattendXML} win/autounattend.xml
|
||||
|
||||
${if efi then ''
|
||||
@ -132,20 +132,18 @@ let
|
||||
''}
|
||||
rm -rf win
|
||||
|
||||
${utils.tpmStartCommands}
|
||||
|
||||
# Qemu requires files to be rw
|
||||
qemu-img create -f qcow2 c.img ${diskImageSize}
|
||||
qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams}
|
||||
''
|
||||
);
|
||||
|
||||
baseImage = pkgs.runCommand "RESTRICTDIST-windows.img" {} ''
|
||||
baseImage = pkgs.runCommandNoCC "RESTRICTDIST-windows.img" {} ''
|
||||
${installScript}
|
||||
mv c.img $out
|
||||
'';
|
||||
|
||||
finalImage = builtins.foldl' (acc: v: pkgs.runCommand "RESTRICTDIST-${v.name}.img" {
|
||||
finalImage = builtins.foldl' (acc: v: pkgs.runCommandNoCC "RESTRICTDIST-${v.name}.img" {
|
||||
buildInputs = with utils; [
|
||||
qemu win-wait win-exec win-put
|
||||
] ++ (v.buildInputs or []);
|
||||
@ -154,17 +152,14 @@ let
|
||||
qemuParams = utils.mkQemuFlags (lib.optional (!impureMode) "-display none" ++ [
|
||||
# Output image
|
||||
"-drive"
|
||||
"file=c.img,index=0,media=disk,if=virtio,cache=unsafe"
|
||||
"file=c.img,index=0,media=disk,cache=unsafe"
|
||||
# Network - enable SSH forwarding
|
||||
"-netdev user,id=n1,net=192.168.1.0/24,restrict=on,hostfwd=tcp::2022-:22"
|
||||
]);
|
||||
|
||||
in ''
|
||||
set -x
|
||||
${utils.tpmStartCommands}
|
||||
|
||||
# Create an image referencing the previous image in the chain
|
||||
qemu-img create -F qcow2 -f qcow2 -b ${acc} c.img
|
||||
qemu-img create -f qcow2 -b ${acc} c.img
|
||||
|
||||
set -m
|
||||
qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} &
|
||||
|
Loading…
Reference in New Issue
Block a user