wfvm/wfvm/autounattend.nix

319 lines
12 KiB
Nix

{ pkgs
, fullName ? "John Doe"
, organization ? "KVM Authority"
, administratorPassword ? "123456"
, uiLanguage ? "en-US"
, inputLocale ? "en-US"
, userLocale ? "en-US"
, systemLocale ? "en-US"
, users ? {}
, productKey ? null
, defaultUser ? "wfvm"
, setupCommands ? []
, timeZone ? "UTC"
, services ? {}
, impureShellCommands ? []
, efi ? true
, driveLetter ? if efi then "E:" else "D:"
, ...
}:
let
lib = pkgs.lib;
serviceCommands = lib.mapAttrsToList (
serviceName: attrs: "powershell Set-Service -Name ${serviceName} " + (
lib.concatStringsSep " " (
(
lib.mapAttrsToList (
n: v: if builtins.typeOf v != "bool" then "-${n} ${v}" else "-${n}"
)
) (
# Always run without interaction
{ Force = true; } // attrs
)
)
)
) services;
sshSetupCommands =
# let
# makeDirs = lib.mapAttrsToList (n: v: ''mkdir C:\Users\${n}\.ssh'') users;
# writeKeys = lib.flatten (lib.mapAttrsToList (n: v: builtins.map (key: let
# commands = [
# ''powershell.exe Set-Content -Path C:\Users\${n}\.ssh\authorized_keys -Value '${key}' ''
# ];
# in lib.concatStringsSep "\n" commands) (v.sshKeys or [])) users);
# mkDirsDesc = builtins.map (c: {Path = c; Description = "Make SSH key dir";}) makeDirs;
# writeKeysDesc = builtins.map (c: {Path = c; Description = "Add SSH key";}) writeKeys;
# in
# mkDirsDesc ++ writeKeysDesc ++
[
{
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.";
}
]
++ setupCommands
++ [
{
Path = ''powershell.exe ${driveLetter}\ssh-setup.ps1'';
Description = "Setup SSH and keys";
}
]
++ serviceCommands
++ impureShellCommands
);
mkCommand = attrs: ''
<RunSynchronousCommand wcm:action="add">
${lib.concatStringsSep "\n" (lib.attrsets.mapAttrsToList (n: v: "<${n}>${v}</${n}>") attrs)}
</RunSynchronousCommand>
'';
mkCommands = commands: (
builtins.foldl' (
acc: v: rec {
i = acc.i + 1;
values = acc.values ++ [ (mkCommand (v // { Order = builtins.toString i; })) ];
}
) {
i = 0;
values = [];
} commands
).values;
mkUser =
{ name
, password
, description ? ""
, displayName ? ""
, groups ? []
# , sshKeys ? [] # Handled in scripts
}: ''
<LocalAccount wcm:action="add">
<Password>
<Value>${password}</Value>
<PlainText>true</PlainText>
</Password>
<Description>${description}</Description>
<DisplayName>${displayName}</DisplayName>
<Group>${builtins.concatStringsSep ";" (lib.unique ([ "Users" ] ++ groups))}</Group>
<Name>${name}</Name>
</LocalAccount>
'';
# 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);
autounattendXML = pkgs.writeText "autounattend.xml" ''
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="windowsPE">
<component name="Microsoft-Windows-PnpCustomizationsWinPE" 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">
<DriverPaths>
<PathAndCredentials wcm:action="add" wcm:keyValue="1">
<Path>D:\</Path>
</PathAndCredentials>
<PathAndCredentials wcm:action="add" wcm:keyValue="2">
<Path>E:\</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">
<DiskConfiguration>
<Disk wcm:action="add">
<CreatePartitions>
<CreatePartition wcm:action="add">
<Order>1</Order>
<Type>${if efi then "EFI" else "Primary"}</Type>
<Size>300</Size>
</CreatePartition>
<CreatePartition wcm:action="add">
<Order>2</Order>
<Type>${if efi then "MSR" else "Primary"}</Type>
<Size>16</Size>
</CreatePartition>
<CreatePartition wcm:action="add">
<Order>3</Order>
<Type>Primary</Type>
<Extend>true</Extend>
</CreatePartition>
</CreatePartitions>
<ModifyPartitions>
<ModifyPartition wcm:action="add">
<Order>1</Order>
<Format>${if efi then "FAT32" else "NTFS"}</Format>
<Label>System</Label>
<PartitionID>1</PartitionID>
</ModifyPartition>
<ModifyPartition wcm:action="add">
<Order>2</Order>
<PartitionID>2</PartitionID>
</ModifyPartition>
<ModifyPartition wcm:action="add">
<Order>3</Order>
<Format>NTFS</Format>
<Label>Windows</Label>
<Letter>C</Letter>
<PartitionID>3</PartitionID>
</ModifyPartition>
</ModifyPartitions>
<DiskID>0</DiskID>
<WillWipeDisk>true</WillWipeDisk>
</Disk>
</DiskConfiguration>
<ImageInstall>
<OSImage>
<InstallTo>
<DiskID>0</DiskID>
<PartitionID>3</PartitionID>
</InstallTo>
<InstallFrom>
<MetaData wcm:action="add">
<Key>/IMAGE/INDEX</Key>
<Value>1</Value>
</MetaData>
</InstallFrom>
</OSImage>
</ImageInstall>
<UserData>
<ProductKey>
${if productKey != null then "<Key>${productKey}</Key>" else ""}
<WillShowUI>OnError</WillShowUI>
</ProductKey>
<AcceptEula>true</AcceptEula>
<FullName>${fullName}</FullName>
<Organization>${organization}</Organization>
</UserData>
</component>
<component name="Microsoft-Windows-International-Core-WinPE" 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">
<SetupUILanguage>
<UILanguage>${uiLanguage}</UILanguage>
</SetupUILanguage>
<InputLocale>${inputLocale}</InputLocale>
<SystemLocale>${systemLocale}</SystemLocale>
<UILanguage>${uiLanguage}</UILanguage>
<UILanguageFallback>en-US</UILanguageFallback>
<UserLocale>${userLocale}</UserLocale>
</component>
</settings>
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" 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">
<InputLocale>${inputLocale}</InputLocale>
<SystemLocale>${systemLocale}</SystemLocale>
<UILanguage>${uiLanguage}</UILanguage>
<UILanguageFallback>en-US</UILanguageFallback>
<UserLocale>${userLocale}</UserLocale>
</component>
<component name="Microsoft-Windows-Shell-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">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideLocalAccountScreen>true</HideLocalAccountScreen>
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<ProtectYourPC>1</ProtectYourPC>
</OOBE>
<TimeZone>${timeZone}</TimeZone>
<UserAccounts>
${if administratorPassword != null then ''
<AdministratorPassword>
<Value>${administratorPassword}</Value>
<PlainText>true</PlainText>
</AdministratorPassword>
'' else ""}
<LocalAccounts>
${builtins.concatStringsSep "\n" (builtins.map mkUser flatUsers)}
</LocalAccounts>
</UserAccounts>
${if defaultUser == null then "" else ''
<AutoLogon>
<Password>
<Value>${(builtins.getAttr defaultUser users).password}</Value>
<PlainText>true</PlainText>
</Password>
<Enabled>true</Enabled>
<Username>${defaultUser}</Username>
</AutoLogon>
''}
<FirstLogonCommands>
<SynchronousCommand wcm:action="add">
<Order>1</Order>
<CommandLine>cmd /C shutdown /s /f /t 00</CommandLine>
<Description>ChangeHideFiles</Description>
</SynchronousCommand>
</FirstLogonCommands>
</component>
</settings>
<settings pass="specialize">
<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">
<RunSynchronous>
${lib.concatStringsSep "\n" (mkCommands commands)}
</RunSynchronous>
</component>
<component name="Microsoft-Windows-SQMApi" 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">
<CEIPEnabled>0</CEIPEnabled>
</component>
</settings>
<!-- Disable Windows UAC -->
<settings pass="offlineServicing">
<component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<EnableLUA>false</EnableLUA>
</component>
</settings>
<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.runCommandNoCC "autounattend.xml" {} ''
${pkgs.libxml2}/bin/xmllint --format ${autounattendXML} > $out
'';
# autounattend.xml is _super_ picky about quotes and other things
setupScript = pkgs.writeText "ssh-setup.ps1" (
''
# Setup SSH and keys
'' +
lib.concatStrings (
builtins.map (c: ''
# ${c.Description}
${c.Path}
'') sshSetupCommands
)
);
}