move example hosts and overwrite to /example/, misc

This commit is contained in:
Niklas Gollenstede 2024-08-28 13:12:07 +02:00
parent 95aa261987
commit c8addc3f98
15 changed files with 60 additions and 44 deletions

View File

@ -147,6 +147,7 @@
"partlabel", // linux
"partprobe", // program / function
"partuuid", // linux
"pbkdf", // option
"pflash", // cli arg
"pipefail", // bash
"pkgs", // nix

View File

@ -36,7 +36,7 @@ in { preface = { # (any »preface« options have to be defined here)
boot.loader.grub.enable = false;
# Example of adding and/or overwriting setup/maintenance functions:
#installer.scripts.install-overwrite = { path = ../example/install.sh.md; order = 1500; };
#installer.scripts.install-overwrite.path = ../lib/install.sh.md;
boot.initrd.systemd.enable = true;

View File

@ -4,7 +4,7 @@
# Installer Script Overrides (Example)
This is an example on how to customize the default installation process.
The [`config.installer.commands.*`](../modules/installer.nix.md) can be used for some per-host customization, but for further reaching changes that are supposed to affect all hosts in a configuration, it may be necessary or more appropriate to extend/override the [default installer functions](../lib/setup-scripts/).
The [`config.installer.commands.*`](../../modules/installer.nix.md) can be used for some per-host customization, but for further reaching changes that are supposed to affect all hosts in a configuration, it may be necessary or more appropriate to extend/override the [default installer functions](../../lib/setup-scripts/).
This file would need to be included in the configuration like this:
```nix
@ -32,7 +32,7 @@ declare-command my-thing withThese coolArguments << 'EOD'
This does my thing with these cool arguments -- duh!
EOD
declare-flag my-thing laser-color COLOR "Color of the laser used for my thing."
function run-qemu { # 1: withThese, 2: coolArguments
function my-thing { # 1: withThese, 2: coolArguments
local withThese=$1
local coolThings=$2
echo "I am playing $withThese to shoot $coolArguments with ${args[laser-color]:-red} lasers!"

Binary file not shown.

View File

@ -6,17 +6,18 @@
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.)
}; outputs = inputs@{ self, ... }: inputs.functions.lib.importRepo inputs ./. (repo@{ overlays, ... }: let
}; outputs = inputs@{ self, ... }: inputs.functions.lib.importRepo inputs ./. (repo': let
repo = repo'.override { applyToPackages = pkgs: packages: builtins.removeAttrs packages [ "libblockdev" ]; };
lib = repo.lib.__internal__;
in [ # Run »nix flake show --allow-import-from-derivation« to see what this merges to:
## Exports (things to reuse in other flakes):
(repo // { packages = lib.fun.packagesFromOverlay { inherit inputs; exclude = [ "libblockdev" ]; }; }) # lib.* nixosModules.* overlays.* packages.*
repo # lib.* nixosModules.* overlays.* packages.*
## Examples:
# The example host definitions from ./hosts/, plus their installers (apps):
(lib.self.mkSystemsFlake { inherit inputs; asDefaultPackage = true; }) # nixosConfigurations.* apps.*-linux.* devShells.*-linux.* packages.*-linux.all-systems/default
(lib.self.mkSystemsFlake { inherit inputs; hosts.dir = "${self}/example/hosts"; asDefaultPackage = true; }) # nixosConfigurations.* apps.*-linux.* devShells.*-linux.* packages.*-linux.all-systems/default
# The same cross-compiled from aarch64 (just to show how that works):
(lib.self.mkSystemsFlake { inherit inputs; buildPlatform = "aarch64-linux"; renameOutputs = name: "arm:${name}"; }) # nixosConfigurations.arm:* apps.*-linux.arm:* devShells.*-linux.arm:* packages.*-linux.arm:all-systems
(lib.self.mkSystemsFlake { inherit inputs; hosts.dir = "${self}/example/hosts"; buildPlatform = "aarch64-linux"; renameOutputs = name: "arm:${name}"; }) # nixosConfigurations.arm:* apps.*-linux.arm:* devShells.*-linux.arm:* packages.*-linux.arm:all-systems
]); }

View File

@ -28,20 +28,26 @@ in rec {
extraModules ? [ ], moduleArgs ? { }, nixosArgs ? { },
nixosSystem ? inputs.nixpkgs.lib.nixosSystem,
buildPlatform ? null,
}: nixosSystem (nixosArgs // {
}: nixosSystem (nixosArgs // (let args = {
#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.
(let
(let # mainModule
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
# ensure that in the main module, the "name" parameter is available during the import stage already:
bindName2function = func: lib.setFunctionArgs (args: func (args // { inherit name; })) (builtins.removeAttrs (lib.functionArgs func) [ "name" ]);
bindName2module = module: if lib.isFunction module then bindName2function module else if module?imports then module // { imports = map bindName2module module.imports; } else module;
in bindName2module module)
{ _module.args.name = lib.mkOverride 0 name; } # (specialisations can somehow end up with the name »configuration«, which is very incorrect)
{ networking.hostName = name; }
# containers may or may not want to inherit extraModules (but it is easy to add), but there is no reason not to inherit specialArgs:
{ options.containers = lib.mkOption { type = lib.types.attrsOf (lib.types.submodule (_: { config = {
specialArgs = builtins.mapAttrs (_: lib.mkDefault) args.specialArgs; # imports = args.extraModules;
}; })); }; }
# `specializations` use `noUserModules`, which inherit specialArgs and extraModules, or `extendModules` inherits all modules and arguments. VM-variants also use `extendModules`.
]; _file = "${dirname}/nixos.nix#modules"; } ];
extraModules = (nixosArgs.extraModules or [ ]) ++ modules ++ extraModules ++ [ { imports = [ (args: {
extraModules = (nixosArgs.extraModules or [ ]) ++ modules ++ extraModules ++ [ { imports = [ (_: {
# These are passed as »extraModules« module argument and can thus be reused when defining containers and such (so define as much stuff as possible here).
# There is, unfortunately, no way to directly pass modules into all containers. Each container will need to be defined with »config.containers."${name}".config.imports = extraModules«.
# (One could do that automatically by defining »options.security.containers = lib.mkOption { type = lib.types.submodule (cfg: { options.config = lib.mkOption { apply = _:_.extendModules { modules = extraModules; }; }); }«.)
@ -57,7 +63,7 @@ in rec {
specialArgs = (nixosArgs.specialArgs or { }) // { inherit inputs; };
# (This is already set during module import, while »_module.args« only becomes available during module evaluation (before that, using it causes infinite recursion). Since it can't be ensured that this is set in every circumstance where »extraModules« are being used, it should generally not be used to set custom arguments.)
});
}; in args));
# Given either a list (or attr set) of »files« (paths to ».nix« or ».nix.md« files for dirs with »default.nix« files in them) or a »dir« path (and optionally a list of base names to »exclude« from it), this builds the NixOS configuration for each host (per file) in the context of all configs provided.
# If »files« is an attr set, exactly one host with the attribute's name as hostname is built for each attribute. Otherwise the default is to build for one host per configuration file, named as the file name without extension or the sub-directory name. Setting »${preface'}.instances« can override this to build the same configuration for those multiple names instead (the specific »name« is passed as additional »moduleArgs« to the modules and can thus be used to adjust the config per instance).
@ -77,11 +83,14 @@ in rec {
in (mapMergeUnique (name: { "${name}" = let
preface = getPreface inputs (moduleArgs // { inherit preface; }) mainModule name; # (call again, with name)
in { inherit preface; } // (mkNixosConfiguration (let systemArgs = (
builtins.removeAttrs args [ "files" "dir" "exclude" ]
builtins.removeAttrs args [ "files" "dir" "exclude" "prefix" ]
) // {
inherit name mainModule;
moduleArgs = (moduleArgs // { inherit preface; });
nixosArgs = (args.nixosArgs or { }) // { specialArgs = (args.nixosArgs.specialArgs or { }) // { inherit preface; }; }; # make this available early, and only for the main evaluation (+specialisations, -containers)
nixosArgs = (args.nixosArgs or { }) // {
specialArgs = (args.nixosArgs.specialArgs or { }) // { inherit preface; }; # make this available early, and only for the main evaluation (+specialisations +containers)
prefix = (args.nixosArgs.prefix or [ ]) ++ ((args.prefix or (_: [ ])) name);
};
extraModules = (args.extraModules or [ ]) ++ [ { imports = [ (args: {
options.${preface'} = {
instances = lib.mkOption { description = "List of host names to instantiate this host config for, instead of just for the file name."; type = lib.types.listOf lib.types.str; readOnly = true; } // (lib.optionalAttrs (!preface?instances) { default = instances; });
@ -123,6 +132,8 @@ in rec {
moduleArgs ? { },
# The »nixosSystem« function defined in »<nixpkgs>/flake.nix«, or equivalent.
nixosSystem ? inputs.nixpkgs.lib.nixosSystem,
# Attribute path labels to prepend to option names/paths. Useful for debugging when building multiple systems at once.
prefix ? (name: [ "[${if renameOutputs == false then name else renameOutputs name}]" ]),
# If provided, this will be set as »config.nixpkgs.buildPlatform« for all hosts, which in turn enables cross-compilation for all hosts whose »config.nixpkgs.hostPlatform« (the architecture they will run on) does not expand to the same value. Without this, building for other platforms may still work (slowly) if »boot.binfmt.emulatedSystems« on the building system is configured for the respective target(s).
buildPlatform ? null,
## The platforms for which the setup scripts (installation & maintenance/debugging) will be defined. Should include the ».buildPlatform« and/or the target system's »config.nixpkgs.hostPlatform«.
@ -134,7 +145,7 @@ in rec {
... }: let
getName = if renameOutputs == false then (name: name) else renameOutputs;
otherArgs = (builtins.removeAttrs args [ "hosts" "moduleInputs" "overlayInputs" "renameOutputs" "asDefaultPackage" ]) // {
inherit inputs modules overlays moduleArgs nixosSystem buildPlatform extraModules;
inherit inputs modules overlays moduleArgs nixosSystem buildPlatform extraModules prefix;
nixosArgs = (args.nixosArgs or { }) // { modules = (args.nixosArgs.modules or [ ]) ++ [ { imports = [ (args: {
${installer}.outputName = getName args.config._module.args.name;
}) ]; _file = "${dirname}/nixos.nix#mkSystemsFlake-extraModule"; } ]; };

View File

@ -52,8 +52,8 @@ function gen-key-home-composite {( set -eu # 1: usage, 2: user
if [[ ${!userPasswords[@]} && ${userPasswords[$user]:-} ]] ; then
password=${userPasswords[$user]}
else
password=$(prompt-new-password "for the user account »$user« (as component for key »(@{config.networking.hostName}:)$usage«)")
if [[ ! $password ]] ; then \exit 1 ; fi
password=$( prompt-new-password "for the user account »$user« (as component for key »(@{config.networking.hostName}:)$usage«)" )
if [[ ! $password ]] ; then \exit 1 ; fi ; userPasswords[$user]=$password # TODO: won't propagate
fi
{ cat "$keystore"/home/"$user".key && cat <<<"$password" ; } | sha256sum | head -c 64
)}
@ -66,8 +66,8 @@ function gen-key-home-yubikey {( set -eu # 1: usage, 2: serialAndSlotAndUser(as
if [[ ${!userPasswords[@]} && ${userPasswords[$user]:-} ]] ; then
password=${userPasswords[$user]}
else
password=$(prompt-new-password "for the user account »$user« (as YubiKey challenge for key »:$usage«)")
if [[ ! $password ]] ; then \exit 1 ; fi
password=$( prompt-new-password "for the user account »$user« (as YubiKey challenge for key »:$usage«)" )
if [[ ! $password ]] ; then \exit 1 ; fi ; userPasswords[$user]=$password # TODO: won't propagate
fi
gen-key-yubikey-challenge "$usage" "$serial:$slot:home-$user=$password" true "»${user}«'s password (to create key »:${usage}«)"
)}

View File

@ -193,8 +193,8 @@ function format-partitions {
elif [[ ${fs[device]} == /dev/mapper/* ]] ; then
if [[ ! @{config.boot.initrd.luks.devices!catAttrSets.device[${fs[device]/'/dev/mapper/'/}]:-} ]] ; then echo "LUKS device ${fs[device]} used by mount ${fs[mountPoint]} does not point at one of the device mappings ${!config.boot.initrd.luks.devices!catAttrSets.device[@]}" 1>&2 ; \return 1 ; fi
else continue ; fi
eval 'declare -a formatArgs='"${fs[formatArgs]}"
( PATH=@{native.e2fsprogs}/bin:@{native.f2fs-tools}/bin:@{native.xfsprogs}/bin:@{native.dosfstools}/bin:$PATH ; ${_set_x:-:} ; mkfs."${fs[fsType]}" "${formatArgs[@]}" "${fs[device]}" >$beLoud 2>$beSilent ) || [[ $options == *,nofail,* ]] || return
eval 'declare -a formatArgs='"${fs[formatArgs]}" ; eval 'declare -a options='"${fs[options]}"
( PATH=@{native.e2fsprogs}/bin:@{native.f2fs-tools}/bin:@{native.xfsprogs}/bin:@{native.dosfstools}/bin:$PATH ; ${_set_x:-:} ; mkfs."${fs[fsType]}" "${formatArgs[@]}" "${fs[device]}" >$beLoud 2>$beSilent ) || [[ ' '${options[@]}' ' == *' 'nofail' '* ]] || return
@{native.parted}/bin/partprobe "${fs[device]}" || true
done
for swapDev in "@{config.swapDevices!catAttrs.device[@]}" ; do

View File

@ -42,6 +42,8 @@ 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
run-hook-script 'Prepare Installer' @{config.installer.commands.prepareInstaller!writeText.prepareInstallerCommands} || exit
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.

View File

@ -7,10 +7,7 @@ function prompt-for-user-passwords { # (void)
userPasswords[$user]=@{config.users.users!catAttrSets.password[$user]}
done
local user ; for user in "@{!config.users.users!catAttrSets.hashedPasswordFile[@]}" "@{!config.users.users!catAttrSets.passwordFile[@]}" ; do
for attempt in 2 3 x ; do
if userPasswords[$user]=$(prompt-new-password "for the user account »$user«") ; then break ; fi
if [[ $attempt == x ]] ; then \return 1 ; fi ; echo "Retrying ($attempt/3):"
done
prompt-new-password-thrice "for the user account »$user«"
done
}

View File

@ -68,7 +68,7 @@ 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"
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 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 the 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 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
@ -158,7 +158,7 @@ function run-qemu {
if [[ ! -e $disk ]] ; then args[install]=always ; fi
done ; fi
if [[ ${args[install]:-} == always ]] && [[ ! ${args[dry-run]:-} ]] ; then (
if [[ ! ${args[trace]:-} ]] && [[! ${args[debug]:-} ]] ; then args[quiet]=1 ; fi
if [[ ! ${args[trace]:-} ]] && [[ ! ${args[debug]:-} ]] ; then args[quiet]=1 ; fi
args[no-inspect]=1 ; install-system "$diskImages" || exit
) || return ; fi
@ -176,7 +176,7 @@ declare-flag run-qemu,install-system,'*' vm-mem "num" "VM RAM in MiB (»
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} )
qemu+=( -m ${args[vm-mem]:-4096} )
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

View File

@ -45,6 +45,12 @@ function prompt-new-password {( set -u # 1: usage
if [[ "$password1" != "$password2" ]] ; then printf 'Passwords mismatch.\n' 1>&2 ; \exit 1 ; fi
printf %s "$password1" || exit
)}
function prompt-new-password-thrice {
local attempt ; for attempt in 2 3 x ; do
if userPasswords[$user]=$( prompt-new-password "$@" ) ; then break ; fi
if [[ $attempt == x ]] ; then \return 1 ; fi ; echo "Retrying ($attempt/3):"
done
}
## 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."
@ -67,16 +73,15 @@ function prompt-secret-as {( set -u # 1: what, 2: secretFile, 3?: owner[:[group]
declare-flag install-system inspectScripts "" "When running installation hooks (»...*Commands« composed as Nix strings) print out and pause before each command. This works ... semi-well."
## Runs an installer hook script, optionally stepping through the script.
function run-hook-script {( # 1: title, 2: scriptPath
trap - EXIT # start with empty traps for sub-shell
function run-hook-script { # 1: title, 2: scriptPath
if [[ ${args[inspectScripts]:-} && "$(cat "$2")" != $'' ]] ; then
echo "Running $1 commands. For each command printed, press Enter to continue or Ctrl+C to abort the installation:" 1>&2
# (this does not help against intentionally malicious scripts, it's quite easy to trick this)
BASH_PREV_COMMAND= ; set -o functrace ; trap 'if [[ $BASH_COMMAND != "$BASH_PREV_COMMAND" ]] ; then echo -n "> $BASH_COMMAND" >&2 ; read ; fi ; BASH_PREV_COMMAND=$BASH_COMMAND' debug
fi
set -e # The called script snippets should not rely on this, but neither should this function rely on the scripts correctly exiting on errors.
source "$2"
)}
set +o functrace ; trap - debug
}
## Lazily builds a nix derivation at run time, instead of when building the script.
# When maybe-using packages that take long to build, instead of »at{some.package.out}«, use: »$( build-lazy at{some.package.drvPath!unsafeDiscardStringContext} out )«

View File

@ -36,6 +36,7 @@ in {
Note that these commands are executed without any further sandboxing (i.e. when not using the VM installation mode, as root on the host).
Partitions may be used via `/dev/disk/by-partlabel/`.${lib.optionalString mounted '' The target system is mounted at `$mnt`.''}
''; type = lib.types.lines; default = ""; }; in {
prepareInstaller = cmdOpt "early during preparation of the installer (right after CLI parsing)" false;
postPartition = cmdOpt "after partitioning the disks" false;
postFormat = cmdOpt "after formatting the partitions with filesystems" false;
postMount = cmdOpt "after mounting all filesystems" true;
@ -47,7 +48,7 @@ in {
type = lib.types.nullOr lib.types.str; default = null;
};
build.scripts = lib.mkOption {
type = lib.types.functionTo lib.types.str; internal = true; readOnly = true;
type = lib.types.functionTo lib.types.str; internal = true; readOnly = true;
default = context: "${lib.fun.substituteImplicit { # This replaces the `@{}` references in the scripts with normal bash variables that hold serializations of the Nix values they refer to.
inherit pkgs; scripts = lib.sort (a: b: a.order < b.order) (lib.attrValues cfg.scripts);
context = { inherit config options pkgs; inherit (moduleArgs) inputs; } // context;
@ -58,7 +59,7 @@ in {
config = {
${installer} = {
scripts = lib.mapAttrs (name: path: lib.mkOptionDefault { inherit path; }) (lib.self.setup-scripts);
scripts = lib.mapAttrs (name: path: lib.mkOptionDefault { inherit path; order = 750; }) (lib.self.setup-scripts);
};
};

View File

@ -206,13 +206,11 @@ in {
}) (lib.mkIf cfg.persistenceFixes { # Cope with the consequences of having »/« (including »/{etc,var,root,...}«) cleared on every reboot.
environment.etc = {
# SSHd host keys:
"ssh/ssh_host_ed25519_key".source = "/${keep}/etc/ssh/ssh_host_ed25519_key";
"ssh/ssh_host_ed25519_key.pub".source = "/${keep}/etc/ssh/ssh_host_ed25519_key.pub";
"ssh/ssh_host_rsa_key".source = "/${keep}/etc/ssh/ssh_host_rsa_key";
"ssh/ssh_host_rsa_key.pub".source = "/${keep}/etc/ssh/ssh_host_rsa_key.pub";
};
# SSHd host keys:
environment.etc = lib.fun.mapMerge ({ path, ... }: if lib.hasPrefix "/etc/" path then let rel = lib.removePrefix "/etc/" path; in {
"${rel}".source = lib.mkDefault "/${keep}/etc/${rel}";
"${rel}.pub".source = lib.mkDefault "/${keep}/etc/${rel}.pub";
} else { }) (config.services.openssh.hostKeys);
systemd.tmpfiles.rules = [ # keep in mind: this does not get applied super early ...
# »/root/.nix-channels« is already being restored.

View File

@ -156,7 +156,7 @@ in let hostModule = {
fileSystems = lib.mkVMOverride {
"/nix/var/nix/db.lower" = {
fsType = "9p"; device = "nix-var-nix-db"; neededForBoot = true;
options = [ "trans=virtio" "version=9p2000.L" "msize=4194304" "ro" ];
options = [ "trans=virtio" "version=9p2000.L" "msize=4194304" "ro" ];
};
"/nix/store".options = lib.mkAfter [ "ro" "msize=4194304" ];
"/nix/store".mountPoint = lib.mkForce "/nix/store.lower";