update to nixos-24.05,

make installation in VM more versatile,
allow imports in host config file based on `name`
This commit is contained in:
Niklas Gollenstede 2024-05-31 22:41:35 +02:00
parent d0ba074777
commit 65c1691644
12 changed files with 125 additions and 91 deletions

View File

@ -76,6 +76,7 @@
"gdisk", // program
"getsize64", // cli arg
"getty", // program
"github", // name
"gnugrep", // package
"gnused", // package
"gollenstede", // name
@ -112,6 +113,7 @@
"mountpoint", // program / function
"msize", // option
"mtab", // linux
"nameserver", // concat
"namespacing", // word
"netdev", // cli arg
"niklas", // name
@ -137,6 +139,7 @@
"optimise", // B/E
"ostype", // virtual box
"overlayed", // word
"overridable", // word
"ovmf", // package
"partlabel", // linux
"partprobe", // program / function
@ -162,6 +165,7 @@
"reexec", // option
"refreservation", // zfs
"relatime", // mount option
"resolv", // abbr
"rootfs", // linux
"rpool", // zfs
"sandboxing", // word

Binary file not shown.

View File

@ -2,7 +2,7 @@
"Fully automated NixOS CLI installer"
); inputs = {
nixpkgs = { url = "github:NixOS/nixpkgs/nixos-23.11"; };
nixpkgs = { url = "github:NixOS/nixpkgs/nixos-24.05"; };
functions = { url = "github:NiklasGollenstede/nix-functions"; inputs.nixpkgs.follows = "nixpkgs"; };
config.url = "github:NiklasGollenstede/nixos-installer?dir=example/defaultConfig"; # "path:./example/defaultConfig"; # (The latter only works on each host after using this flake directly (not as dependency or another flake). The former effectively points to the last commit, i.e. it takes two commits to apply changes to the default config.)

View File

@ -1,7 +1,6 @@
dirname: inputs@{ self, nixpkgs, functions, ...}: let
inherit (nixpkgs) lib;
inherit (functions.lib) extractBashFunction forEachSystem getModulesFromInputs getNixFiles getOverlaysFromInputs importWrapped mapMerge mapMergeUnique mergeAttrsUnique substituteImplicit;
setup-scripts = (import "${dirname}/setup-scripts" "${dirname}/setup-scripts" inputs);
inherit (functions.lib) forEachSystem getModulesFromInputs getNixFiles getOverlaysFromInputs importWrapped mapMerge mapMergeUnique mergeAttrsUnique; # trace;
inherit (inputs.config.rename) installer; preface' = inputs.config.rename.preface;
getModuleConfig = module: inputs: args: if builtins.isFunction module then (
@ -33,8 +32,12 @@ in rec {
#system = null; # (This actually does nothing more than setting »config.nixpkgs.system« (which is the same as »config.nixpkgs.buildPlatform.system«) and can be null/unset here.)
modules = (nixosArgs.modules or [ ]) ++ [ { imports = [ # Anything specific to only this evaluation of the module tree should go here.
(if (builtins.isPath mainModule) || (builtins.isString mainModule) then (importWrapped inputs mainModule).module else mainModule)
{ _module.args.name = lib.mkOverride 99 name; } # (specialisations can somehow end up with the name »configuration«, which is very incorrect)
(let
module = if (builtins.isPath mainModule) || (builtins.isString mainModule) then (importWrapped inputs mainModule).module else mainModule;
bindName = func: lib.setFunctionArgs (args: func (args // { inherit name; })) (builtins.removeAttrs (lib.functionArgs func) [ "name" ]);
bindToModule = module: if lib.isFunction module then bindName module else if module?imports then module // { imports = map bindToModule module.imports; } else module;
in bindToModule module) # ensure that in the main module, the "name" parameter is available during the import stage already
{ _module.args.name = lib.mkOverride 0 name; } # (specialisations can somehow end up with the name »configuration«, which is very incorrect)
{ networking.hostName = name; }
]; _file = "${dirname}/nixos.nix#modules"; } ];
@ -101,8 +104,8 @@ in rec {
# Builds a system of NixOS hosts and exports them, plus »apps« and »devShells« to manage them, as flake outputs.
# All arguments are optional, as long as the default can be derived from the other arguments as passed.
mkSystemsFlake = args@{
# An attrset of imported Nix flakes, for example the argument(s) passed to the flake »outputs« function. All other arguments are optional (and have reasonable defaults) if this is provided and contains »self« and the standard »nixpkgs«. This is also the second argument passed to the individual host's top level config files.
mkSystemsFlake = lib.makeOverridable (args@{
# An attrset of imported Nix flakes, for example the argument(s) passed to the flake »outputs« function. All other arguments are optional (and have reasonable defaults) if this is provided and contains »self« and the standard »nixpkgs«. This is also the second argument passed to the individual hosts' top level config files.
inputs ? { },
# Arguments »{ files, dir, exclude, }« to »mkNixosConfigurations«, see there for details. May also be a list of those attrsets, in which case those multiple sets of hosts will be built separately by »mkNixosConfigurations«, allowing for separate sets of »peers« passed to »mkNixosConfiguration«. Each call will receive all other arguments, and the resulting sets of hosts will be merged.
hosts ? ({ dir = "${getFlakeDir inputs.self "Can't determine flake dir from »inputs.self«. Supply »mkSystemsFlake.hosts.dir« explicitly!"}/hosts"; exclude = [ ]; }),
@ -141,25 +144,18 @@ in rec {
inherit nixosConfigurations;
} // (forEachSystem setupPlatforms (buildSystem: let
pkgs = (import inputs.nixpkgs { inherit overlays; system = buildSystem; });
tools = lib.unique (map (p: p.outPath) (lib.filter lib.isDerivation pkgs.stdenv.allowedRequisites));
in rec {
apps = lib.mapAttrs (name: system: rec { type = "app"; derivation = writeSystemScripts { inherit name pkgs system; }; program = "${derivation}"; }) nixosConfigurations;
# dummy that just pulls in all system builds
packages = let all-systems = pkgs.runCommandLocal "all-systems" { } ''
${''
mkdir -p $out/systems
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: system: "ln -sT ${system.config.system.build.toplevel} $out/systems/${getName name}") nixosConfigurations)}
''}
${''
mkdir -p $out/scripts
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: system: "ln -sT ${apps.${name}.program} $out/scripts/${getName name}") nixosConfigurations)}
''}
${lib.optionalString (inputs != { }) ''
mkdir -p $out/inputs
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: { outPath, ... }: "ln -sT ${outPath} $out/inputs/${name}") inputs)}
''}
mkdir -p $out/systems
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: system: "ln -sT ${system.config.system.build.toplevel} $out/systems/${getName name}") nixosConfigurations)}
mkdir -p $out/scripts
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: system: "ln -sT ${apps.${name}.program} $out/scripts/${getName name}") nixosConfigurations)}
mkdir -p $out/inputs
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: { outPath, ... }: "ln -sT ${outPath} $out/inputs/${name}") inputs)}
''; in { inherit all-systems; } // (lib.optionalAttrs asDefaultPackage { default = all-systems ; });
checks.all-systems = packages.all-systems;
@ -169,7 +165,7 @@ in rec {
apps = mapMergeUnique (k: v: { ${renameOutputs k} = v; }) outputs.apps.${buildSystem};
packages.${renameOutputs "all-systems"} = outputs.packages.${buildSystem}.all-systems;
checks.${renameOutputs "all-systems"} = outputs.checks.${buildSystem}.all-systems;
}));
})));
# This makes the »./setup-scripts/*« callable from the command line:
writeSystemScripts = {

View File

@ -8,7 +8,7 @@ declare-flag install-system skip-formatting "" "Skip partitioning, formatting, a
## Prepares the disks of the target system for the copying of files.
function do-disk-setup { # 1: diskPaths
ensure-disks "$1" || return
ensure-disks || return
export mnt=/tmp/nixos-install-@{config.networking.hostName} && mkdir -p "$mnt" && prepend_trap "rmdir $mnt" EXIT || return # »mnt=/run/user/0/...« would be more appropriate, but »nixos-install« does not like the »700« permissions on »/run/user/0«
@ -51,14 +51,14 @@ function do-disk-setup { # 1: diskPaths
declare-flag install-system image-owner "" "When using image files, »chown« them to this »owner[:group]« before the installation."
## Parses and expands »diskPaths« to ensure that a disk or image exists for each »config.setup.disks.devices«, creates and loop-mounts images for non-/dev/ paths, and checks whether physical device sizes match.
function ensure-disks { # 1: diskPaths, 2?: skipLosetup
function ensure-disks {
declare -g -A blockDevs=( ) # this ends up in the caller's scope
if [[ $1 == */ ]] ; then
mkdir -p "$1"
for name in "@{!config.setup.disks.devices[@]}" ; do blockDevs[$name]=${1}${name}.img ; done
if [[ ${args[disks]} == */ ]] ; then
mkdir -p "${args[disks]}"
for name in "@{!config.setup.disks.devices[@]}" ; do blockDevs[$name]=${args[disks]}${name}.img ; done
else
local path ; for path in ${1//:/ } ; do
local path ; for path in ${args[disks]//:/ } ; do
local name=${path/=*/} ; if [[ $name != "$path" ]] ; then path=${path/$name=/} ; else name=primary ; fi
if [[ ${blockDevs[$name]:-} ]] ; then echo "Path for block device $name specified more than once. Duplicate definition: $path" 1>&2 ; \return 1 ; fi
blockDevs[$name]=$path
@ -73,7 +73,7 @@ function ensure-disks { # 1: diskPaths, 2?: skipLosetup
local outFile=${blockDevs[$name]} &&
install -m 640 -T /dev/null "$outFile" && truncate -s "${disk[size]}" "$outFile" || return
if [[ ${args[image-owner]:-} ]] ; then chown "${args[image-owner]}" "$outFile" || return ; fi
if [[ ${2:-} ]] ; then continue ; fi
if [[ ${arg_skipLosetup:-} ]] ; then continue ; fi
blockDevs[$name]=$( @{native.util-linux}/bin/losetup --show -f "$outFile" ) && prepend_trap "@{native.util-linux}/bin/losetup -d '${blockDevs[$name]}'" EXIT || return
else
local size=$( @{native.util-linux}/bin/blockdev --getsize64 "${blockDevs[$name]}" || : ) ; local waste=$(( size - ${disk[size]} ))

View File

@ -3,12 +3,9 @@
# NixOS Installation
##
declare-command install-system diskPaths << 'EOD'
declare-command install-system '[--disks=]diskPaths' << 'EOD'
This command installs a NixOS system to local disks or image files.
It gets all the information it needs from the system's NixOS configuration -- except for the path(s) of the target disk(s) / image file(s).
If »diskPaths« points to something in »/dev/«, then it is directly formatted and written to as block device, otherwise »diskPaths« is (re-)created as raw image and then used as loop device.
For hosts that install to multiple disks, pass a :-separated list of »<disk-name>=<path>« pairs (the name may be omitted only for the "default" disk).
It gets all the information it needs from the system's NixOS configuration -- except for the path(s) of the target disk(s) / image file(s); see the »--disks=« flag.
Since the installation needs to format and mount (image files as) disks, it needs some way of elevating permissions. It can:
* be run as »root«, requiring Nix to be installed system-wide / for root,
@ -25,10 +22,13 @@ What the installation does is defined solely by the target host's NixOS configur
The "Installation" section of each host's documentation should contain host specific details, if any.
Various »FLAG«s below affect how the installation is performed (in VM, verbosity, debugging, ...).
EOD
declare-flag install-system disks "diskPaths" "The disk(s) (to be) used by this system installation.
If »diskPaths« points to something in »/dev/«, then it is directly used as block device, otherwise »diskPaths« is (re-)created as raw image file and then used as loop device.
For hosts that install to multiple disks, pass a :-separated list of »<disk-name>=<path>« pairs (the name may be omitted only for the "default" disk)."
function install-system {( # 1: diskPaths
trap - EXIT # start with empty traps for sub-shell
prepare-installer "$@" || exit
do-disk-setup "$1" || exit
do-disk-setup || exit
install-system-to $mnt || exit
)}
@ -42,15 +42,15 @@ declare-flag install-system no-vm "" "Never perform the installation in a VM. Fa
## Does some argument validation, performs some sanity checks, includes a hack to make installation work when nix isn't installed for root, and runs the installation in qemu (if requested).
function prepare-installer { # 1: diskPaths
: ${1:?"The first positional argument must specify the path(s) to the disk(s) and/or image file(s) to install to"}
if [[ ! ${args[disks]:-} ]] ; then args[disks]=${1:?"The disks flag or the first positional argument must specify the path(s) to the disk(s) and/or image file(s) to install to"} ; shift ; fi
umask g-w,o-w # Ensure that files created without explicit permissions are not writable for group and other (0022).
umask g-w,o-w # Ensure that files created without explicit permissions are not writable for group and other.
if [[ "$(id -u)" != '0' ]] ; then
if [[ ! ${args[no-vm]:-} ]] ; then reexec-in-qemu "$@" || return ; \exit 0 ; fi
if [[ ! ${args[no-vm]:-} ]] ; then exec-in-qemu install-system || return ; \exit 0 ; fi
echo 'Script must be run as root or in qemu (without »--no-vm«).' 1>&2 ; \return 1
fi
if [[ ${args[vm]:-} ]] ; then reexec-in-qemu "$@" || return ; \exit 0 ; fi
if [[ ${args[vm]:-} ]] ; then exec-in-qemu install-system || return ; \exit 0 ; fi
if [[ -e "/run/keystore-@{config.networking.hostName!hashString.sha256:0:8}" ]] ; then echo "Keystore »/run/keystore-@{config.networking.hostName!hashString.sha256:0:8}/« is already open. Close it and remove the mountpoint before running the installer." 1>&2 ; \return 1 ; fi
@ -62,7 +62,7 @@ function prepare-installer { # 1: diskPaths
if @{native.zfs}/bin/zfs get -o value -H name "$poolName" &>/dev/null ; then echo "ZFS pool »$poolName« is already imported. Export the pool before running the installer." 1>&2 ; \return 1 ; fi
done
if [[ ${SUDO_USER:-} && ! $( PATH=$hostPath which nix 2>/dev/null ) && $( PATH=$hostPath which su 2>/dev/null ) ]] ; then # use Nix as the user who called this script, as Nix may not be set up for root
if [[ ${SUDO_USER:-} && ! $( PATH=$hostPath which nix 2>/dev/null ) && $( PATH=$hostPath which su 2>/dev/null ) ]] ; then # use Nix as the user who called this script, if Nix is not be set up for root
function nix {( set +x ; declare -a args=("$@") ; PATH=$hostPath su - "$SUDO_USER" -s "@{native.bashInteractive!getExe}" -c "$(declare -p args)"' ; nix "${args[@]}"' )}
else # use Nix by absolute path, as it won't be on »$PATH«
PATH=$PATH:@{native.nix}/bin
@ -75,28 +75,32 @@ function prepare-installer { # 1: diskPaths
declare-flag install-system vm-shared "dir-path" "When installing inside the VM, specifies a host path that is read-write mounted at »/tmp/shared« inside the VM."
declare-flag install-system vm-args "qemu-args" "When installing inside the VM, extra arguments to pass to qemu."
## Re-executes the current system's installation in a qemu VM.
function reexec-in-qemu {
# (not sure whether this works for block devices)
ensure-disks "$1" 1 || return
qemu=( -m 3072 ) ; declare -A qemuDevs=( )
local index=2 ; local name ; for name in "${!blockDevs[@]}" ; do
#if [[ ${blockDevs[$name]} != /dev/* ]] ; then
qemu+=( # not sure how correct the interpretations of the command are
-drive format=raw,file="$( realpath "${blockDevs[$name]}" )",media=disk,if=none,index=${index},id=drive${index} # create the disk drive, without attaching it, name it driveX
#-device ahci,acpi-index=${index},id=ahci${index} # create an (ich9-)AHCI bus named »ahciX«
#-device ide-hd,drive=drive${index},bus=ahci${index}.${index} # attach IDE?! disk driveX as device X on bus »ahciX«
-device virtio-blk-pci,drive=drive${index},disable-modern=on,disable-legacy=off # alternative to the two lines above (implies to be faster, but seems to require guest drivers)
)
qemuDevs[$name]=/dev/vd$( printf "\x$(printf %x $(( index - 1 + 97 )) )" ) # a is used by the (unused) root disk
let index+=1
done
## (Re-)executes the current system's script in a qemu VM.
function exec-in-qemu { # 1: entry, ...: argv
qemu=( ) ; apply-vm-args
args[vm]='' ; args[no-vm]=1
newArgs=( ) ; for arg in "${!args[@]}" ; do newArgs+=( --"$arg"="${args[$arg]}" ) ; done
devSpec= ; for name in "${!qemuDevs[@]}" ; do devSpec+="$name"="${qemuDevs[$name]}": ; done
newArgs+=( ${devSpec%:} ) ; shift ; (( $# == 0 )) || args+=( "$@" ) # (( ${#argv[@]} > 1 )) && args+=( "${argv[@]:1}" )
if [[ ${args[disks]:-} ]] ; then
# (not sure whether this works for block devices)
arg_skipLosetup=1 ensure-disks || return
args[disks]=''
local index=2 # 1/a is used by the (unused) root disk
local name ; for name in "${!blockDevs[@]}" ; do
#if [[ ${blockDevs[$name]} != /dev/* ]] ; then
qemu+=( # not sure how correct the interpretations of the command are
-drive format=raw,file="$( realpath "${blockDevs[$name]}" )",media=disk,if=none,index=${index},id=drive${index} # create the disk drive, without attaching it, name it driveX
#-device ahci,acpi-index=${index},id=ahci${index} # create an (ich9-)AHCI bus named »ahciX«
#-device ide-hd,drive=drive${index},bus=ahci${index}.${index} # attach IDE?! disk driveX as device X on bus »ahciX«
-device virtio-blk-pci,drive=drive${index},disable-modern=on,disable-legacy=off # alternative to the two lines above (implies to be faster, but seems to require guest drivers)
)
args[disks]+="$name"=/dev/vd"$( printf "\x$(printf %x $(( index - 1 + 97 )) )" )": ; let index+=1
done
args[disks]=${args[disks]%:}
fi
newArgs=( ) ; (( $# == 0 )) || newArgs+=( "$@" )
for arg in "${!args[@]}" ; do newArgs+=( --"$arg"="${args[$arg]}" ) ; done
#local output=@{inputs.self}'#'nixosConfigurations.@{config.installer.outputName:?}.config.system.build.vmExec
local output=@{config.system.build.vmExec.drvPath!unsafeDiscardStringContext} # this is more accurate, but also means another system needs to get evaluated every time
@ -107,7 +111,7 @@ function reexec-in-qemu {
local scripts=$self ; if [[ @{pkgs.system} != "@{native.system}" ]] ; then
scripts=$( build-lazy @{inputs.self}'#'apps.@{pkgs.system}.@{config.installer.outputName:?}.derivation ) || return
fi
local command="$scripts install-system $( printf '%q ' "${newArgs[@]}" ) || exit"
local command="$scripts $( printf '%q ' "${newArgs[@]}" ) || exit"
local runInVm ; runInVm=$( build-lazy $output )/bin/run-@{config.system.name}-vm-exec || return
@ -119,10 +123,10 @@ function reexec-in-qemu {
function nixos-install-cmd {( # 1: mnt, 2: topLevel
# »nixos-install« by default does some stateful things (see »--no-root-passwd« »--no-channel-copy«), builds and copies the system config, registers the system (»nix-env --profile /nix/var/nix/profiles/system --set $targetSystem«), and then calls »NIXOS_INSTALL_BOOTLOADER=1 nixos-enter -- $topLevel/bin/switch-to-configuration boot«, which is essentially the same as »NIXOS_INSTALL_BOOTLOADER=1 nixos-enter -- @{config.system.build.installBootLoader} $targetSystem«, i.e. the side effects of »nixos-enter« and then calling the bootloader-installer.
#PATH=@{native.nix}/bin:$PATH:@{config.systemd.package}/bin TMPDIR=/tmp LC_ALL=C @{native.nixos-install-tools}/bin/nixos-install --system "$2" --no-root-passwd --no-channel-copy --root "$1" || exit # We did most of this, so just install the bootloader:
#PATH=@{native.nix}/bin:$PATH:@{config.systemd.package}/bin TMPDIR=/tmp LC_ALL=C @{native.nixos-install-tools-no-doc}/bin/nixos-install --system "$2" --no-root-passwd --no-channel-copy --root "$1" || exit # We did most of this, so just install the bootloader:
export NIXOS_INSTALL_BOOTLOADER=1 # tells some bootloader installers (systemd & grub) to not skip parts of the installation
LC_ALL=C PATH=@{native.busybox}/bin:$PATH:@{native.util-linux}/bin @{native.nixos-install-tools}/bin/nixos-enter --silent --root "$1" -c "source /etc/set-environment ; ${_set_x:-:} ; @{config.system.build.installBootLoader} $2" || exit
LC_ALL=C PATH=@{native.busybox}/bin:$PATH:@{native.util-linux}/bin @{native.nixos-install-tools-no-doc}/bin/nixos-enter --silent --root "$1" -c "source /etc/set-environment ; ${_set_x:-:} ; @{config.system.build.installBootLoader} $2" || exit
# (newer versions of »mount« seem to be unable to do »--make-private« on »rootfs« (in the initrd), but busybox's mount still works)
)}
@ -201,7 +205,7 @@ function install-system-to {( set -u # 1: mnt, 2?: topLevel
else
( set +x ; echo "Installation done! This shell is in a chroot in the mounted system for inspection. Exiting the shell will unmount the system." 1>&2 )
fi
LC_ALL=C PATH=@{native.busybox}/bin:$PATH:@{native.util-linux}/bin @{native.nixos-install-tools}/bin/nixos-enter --root $mnt -- /nix/var/nix/profiles/system/sw/bin/bash -c 'source /etc/set-environment ; NIXOS_INSTALL_BOOTLOADER=1 CHROOT_DIR="'"$mnt"'" mnt=/ exec "'"$self"'" bash' || exit # +o monitor
LC_ALL=C PATH=@{native.busybox}/bin:$PATH:@{native.util-linux}/bin @{native.nixos-install-tools-no-doc}/bin/nixos-enter --root $mnt -- /nix/var/nix/profiles/system/sw/bin/bash -c 'source /etc/set-environment ; NIXOS_INSTALL_BOOTLOADER=1 CHROOT_DIR="'"$mnt"'" mnt=/ exec "'"$self"'" bash' || exit # +o monitor
fi
mkdir -p $mnt/var/lib/systemd/timesync && touch $mnt/var/lib/systemd/timesync/clock || true # save current time

View File

@ -57,8 +57,7 @@ declare-flag run-qemu efi "" "Treat the target system as EFI syste
declare-flag run-qemu efi-vars "path" "For »--efi« systems, path to a file storing the EFI variables. The default is in »XDG_RUNTIME_DIR«, i.e. it does not persist across host reboots."
declare-flag run-qemu graphic "" "Open a graphical window even of the target system logs to serial and not (explicitly) TTY1."
declare-flag run-qemu install "[1|always]" "If any of the guest system's disk images does not exist, perform the its installation before starting the VM. If set to »always«, always install before starting the VM. With this flag set, »diskImages« defaults to paths in »/tmp/."
declare-flag run-qemu mem "num" "VM RAM in MiB (»qemu -m«)."
declare-flag run-qemu no-kvm "" "Do not rey to use (or complain about the unavailability of) KVM."
declare-flag run-qemu no-kvm "" "Do not try to use (or complain about the unavailability of) KVM."
declare-flag run-qemu nat-fw "forwards" "Port forwards to the guest's NATed NIC. E.g: »--nat-fw=:8000-:8000,:8001-:8001,127.0.0.1:2022-:22«."
declare-flag run-qemu no-nat "" "Do not provide a NATed NIC to the guest."
declare-flag run-qemu nic "type[,options]" "Create an additional network interface using the »-nic« flag. Automatically sets a decent »model« and a »mac« derived from »config.networking.hostId«.
@ -67,12 +66,9 @@ $ ... --nic=socket,listen=:4321 # once
$ ... --nic=socket,connect=:4321 # once
Example 2 (connect many VMs, unprivileged):
$ nix shell nixpkgs#vde2 --command vde_switch -sock /tmp/vm-net
$ ... --nic=vde,sock=/tmp/vm-net # multiple times
"
$ ... --nic=vde,sock=/tmp/vm-net # multiple times"
declare-flag run-qemu no-serial "" "Do not connect the calling terminal to a serial adapter the guest can log to and open a terminal on the guests serial, as would be the default if the guests logs to ttyS0."
declare-flag run-qemu share "decls" "Host dirs to make available as network shares for the guest, as space separated list of »name:host-path,options. E.g. »--share='foo:/home/user/foo,readonly=on bar:/tmp/bar«. In the VM hte share can be mounted with: »$ mount -t 9p -o trans=virtio -o version=9p2000.L -o msize=4194304 -o ro foo /foo«."
declare-flag run-qemu smp "num" "Number of guest CPU cores."
declare-flag run-qemu usb-port "path" "A physical USB port (or hub) to pass to the guest (e.g. a YubiKey for unlocking). Specified as »<bus>-<port>«, where bus and port refer to the physical USB port »/sys/bus/usb/devices/<bus>-<port>« (see »lsusb -tvv«). E.g.: »--usb-port=3-1.1.1.4«."
declare-flag run-qemu virtio-blk "" "Pass the system's disks/images as virtio disks, instead of using AHCI+IDE. Default iff »boot.initrd.availableKernelModules« includes »virtio_blk« (because it requires that driver)."
function run-qemu {
if [[ ${args[install]:-} && ! ${argv[0]:-} ]] ; then argv[0]=/tmp/nixos-vm/@{config.installer.outputName:-@{config.system.name}}/ ; fi
@ -99,9 +95,6 @@ function run-qemu {
qemu+=( -machine type=virt ) # aarch64 has no default, but this seems good
fi ; qemu+=( -cpu max )
qemu+=( -m ${args[mem]:-2048} )
if [[ ${args[smp]:-} ]] ; then qemu+=( -smp ${args[smp]} ) ; fi
if [[ @{config.virtualisation.useEFIBoot:-} || @{config.boot.loader.systemd-boot.enable} || ${args[efi]:-} ]] ; then # UEFI. Otherwise it boots SeaBIOS.
local ovmf ; ovmf=$( build-lazy @{pkgs.OVMF.drvPath!unsafeDiscardStringContext} fd ) || return
#qemu+=( -bios ${ovmf}/FV/OVMF.fd ) # This works, but is a legacy fallback that stores the EFI vars in /NvVars on the EFI partition (which is really bad).
@ -159,18 +152,15 @@ function run-qemu {
qemu+=( -nic model=virtio-net-pci,mac=$mac,type="${args[nic]}" )
fi
# To pass a USB device (e.g. a YubiKey for unlocking), add pass »--usb-port=${bus}-${port}«, where bus and port refer to the physical USB port »/sys/bus/usb/devices/${bus}-${port}« (see »lsusb -tvv«). E.g.: »--usb-port=3-1.1.1.4«
if [[ ${args[usb-port]:-} ]] ; then local decl ; for decl in ${args[usb-port]//:/ } ; do
qemu+=( -usb -device usb-host,hostbus="${decl/-*/}",hostport="${decl/*-/}" )
done ; fi
apply-vm-args
if [[ ${args[install]:-} == 1 ]] ; then local disk ; for disk in "${disks[@]}" ; do
if [[ ! -e $disk ]] ; then args[install]=always ; fi
done ; fi
if [[ ${args[install]:-} == always ]] ; then
local verbosity=--quiet ; if [[ ${args[trace]:-} ]] ; then verbosity=--trace ; fi ; if [[ ${args[debug]:-} ]] ; then verbosity=--debug ; fi
hostPath=${hostPath:-} ${args[dry-run]:+echo} "$self" install-system "$diskImages" $verbosity --no-inspect || return
fi
if [[ ${args[install]:-} == always ]] && [[ ! ${args[dry-run]:-} ]] ; then (
if [[ ! ${args[trace]:-} ]] && [[! ${args[debug]:-} ]] ; then args[quiet]=1 ; fi
args[no-inspect]=1 ; install-system "$diskImages" || exit
) || return ; fi
qemu+=( "${argv[@]}" )
if [[ ${args[dry-run]:-} ]] ; then
@ -182,6 +172,18 @@ function run-qemu {
# https://askubuntu.com/questions/54814/how-can-i-ctrl-alt-f-to-get-to-a-tty-in-a-qemu-session
}
declare-flag run-qemu,install-system,'*' vm-mem "num" "VM RAM in MiB (»qemu -m«)."
declare-flag run-qemu,install-system,'*' vm-smp "num" "Number of guest CPU cores."
declare-flag run-qemu,install-system,'*' vm-usb-port "path" "A physical USB port (or hub) to pass to the guest (e.g. a YubiKey for unlocking). Specified as »<bus>-<port>«, where bus and port refer to the physical USB port »/sys/bus/usb/devices/<bus>-<port>« (see »lsusb -tvv«). E.g.: »--vm-usb-port=3-1.1.1.4«."
function apply-vm-args {
qemu+=( -m ${args[vm-mem]:-2048} )
if [[ ${args[vm-smp]:-} ]] ; then qemu+=( -smp ${args[vm-smp]} ) ; fi
if [[ ${args[vm-usb-port]:-} ]] ; then local decl ; for decl in ${args[vm-usb-port]//:/ } ; do
qemu+=( -usb -device usb-host,hostbus="${decl/-*/}",hostport="${decl/*-/}" )
done ; fi
}
declare-command add-bootkey-to-keydev blockDev << 'EOD'
Creates a random static key on a new key partition on the GPT partitioned »$blockDev«. The drive can then be used as headless but removable disk unlock method (»usbPartition«/»usb-part«).
To create/clear the GPT beforehand, run: $ sgdisk --zap-all "$blockDev"

View File

@ -47,8 +47,9 @@ function prompt-new-password {( set -u # 1: usage
)}
## If »secretFile« does not exist, interactively prompts up to three times for the secret to be stored in that file.
declare-flag '*' no-optional-prompts "" "Skip prompting for (and thus saving) secret marked as optional."
function prompt-secret-as {( set -u # 1: what, 2: secretFile, 3?: owner[:[group]], 4?: mode
if [[ -e $2 ]] ; then \return ; fi
if [[ ${arg_optional:-} && ${args[no-optional-prompts]:-} ]] ; then \return ; fi ; if [[ -e $2 ]] ; then \return ; fi
what=$1 ; shift
function prompt {
read -s -p "Please enter $what: " value || exit ; echo 1>&2

View File

@ -170,7 +170,7 @@ in let module = {
initrd-cleanup.preStart = ''
umount ${keystore} || true
rmdir ${keystore} || true
${config.systemd.package}/lib/systemd/systemd-cryptsetup detach keystore-${hash}
systemd-cryptsetup detach keystore-${hash}
'';
};
boot.initrd.luks.devices."keystore-${hash}".keyFileTimeout = 10;

View File

@ -61,6 +61,7 @@ in let hostModule = {
if [[ ! ''${args[initrd-console]:-} ]] ; then noConsole=1 ; fi
if [[ ''${args[initrd-console]:-} ]] ; then touch $tmp/xchg/initrd-console ; fi
if [[ ''${args[quiet]:-} ]] ; then touch $tmp/xchg/quiet ; fi
</etc/hosts grep -oP '127.0.0.[12] (?!localhost)\K.*' >$tmp/xchg/host
${cfg.virtualisation.qemu.package}/bin/qemu-img create -f qcow2 $tmp/dummyImage 4M &>/dev/null # do this silently
@ -69,7 +70,9 @@ in let hostModule = {
export QEMU_KERNEL_PARAMS="init=${config.system.build.toplevel}/init ''${noConsole:+console=tty1} edd=off boot.shell_on_fail"
export QEMU_NET_OPTS= QEMU_OPTS=
if [[ ''${args[quiet]:-} ]] ; then
${launch} "''${argv[@]}" &> >( ${pkgs.coreutils}/bin/tr -dc '[[:print:]]\r\n\t' | { while IFS= read line ; do if [[ $line == magic:cm4alv0wly79p6i4aq32hy36i* ]] ; then break ; fi ; done ; cat ; } ) || { e=$? ; echo "Execution of VM failed!" 1>&2 ; exit $e ; }
${launch} "''${argv[@]}" &> >( ${pkgs.coreutils}/bin/tr -dc '[[:print:]]\r\n\t' | {
while IFS= read line ; do if [[ $line == magic:cm4alv0wly79p6i4aq32hy36i* ]] ; then break ; fi ; done ; cat ;
} ) || { e=$? ; echo "Execution of VM failed!" 1>&2 ; exit $e ; }
else
${launch} "''${argv[@]}" || exit
fi
@ -90,7 +93,7 @@ in let hostModule = {
# Instead of tearing down the initrd environment, adjust some mounts and run the »command« in the initrd:
boot.initrd.systemd.enable = lib.mkVMOverride false;
boot.initrd.postMountCommands = ''
set -x
for fs in tmp/shared tmp/xchg nix/store.lower nix/var/nix/db.lower ; do
mkdir -p /$fs && mount --move $targetRoot/$fs /$fs || fail
done
@ -108,14 +111,24 @@ in let hostModule = {
toplevel=$(dirname $stage2Init)
ln -sfT $toplevel /run/current-system
ln -sfT $toplevel /run/booted-system
ln -sfT $toplevel/kernel-modules/lib/modules /lib/modules
rm -rf /lib/modules ; ln -sfT $toplevel/kernel-modules/lib/modules /lib/modules
# Also mostly dor debugging shells:
# Set up /etc:
mv /etc /etc.initrd
mkdir -p -m 755 /etc.work /etc.upper /etc
mount -t overlay overlay -o lowerdir=$toplevel/etc,workdir=/etc.work,upperdir=/etc.upper /etc
( cd /etc.initrd ; cp -a mtab udev /etc/ ) # (keep these)
# Set up NATed networking:
cat /etc/hosts >/etc/hosts.cp ; rm /etc/hosts ; mv /etc/hosts.cp /etc/hosts
perl -pe 's/127.0.0.[12](?! localhost)/# /' -i /etc/hosts
perl -pe 's/::1(?! localhost)/# /' -i /etc/hosts
{ printf '10.0.2.2 ' ; cat /tmp/xchg/host ; } >>/etc/hosts
ip addr add 10.0.2.15/24 dev eth0
ip link set dev eth0 up
ip route add default via 10.0.2.2 dev eth0
echo nameserver 1.1.1.1 >/etc/resolv.conf # 10.0.2.3 doesn't reply
# »nix copy« complains without »nixbld« group:
rm -f /etc/passwd /etc/group
printf '%s\n' 'root:x:0:0:root:/root:/bin/bash' >/etc/passwd
@ -126,7 +139,7 @@ in let hostModule = {
console=/dev/ttyS0 ; if [[ -e /tmp/xchg/initrd-console ]] ; then console=/dev/console ; fi # (does this even make a difference?)
if [[ -e /tmp/xchg/quiet ]] ; then printf '\n%s\n' 'magic:cm4alv0wly79p6i4aq32hy36i...' >$console ; fi
exit=0 ; bash /tmp/xchg/script <$console >$console 2>$console || exit=$?
set +x ; exit=0 ; bash /tmp/xchg/script <$console >$console 2>$console || exit=$?
echo $exit >/tmp/xchg/exit
sync ; sync
@ -135,6 +148,7 @@ in let hostModule = {
sleep infinity # the VM will halt very soon
'';
boot.initrd.kernelModules = [ "overlay" ]; # for writable »/etc«, chown of »/nix/store« and locks in »/nix/var/nix/db«
#boot.initrd.extraUtilsCommands = ''copy_bin_and_libs ${pkgs.perl}/bin/perl'';
}) ({
@ -148,6 +162,7 @@ in let hostModule = {
"/nix/store".mountPoint = lib.mkForce "/nix/store.lower";
}; # mount -t 9p -o trans=virtio -o version=9p2000.L -o msize=4194304 nix-var-nix-db /nix/var/nix/db
virtualisation.qemu.options = [ "-virtfs local,path=/nix/var/nix/db,security_model=none,mount_tag=nix-var-nix-db,readonly=on" ]; # (doing this manually to pass »readonly«, to not ever corrupt the host's Nix DBs)
boot.resumeDevice = lib.mkVMOverride "";
}) ({
@ -159,10 +174,13 @@ in let hostModule = {
}) ({
virtualisation = if (builtins.substring 0 5 pkgs.lib.version) > "22.05" then { host.pkgs = lib.mkDefault pkgs.buildPackages; } else { };
}) ({
virtualisation.host.pkgs = lib.mkDefault pkgs.buildPackages;
virtualisation.qemu.package = lib.mkIf (pkgs.buildPackages.system != pkgs.system) (cfg.virtualisation.host or { pkgs = pkgs.buildPackages; }).pkgs.qemu_full;
}) ({
#virtualisation.qemu.options = [ "-nic user,model=virtio-net-pci" ]; # NAT
}) ({
specialisation = lib.mkForce { };

View File

@ -12,9 +12,10 @@ GPT-FDisk patched to be able to move not only the primary, but also the backup p
dirname: inputs: final: prev: let
inherit (final) pkgs; lib = inputs.self.lib.__internal__;
debug = false;
enable = lib.versionOlder prev.gptfdisk.version "1.0.10"; # patch merged in that version
in {
gptfdisk = (
gptfdisk = if !enable then prev.gptfdisk else (
if debug then pkgs.enableDebugging else (x: x)
) (prev.gptfdisk.overrideAttrs (old: let
pname = "gptfdisk";
@ -31,6 +32,6 @@ in {
dontStrip = true;
} else { })));
libblockdev = prev.libblockdev.override { inherit (prev) gptfdisk; };
libblockdev = prev.libblockdev.override (lib.optionalAttrs enable { inherit (prev) gptfdisk; });
}

View File

@ -0,0 +1,8 @@
dirname: inputs: final: prev: let
lib = inputs.self.lib.__internal__;
in {
## The options reference manual takes long to generate, is also available online (in this exact form, as this version is the generic nixpkgs one), and has failed to generate on occasion.
nixos-install-tools-no-doc = (prev.nixos-install-tools.override (old: { nixos = args: lib.recursiveUpdate (old.nixos args) { config.system.build.manual.nixos-configuration-reference-manpage = null; }; }));
#nixos-install-tools-no-doc = (prev.nixos-install-tools.overrideAttrs (old: { pkgs = builtins.toJSON (builtins.tail (builtins.fromJSON old.pkgs)); }));
}