Compare commits

..

1 Commits

Author SHA1 Message Date
Jacek Galowicz ffd9355bd5 Use pinned nixpkgs
(#13)
2021-08-20 12:01:47 +02:00
16 changed files with 265 additions and 274 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 11 installation. * Fully automatic, parameterizable Windows 10 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,30 +52,3 @@ 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
};
};
}
```

View File

@ -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
}

View File

@ -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;
};
};
};
}

View File

@ -15,8 +15,6 @@
, impureShellCommands ? [] , impureShellCommands ? []
, driveLetter ? "D:" , driveLetter ? "D:"
, efi ? true , efi ? true
, imageSelection ? "Windows 11 Pro N"
, enableTpm
, ... , ...
}: }:
@ -51,28 +49,34 @@ let
# mkDirsDesc ++ writeKeysDesc ++ # mkDirsDesc ++ writeKeysDesc ++
[ [
{ {
Path = ''powershell.exe ${driveLetter}\install-ssh.ps1''; Path = ''powershell.exe Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 -Source ${driveLetter}\fod -LimitAccess'';
Description = "Install OpenSSH service."; 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; 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}\setup.ps1''; Path = ''powershell.exe ${driveLetter}\ssh-setup.ps1'';
Description = "Setup SSH and keys"; Description = "Setup SSH and keys";
} }
] ]
@ -147,14 +151,6 @@ let
</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">
@ -206,10 +202,9 @@ let
<PartitionID>3</PartitionID> <PartitionID>3</PartitionID>
</InstallTo> </InstallTo>
<InstallFrom> <InstallFrom>
<Path>\install.swm</Path>
<MetaData wcm:action="add"> <MetaData wcm:action="add">
<Key>/IMAGE/NAME</Key> <Key>/IMAGE/INDEX</Key>
<Value>${imageSelection}</Value> <Value>1</Value>
</MetaData> </MetaData>
</InstallFrom> </InstallFrom>
</OSImage> </OSImage>
@ -217,7 +212,7 @@ let
<UserData> <UserData>
<ProductKey> <ProductKey>
${if productKey != null then "<Key>${productKey}</Key>" else "<Key/>"} ${if productKey != null then "<Key>${productKey}</Key>" else ""}
<WillShowUI>OnError</WillShowUI> <WillShowUI>OnError</WillShowUI>
</ProductKey> </ProductKey>
<AcceptEula>true</AcceptEula> <AcceptEula>true</AcceptEula>
@ -280,12 +275,14 @@ let
</AutoLogon> </AutoLogon>
''} ''}
</component> <FirstLogonCommands>
<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"> <SynchronousCommand wcm:action="add">
<Reseal> <Order>1</Order>
<ForceShutdownNow>true</ForceShutdownNow> <CommandLine>cmd /C shutdown /s /f /t 00</CommandLine>
<Mode>OOBE</Mode> <Description>ChangeHideFiles</Description>
</Reseal> </SynchronousCommand>
</FirstLogonCommands>
</component> </component>
</settings> </settings>
@ -307,18 +304,18 @@ let
</component> </component>
</settings> </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> </unattend>
''; '';
in { in {
# Lint and format as a sanity check # Lint and format as a sanity check
autounattendXML = pkgs.runCommand "autounattend.xml" {} '' autounattendXML = pkgs.runCommandNoCC "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 "setup.ps1" ( setupScript = pkgs.writeText "ssh-setup.ps1" (
'' ''
# Setup SSH and keys # Setup SSH and keys
'' + '' +

View File

@ -1,10 +1,7 @@
{ pkgs }: { pkgs }:
pkgs.runCommand "win-bundle-installer.exe" {} '' pkgs.runCommandNoCC "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 bundle.exe $out mv build.exe $out
'' ''

View File

@ -1,3 +0,0 @@
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,27 +1,19 @@
{ pkgs ? import <nixpkgs> {} {
# Whether to generate just a script to start and debug the windows installation pkgs ? import (import ./nix/sources.nix).nixpkgs {}
, impureMode ? false , impureMode ? false
# Flake input `self`
, self ? null
}: }:
let let
wfvm = wfvm = (import ./default.nix { inherit pkgs; });
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.requireFile rec { # windowsImage = pkgs.fetchurl {
# name = "Win11_22H2_English_x64v1.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 = "08mbppsm1naf73z8fjyqkf975nbls7xj9n4fq0yp802dv1rz3whd"; # sha256 = "668fe1af70c2f7416328aee3a0bb066b12dc6bbd2576f40f812b95741e18bc3a";
# message = "Get disk image ${name} from https://www.microsoft.com/en-us/software-download/windows11/";
# }; # };
# impureShellCommands = [ # impureShellCommands = [
@ -57,7 +49,7 @@ wfvm.makeWindowsImage {
disable-autolock disable-autolock
disable-firewall disable-firewall
]) ])
anaconda3 msys2 anaconda3 msys2 msvc msvc-ide-unbreak
]; ];
# services = { # services = {
@ -68,10 +60,8 @@ wfvm.makeWindowsImage {
# }; # };
# }; # };
# License key (required) # License key
# productKey = throw "Search the f* web" # productKey = "iboughtthisone";
imageSelection = "Windows 11 Pro N";
# Locales # Locales
# uiLanguage = "en-US"; # uiLanguage = "en-US";

View File

@ -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

View File

@ -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://aka.ms/vs/16/release/vs_community.exe"; url = "https://download.visualstudio.microsoft.com/download/pr/ac05c4f5-0da1-429f-8701-ce509ac69926/cc9556137c66a373670376d6db2fc5c5c937b2b0bf7b3d3cac11c69e33615511/vs_Community.exe";
sha256 = "sha256-l4ZKFZTgHf3BmD0eFWyGwsvb4lqB/LiQYizAABOs3gg="; sha256 = "04amc4rrxihimhy3syxzn2r3gjf5qlpxpmkn0dkp78v6gh9md5fc";
}; };
# 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 = "sha256-GoOKzln8DXVMx52jWGEjwkOFkpSW+wEffAVmBVugIyk="; outputHash = "0fp7a6prjp8n8sirwday13wis3xyzhmrwi377y3x89nxzysp0mnv";
phases = [ "buildPhase" ]; phases = [ "buildPhase" ];
buildInputs = [ download-vs ]; buildInputs = [ download-vs ];
@ -118,7 +118,7 @@ in
name = "MSVC-ide-unbreak"; name = "MSVC-ide-unbreak";
script = 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 sleep 40
''; '';
}; };
@ -154,16 +154,6 @@ in
win-exec "reg add HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\Personalization /v NoLockScreen /t REG_DWORD /d 1" 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 # Chain together layers that are quick to run so that the VM does
# not have to be started/shutdown for each. # not have to be started/shutdown for each.

26
wfvm/nix/sources.json Normal file
View File

@ -0,0 +1,26 @@
{
"niv": {
"branch": "master",
"description": "Easy dependency management for Nix projects",
"homepage": "https://github.com/nmattia/niv",
"owner": "nmattia",
"repo": "niv",
"rev": "e0ca65c81a2d7a4d82a189f1e23a48d59ad42070",
"sha256": "1pq9nh1d8nn3xvbdny8fafzw87mj7gsmp6pxkdl65w2g18rmcmzx",
"type": "tarball",
"url": "https://github.com/nmattia/niv/archive/e0ca65c81a2d7a4d82a189f1e23a48d59ad42070.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
},
"nixpkgs": {
"branch": "master",
"description": "Nix Packages collection",
"homepage": "",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f8248ab6d9e69ea9c07950d73d48807ec595e923",
"sha256": "009i9j6mbq6i481088jllblgdnci105b2q4mscprdawg3knlyahk",
"type": "tarball",
"url": "https://github.com/NixOS/nixpkgs/archive/f8248ab6d9e69ea9c07950d73d48807ec595e923.tar.gz",
"url_template": "https://github.com/<owner>/<repo>/archive/<rev>.tar.gz"
}
}

148
wfvm/nix/sources.nix Normal file
View File

@ -0,0 +1,148 @@
# This file has been generated by Niv.
let
#
# The fetchers. fetch_<type> fetches specs of type <type>.
#
fetch_file = pkgs: spec:
if spec.builtin or true then
builtins_fetchurl { inherit (spec) url sha256; }
else
pkgs.fetchurl { inherit (spec) url sha256; };
fetch_tarball = pkgs: name: spec:
let
ok = str: ! builtins.isNull (builtins.match "[a-zA-Z0-9+-._?=]" str);
# sanitize the name, though nix will still fail if name starts with period
name' = stringAsChars (x: if ! ok x then "-" else x) "${name}-src";
in
if spec.builtin or true then
builtins_fetchTarball { name = name'; inherit (spec) url sha256; }
else
pkgs.fetchzip { name = name'; inherit (spec) url sha256; };
fetch_git = spec:
builtins.fetchGit { url = spec.repo; inherit (spec) rev ref; };
fetch_local = spec: spec.path;
fetch_builtin-tarball = name: throw
''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=tarball -a builtin=true'';
fetch_builtin-url = name: throw
''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`.
$ niv modify ${name} -a type=file -a builtin=true'';
#
# Various helpers
#
# The set of packages used when specs are fetched using non-builtins.
mkPkgs = sources:
let
sourcesNixpkgs =
import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) {};
hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath;
hasThisAsNixpkgsPath = <nixpkgs> == ./.;
in
if builtins.hasAttr "nixpkgs" sources
then sourcesNixpkgs
else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then
import <nixpkgs> {}
else
abort
''
Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or
add a package called "nixpkgs" to your sources.json.
'';
# The actual fetching function.
fetch = pkgs: name: spec:
if ! builtins.hasAttr "type" spec then
abort "ERROR: niv spec ${name} does not have a 'type' attribute"
else if spec.type == "file" then fetch_file pkgs spec
else if spec.type == "tarball" then fetch_tarball pkgs name spec
else if spec.type == "git" then fetch_git spec
else if spec.type == "local" then fetch_local spec
else if spec.type == "builtin-tarball" then fetch_builtin-tarball name
else if spec.type == "builtin-url" then fetch_builtin-url name
else
abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}";
# If the environment variable NIV_OVERRIDE_${name} is set, then use
# the path directly as opposed to the fetched source.
replace = name: drv:
let
saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name;
ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}";
in
if ersatz == "" then drv else ersatz;
# Ports of functions for older nix versions
# a Nix version of mapAttrs if the built-in doesn't exist
mapAttrs = builtins.mapAttrs or (
f: set: with builtins;
listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))
);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295
range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1);
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257
stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1));
# https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269
stringAsChars = f: s: concatStrings (map f (stringToCharacters s));
concatStrings = builtins.concatStringsSep "";
# fetchTarball version that is compatible between all the versions of Nix
builtins_fetchTarball = { url, name, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchTarball;
in
if lessThan nixVersion "1.12" then
fetchTarball { inherit name url; }
else
fetchTarball attrs;
# fetchurl version that is compatible between all the versions of Nix
builtins_fetchurl = { url, sha256 }@attrs:
let
inherit (builtins) lessThan nixVersion fetchurl;
in
if lessThan nixVersion "1.12" then
fetchurl { inherit url; }
else
fetchurl attrs;
# Create the final "sources" from the config
mkSources = config:
mapAttrs (
name: spec:
if builtins.hasAttr "outPath" spec
then abort
"The values in sources.json should not have an 'outPath' attribute"
else
spec // { outPath = replace name (fetch config.pkgs name spec); }
) config.sources;
# The "config" used by the fetchers
mkConfig =
{ sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null
, sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile)
, pkgs ? mkPkgs sources
}: rec {
# The sources, i.e. the attribute set of spec name to spec
inherit sources;
# The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers
inherit pkgs;
};
in
mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); }

