username/password move from the sealed image to makeConfigMedium, so two
VMs get distinct logins (and, as before, distinct SIDs). The sealed
image bakes only a generic bootstrap account ("vmixsetup") whose sole job
is to carry OOBE to a logon; post-oobe then creates the real account from
the config CD, switches autologon to it, and reboots so it builds its own
SID-bound profile on D:\Users\<username>. A one-shot cleanup (RunOnce,
first logon of the real account) retires the bootstrap and its profile
and applies the per-user tint. So nothing about the account is shared or
baked, and the on-disk profile folder matches the real username.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117qMyjpuXsjpVAcpJbFD8g
59 lines
2.8 KiB
Nix
59 lines
2.8 KiB
Nix
# Per-VM config medium for a sealed Windows image (see templates.seal).
|
|
#
|
|
# A sealed image carries no per-VM data. The values that differ between VMs --
|
|
# hostname, the static address the guest asserts, timezone, desktop tint -- are
|
|
# written here as a PowerShell data file and packed into a tiny ISO. config.nix
|
|
# attaches it as a read-only CD-ROM; the image's baked first-boot scripts
|
|
# (vmix-load-config.cmd finds it, then dot-source it) apply the values. So one
|
|
# sealed store path is shared by every VM, and only this cheap ISO is per-VM.
|
|
#
|
|
# Usage:
|
|
# makeConfigMedium {
|
|
# name = "win-config";
|
|
# hostname = "panda-win";
|
|
# staticIP = { address = "10.10.10.26"; prefixLength = 24;
|
|
# gateway = "10.10.10.1"; dns = [ "10.10.10.1" ]; };
|
|
# timezone = "E. South America Standard Time";
|
|
# bgColor = "#856558";
|
|
# }
|
|
{ pkgs, lib, makeFilesISO, ... }:
|
|
{
|
|
name ? "vmix-config",
|
|
hostname ? "",
|
|
# The per-VM account. The sealed image carries a generic bootstrap account
|
|
# (only there to carry OOBE); on first boot this real account is created from
|
|
# here, gets the SID-bound profile on D:\Users\<username>, and the bootstrap
|
|
# is retired. Distinct per VM -- nothing about the account is shared/baked.
|
|
username ? "",
|
|
password ? "",
|
|
# { address; prefixLength; gateway; dns = [ ... ]; }
|
|
staticIP ? null,
|
|
timezone ? null,
|
|
# Solid desktop background as a hex string, e.g. "#856558". Converted to the
|
|
# registry's decimal "R G B" on the target, in vmix-apply-config.ps1.
|
|
bgColor ? null,
|
|
}:
|
|
let
|
|
dnsList = lib.optionalString (staticIP != null)
|
|
(lib.concatMapStringsSep "," (s: "'${s}'") staticIP.dns);
|
|
|
|
# Consumed by dot-sourcing (. C:\vmix-config.ps1), so it only assigns
|
|
# variables. Anything not set here is simply absent, and the baked scripts
|
|
# guard on that ($VmixIpAddress being null skips the static-IP assignment).
|
|
configPs1 = pkgs.writeText "vmix-config.ps1" ''
|
|
# vmix per-VM config -- generated, read by the sealed image's baked scripts.
|
|
$VmixHostname = '${hostname}'
|
|
${lib.optionalString (username != "") "$VmixUsername = '${username}'"}
|
|
${lib.optionalString (username != "") "$VmixPassword = '${password}'"}
|
|
${lib.optionalString (staticIP != null) ''
|
|
$VmixIpAddress = '${staticIP.address}'
|
|
$VmixPrefixLength = ${toString staticIP.prefixLength}
|
|
$VmixGateway = '${staticIP.gateway}'
|
|
$VmixDns = @(${dnsList})''}
|
|
${lib.optionalString (timezone != null) "$VmixTimeZone = '${timezone}'"}
|
|
${lib.optionalString (bgColor != null) "$VmixBgColor = '${bgColor}'"}
|
|
'';
|
|
in
|
|
# makeFilesISO strips the store-hash prefix, so this lands at the ISO root as
|
|
# exactly vmix-config.ps1 -- which is what vmix-load-config.cmd scans for.
|
|
makeFilesISO { inherit name; files = [ configPs1 ]; }
|