1
wfvm/openssh/README.md Normal file
View File

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

Binary file not shown.

View File

@ -1,45 +1,23 @@
{ pkgs { pkgs, baseRtc ? "2020-04-20T14:21:42", cores ? "4", qemuMem ? "4G", efi ? true }:
, 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; qemu = pkgs.qemu_test;
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,smm=on" "-M q35"
"-vga qxl" "-vga qxl"
"-rtc base=${baseRtc}" "-rtc base=${baseRtc}"
"-device qemu-xhci" "-device qemu-xhci"
"-device virtio-net-pci,netdev=n1" "-device virtio-net-pci,netdev=n1"
] ++ pkgs.lib.optionals efi [ ] ++ pkgs.lib.optionals efi [
"-bios ${OVMF.fd}/FV/OVMF.fd" "-bios ${pkgs.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" ''
@ -82,10 +60,8 @@ 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 -b- << EOF wfvm@localhost <<< "cd $2
cd $2 put $1"
put $1
EOF
''; '';
win-get = pkgs.writeShellScriptBin "win-get" '' win-get = pkgs.writeShellScriptBin "win-get" ''
set -e set -e
@ -115,7 +91,6 @@ 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,17 +5,16 @@
, 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 ? "Windows 11 Pro N" , imageSelection ? "1"
, efi ? true , efi ? true
, ... , ...
}@attrs: }@attrs:
let let
lib = pkgs.lib; lib = pkgs.lib;
utils = import ./utils.nix ({ inherit pkgs efi enableTpm; } // attrs); utils = import ./utils.nix { inherit pkgs efi; };
inherit (pkgs) guestfs-tools; libguestfs = pkgs.libguestfs-with-appliance;
# 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: {
@ -25,7 +24,7 @@ let
}); });
runQemuCommand = name: command: ( runQemuCommand = name: command: (
pkgs.runCommand name { buildInputs = [ p7zip utils.qemu guestfs-tools ]; } pkgs.runCommandNoCC name { buildInputs = [ p7zip utils.qemu libguestfs ]; }
( (
'' ''
if ! test -f; then if ! test -f; then
@ -36,25 +35,23 @@ let
) )
); );
windowsIso = if windowsImage != null then windowsImage else pkgs.requireFile rec { windowsIso = if windowsImage != null then windowsImage else pkgs.fetchurl {
name = "Win11_22H2_English_x64v2.iso"; name = "RESTRICTDIST-release_svc_refresh_CLIENT_LTSC_EVAL_x64FRE_en-us.iso";
sha256 = "0xhhxy47yaf1jsfmskym5f65hljw8q0aqs70my86m402i6dsjnc0"; url = "https://software-download.microsoft.com/download/sg/17763.107.101029-1455.rs5_release_svc_refresh_CLIENT_LTSC_EVAL_x64FRE_en-us.iso";
message = "Get disk image ${name} from https://www.microsoft.com/en-us/software-download/windows11/"; sha256 = "668fe1af70c2f7416328aee3a0bb066b12dc6bbd2576f40f812b95741e18bc3a";
}; };
# stable as of 2021-04-08
virtioWinIso = pkgs.fetchurl { virtioWinIso = pkgs.fetchurl {
url = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.229-1/virtio-win.iso"; url = "https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/archive-virtio/virtio-win-0.1.185-2/virtio-win-0.1.185.iso";
sha256 = "1q5vrcd70kya4nhlbpxmj7mwmwra1hm3x7w8rzkawpk06kg0v2n8"; sha256 = "11n3kjyawiwacmi3jmfmn311g9xvfn6m0ccdwnjxw1brzb4kqaxg";
}; };
openSshServerPackage = pkgs.fetchurl { openSshServerPackage = ./openssh/server-package.cab;
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 enableTpm; inherit pkgs;
users = users // { users = users // {
wfvm = { wfvm = {
password = "1234"; password = "1234";
@ -72,16 +69,17 @@ 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 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/OpenSSH-Win64.zip cp ${openSshServerPackage} pkgs/fod/OpenSSH-Server-Package~31bf3856ad364e35~amd64~~.cab
# SSH setup script goes here because windows XML parser sucks # SSH setup script goes here because windows XML parser sucks
cp ${./install-ssh.ps1} pkgs/install-ssh.ps1 cp ${autounattend.setupScript} pkgs/ssh-setup.ps1
cp ${autounattend.setupScript} pkgs/setup.ps1
virt-make-fs --partition --type=fat pkgs/ $out virt-make-fs --partition --type=fat pkgs/ $out
''; '';
@ -109,7 +107,7 @@ let
'' ''
#!${pkgs.runtimeShell} #!${pkgs.runtimeShell}
set -euxo pipefail 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 # 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
@ -119,10 +117,20 @@ let
mkdir -p win/nix-win mkdir -p win/nix-win
7z x -y ${windowsIso} -owin 7z x -y ${windowsIso} -owin
# Split image so it fits in FAT32 partition # Extract desired variant from install.wim
wimsplit win/sources/install.wim win/sources/install.swm 4070 # 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 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 cp ${autounattend.autounattendXML} win/autounattend.xml
${if efi then '' ${if efi then ''
@ -132,20 +140,18 @@ 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.runCommand "RESTRICTDIST-windows.img" {} '' baseImage = pkgs.runCommandNoCC "RESTRICTDIST-windows.img" {} ''
${installScript} ${installScript}
mv c.img $out 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; [ buildInputs = with utils; [
qemu win-wait win-exec win-put qemu win-wait win-exec win-put
] ++ (v.buildInputs or []); ] ++ (v.buildInputs or []);
@ -160,11 +166,8 @@ let
]); ]);
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 -f qcow2 -b ${acc} c.img qemu-img create -f qcow2 -b ${acc} c.img
set -m set -m
qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} & qemu-system-x86_64 ${lib.concatStringsSep " " qemuParams} &