add --skip-formatting flag,

open debug shells with variables+functions
This commit is contained in:
Niklas Gollenstede 2023-08-14 18:59:47 +02:00
parent 41c75410e1
commit b2cbacc21e
12 changed files with 96 additions and 59 deletions

View File

@ -59,6 +59,7 @@
"extra_isize", // ext4 option "extra_isize", // ext4 option
"fdisk", // program "fdisk", // program
"fetchurl", // nix function "fetchurl", // nix function
"fido2", // protocol
"filesystems", // plural "filesystems", // plural
"fmask", // mount "fmask", // mount
"foldl", // nix (fold left) "foldl", // nix (fold left)
@ -203,5 +204,6 @@
"yubikey", // program "yubikey", // program
"YubiKeys", // plural "YubiKeys", // plural
"zfsutil", // program / function "zfsutil", // program / function
"zpool", "zpools", // zfs
], ],
} }

Binary file not shown.

View File

@ -36,7 +36,7 @@ in rec {
{ networking.hostName = name; } { networking.hostName = name; }
]; _file = "${dirname}/nixos.nix#modules"; } ]; ]; _file = "${dirname}/nixos.nix#modules"; } ];
extraModules = (nixosArgs.extraModules or [ ]) ++ modules ++ extraModules ++ [ ({ config, ... }: { imports = [ ({ extraModules = (nixosArgs.extraModules or [ ]) ++ modules ++ extraModules ++ [ { imports = [ (args: {
# 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). # 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«. # 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; }; }); }«.) # (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; }; }); }«.)
@ -47,7 +47,7 @@ in rec {
system.nixos.revision = lib.mkIf (inputs?nixpkgs.rev) inputs.nixpkgs.rev; # (evaluating the default value fails under some circumstances) system.nixos.revision = lib.mkIf (inputs?nixpkgs.rev) inputs.nixpkgs.rev; # (evaluating the default value fails under some circumstances)
}) ]; _file = "${dirname}/nixos.nix#mkNixosConfiguration-extraModule"; }) ]; }) ]; _file = "${dirname}/nixos.nix#mkNixosConfiguration-extraModule"; } ];
specialArgs = (nixosArgs.specialArgs or { }) // { inherit inputs; }; 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.) # (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.)
@ -77,7 +77,7 @@ in rec {
inherit name mainModule; inherit name mainModule;
moduleArgs = (moduleArgs // { inherit preface; }); 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)
extraModules = (args.extraModules or [ ]) ++ [ { imports = [ ({ extraModules = (args.extraModules or [ ]) ++ [ { imports = [ (args: {
options.${preface'} = { 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; }); 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; });
id = lib.mkOption { description = "This system's ID. If set, »mkSystemsFlake« will ensure that the ID is unique among all »moduleArgs.nodes«."; type = lib.types.nullOr (lib.types.either lib.types.int lib.types.str); readOnly = true; apply = id: if id == null then null else toString id; } // (lib.optionalAttrs (!preface?id) { default = null; }); id = lib.mkOption { description = "This system's ID. If set, »mkSystemsFlake« will ensure that the ID is unique among all »moduleArgs.nodes«."; type = lib.types.nullOr (lib.types.either lib.types.int lib.types.str); readOnly = true; apply = id: if id == null then null else toString id; } // (lib.optionalAttrs (!preface?id) { default = null; });
@ -171,7 +171,7 @@ in rec {
description = '' description = ''
Call per-host setup and maintenance commands. Most importantly, »install-system«. Call per-host setup and maintenance commands. Most importantly, »install-system«.
''; '';
ownPath = if (system.config.${installer}.outputName != null) then "nix run REPO#${system.config.${installer}.outputName} --" else "$0"; ownPath = if (system.config.${installer}.outputName != null) then "nix run REPO#${system.config.${installer}.outputName} --" else builtins.placeholder "out";
usageLine = '' usageLine = ''
Usage: Usage:
%s [sudo] [bash] [--FLAG[=value]]... [--] [COMMAND [ARG]...] %s [sudo] [bash] [--FLAG[=value]]... [--] [COMMAND [ARG]...]
@ -207,13 +207,14 @@ in rec {
tools = lib.unique (map (p: p.outPath) (lib.filter lib.isDerivation pkgs.stdenv.allowedRequisites)); tools = lib.unique (map (p: p.outPath) (lib.filter lib.isDerivation pkgs.stdenv.allowedRequisites));
esc = lib.escapeShellArg; esc = lib.escapeShellArg;
in pkgs.writeShellScript "scripts-${name}" '' in pkgs.writeShellScript "scripts-${name}" ''
self=${builtins.placeholder "out"}
# if first arg is »sudo«, re-execute this script with sudo (as root) # if first arg is »sudo«, re-execute this script with sudo (as root)
if [[ ''${1:-} == sudo ]] ; then shift ; exec sudo --preserve-env=SSH_AUTH_SOCK -- "$0" "$@" ; fi if [[ ''${1:-} == sudo ]] ; then shift ; exec sudo --preserve-env=SSH_AUTH_SOCK -- "$self" "$@" ; fi
# if the (now) first arg is »bash« or there are no args, re-execute this script as bash »--init-file«, starting an interactive bash in the context of the script # if the (now) first arg is »bash« or there are no args, re-execute this script as bash »--init-file«, starting an interactive bash in the context of the script
if [[ ''${1:-} == bash ]] || [[ $# == 0 && $0 != ${pkgs.bashInteractive}/bin/bash ]] ; then if [[ ''${1:-} == bash ]] || [[ $# == 0 && $0 != ${pkgs.bashInteractive}/bin/bash ]] ; then
shift ; exec ${pkgs.bashInteractive}/bin/bash --init-file <(cat << "EOS"${"\n"+'' shift ; exec ${pkgs.bashInteractive}/bin/bash --init-file <(echo '
# prefix the script to also include the default init files # prefix the script to also include the default init files
! [[ -e /etc/profile ]] || . /etc/profile ! [[ -e /etc/profile ]] || . /etc/profile
for file in ~/.bash_profile ~/.bash_login ~/.profile ; do for file in ~/.bash_profile ~/.bash_login ~/.profile ; do
@ -222,8 +223,9 @@ in rec {
# add active »hostName« to shell prompt # add active »hostName« to shell prompt
PS1=''${PS1/\\$/\\[\\e[93m\\](${name})\\[\\e[97m\\]\\$} PS1=''${PS1/\\$/\\[\\e[93m\\](${name})\\[\\e[97m\\]\\$}
''}EOS
cat $0) -i -s ':' "$@" source "'"$self"'" ; PATH=$hostPath
') -i -s ':' "$@"
fi fi
# provide installer tools (not necessarily for system.pkgs.config.hostPlatform) # provide installer tools (not necessarily for system.pkgs.config.hostPlatform)
@ -238,19 +240,18 @@ in rec {
shopt -s expand_aliases # enable aliases in non-interactive bash shopt -s expand_aliases # enable aliases in non-interactive bash
for control in return exit ; do alias $control='{ for control in return exit ; do alias $control='{
status=$? ; if ! (( status )) ; then '$control' 0 ; fi # control flow return status=$? ; if ! (( status )) ; then '$control' 0 ; fi # control flow return
if ! ${pkgs.bashInteractive}/bin/bash --init-file ${system.config.environment.etc.bashrc.source} ; then '$control' $status ; fi # »|| '$control'« as an error-catch if ! PATH=$hostPath "$self" bash ; then '$control' $status ; fi # »|| '$control'« as an error-catch
#if ! ${pkgs.bashInteractive}/bin/bash --init-file ${system.config.environment.etc.bashrc.source} ; then '$control' $status ; fi # »|| '$control'« as an error-catch
}' ; done }' ; done
fi fi
declare -g -A allowedArgs=( ) allowedArgCtx=( ) ; function declare-flag { # 1: context, 2: name, 3?: value, 4: description declare -g -A allowedArgs=( ) ; function declare-flag { # 1: context, 2: name, 3?: value, 4: description
if [[ ''${allowedArgCtx[$2]:-} && ''${allowedArgCtx[$2]:-} != "$1" ]] ; then echo "Flag $2 was declared in conflicting contexts ''${allowedArgCtx[$2]} and $1" >&2 ; \exit 1 ; fi
allowedArgCtx[$2]=$1
local name=--$2 ; if [[ $3 ]]; then name+='='$3 ; fi ; allowedArgs[$name]="($1) $4" local name=--$2 ; if [[ $3 ]]; then name+='='$3 ; fi ; allowedArgs[$name]="($1) $4"
} }
declare-flag global command "" 'Interpret the first positional argument as bash script (instead of the name of a single command) and »eval« it (with access to all commands and internal functions and variables).' declare-flag '*' command "" 'Interpret the first positional argument as bash script (instead of the name of a single command) and »eval« it (with access to all commands and internal functions and variables).'
declare-flag global debug "" 'Hook into any »|| exit« / »|| return« statements and open a shell if they are triggered by an error. Implies »--trace«.' declare-flag '*' debug "" 'Hook into any »|| exit« / »|| return« statements and open a shell if they are triggered by an error. Implies »--trace«.'
declare-flag global trace "" "Turn on bash's »errtrace« option before running »COMMAND«." declare-flag '*' trace "" "Turn on bash's »errtrace« option before running »COMMAND«."
declare-flag global quiet "" "Try to suppress all non-error output. May also swallow some error related output." declare-flag '*' quiet "" "Try to suppress all non-error output. May also swallow some error related output."
declare -g -A allowedCommands=( ) ; function declare-command { allowedCommands[$@]=$(< /dev/stdin) ; } declare -g -A allowedCommands=( ) ; function declare-command { allowedCommands[$@]=$(< /dev/stdin) ; }
${system.config.${installer}.build.scripts { native = pkgs; }} ${system.config.${installer}.build.scripts { native = pkgs; }}
if [[ ''${args[help]:-} ]] ; then ( if [[ ''${args[help]:-} ]] ; then (

View File

@ -3,22 +3,36 @@
# Disk Partitioning and Formatting # Disk Partitioning and Formatting
## ##
declare-flag install-system skip-formatting "" "Skip partitioning, formatting, and their post-commands. Instead, assume that all required disks/images/zpools are correctly partitioned/formatted, and simply (unlock/import and) mount them. This is useful to skip the destruktive part of the installation, but still do the (largely idempotent) part of copying and linking the current system generation and installing the bootloader -- i.e, repair an installation."
## Prepares the disks of the target system for the copying of files. ## Prepares the disks of the target system for the copying of files.
function do-disk-setup { # 1: diskPaths function do-disk-setup { # 1: diskPaths
ensure-disks "$1" || return ensure-disks "$1" || return
prompt-for-user-passwords || return prompt-for-user-passwords || return
populate-keystore || return
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« 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«
partition-disks || return if [[ ${args[skip-formatting]:-} ]] ; then
create-luks-layers && open-luks-layers || return # other block layers would go here too (but figuring out their dependencies would be difficult) if [[ @{config.setup.keystore.enable} ]] ; then
run-hook-script 'Post Partitioning' @{config.installer.commands.postPartition!writeText.postPartitionCommands} || return mount-keystore-luks-primary || return
else
populate-keystore || return # (this may be insufficient)
fi
open-luks-layers || return
if [[ $(LC_ALL=C type -t import-zpools) == function ]] ; then import-zpools $mnt || return ; fi
else
populate-keystore || return
partition-disks || return
create-luks-layers || return
open-luks-layers || return
# other block layers would go here too (but figuring out their dependencies would be difficult)
run-hook-script 'Post Partitioning' @{config.installer.commands.postPartition!writeText.postPartitionCommands} || return
format-partitions || return format-partitions || return
if [[ $(LC_ALL=C type -t create-zpools) == function ]] ; then create-zpools $mnt || return ; fi if [[ $(LC_ALL=C type -t create-zpools) == function ]] ; then create-zpools $mnt || return ; fi
run-hook-script 'Post Formatting' @{config.installer.commands.postFormat!writeText.postFormatCommands} || return run-hook-script 'Post Formatting' @{config.installer.commands.postFormat!writeText.postFormatCommands} || return
fi
fix-grub-install || return fix-grub-install || return
@ -52,7 +66,7 @@ function ensure-disks { # 1: diskPaths, 2?: skipLosetup
fi fi
local name ; for name in "@{!config.setup.disks.devices[@]}" ; do local name ; for name in "@{!config.setup.disks.devices[@]}" ; do
if [[ ! @{config.setup.disks.devices!catAttrSets.partitionDuringInstallation[$name]} ]] ; then continue ; fi if [[ ! @{config.setup.disks.devices!catAttrSets.partitionDuringInstallation[$name]} ]] ; then unset blockDevs[$name] ; continue ; fi
if [[ ! ${blockDevs[$name]:-} ]] ; then echo "Path for block device $name not provided" 1>&2 ; \return 1 ; fi if [[ ! ${blockDevs[$name]:-} ]] ; then echo "Path for block device $name not provided" 1>&2 ; \return 1 ; fi
eval 'local -A disk='"@{config.setup.disks.devices[$name]}" eval 'local -A disk='"@{config.setup.disks.devices[$name]}"
if [[ ${blockDevs[$name]} != /dev/* ]] ; then if [[ ${blockDevs[$name]} != /dev/* ]] ; then

View File

@ -103,7 +103,7 @@ function reexec-in-qemu {
echo 'Performing the installation in a cross-ISA qemu system VM; this will be very, very slow (many hours) ...' echo 'Performing the installation in a cross-ISA qemu system VM; this will be very, very slow (many hours) ...'
output=@{inputs.self}'#'nixosConfigurations.@{config.installer.outputName:?}.config.system.build.vmExec-@{pkgs.buildPackages.system} output=@{inputs.self}'#'nixosConfigurations.@{config.installer.outputName:?}.config.system.build.vmExec-@{pkgs.buildPackages.system}
fi fi
local scripts=$0 ; if [[ @{pkgs.system} != "@{native.system}" ]] ; then local scripts=$self ; if [[ @{pkgs.system} != "@{native.system}" ]] ; then
scripts=$( build-lazy @{inputs.self}'#'apps.@{pkgs.system}.@{config.installer.outputName:?}.derivation ) || return scripts=$( build-lazy @{inputs.self}'#'apps.@{pkgs.system}.@{config.installer.outputName:?}.derivation ) || return
fi fi
local command="$scripts install-system $( printf '%q ' "${newArgs[@]}" ) || exit" local command="$scripts install-system $( printf '%q ' "${newArgs[@]}" ) || exit"
@ -129,7 +129,7 @@ declare-flag install-system toplevel "" "Optional replacement for the actual »c
declare-flag install-system no-inspect "" "Do not inspect the (successfully) installed system before unmounting its filesystems." declare-flag install-system no-inspect "" "Do not inspect the (successfully) installed system before unmounting its filesystems."
declare-flag install-system inspect-cmd "script" "Instead of opening an interactive shell for the post-installation inspection, »eval« this script." declare-flag install-system inspect-cmd "script" "Instead of opening an interactive shell for the post-installation inspection, »eval« this script."
## Copies the system's dependencies to the disks mounted at »$mnt« and installs the bootloader. If »$inspect« is set, a root shell will be opened in »$mnt« afterwards. ## Copies the system's dependencies to the disks mounted at »$mnt« and installs the bootloader. By default, a root shell will be opened in »$mnt« afterwards.
# »$topLevel« may point to an alternative top-level dependency to install. # »$topLevel« may point to an alternative top-level dependency to install.
function install-system-to {( set -u # 1: mnt, 2?: topLevel function install-system-to {( set -u # 1: mnt, 2?: topLevel
targetSystem=${args[toplevel]:-@{config.system.build.toplevel}} targetSystem=${args[toplevel]:-@{config.system.build.toplevel}}
@ -200,7 +200,7 @@ function install-system-to {( set -u # 1: mnt, 2?: topLevel
else 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 ) ( 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 fi
LC_ALL=C PATH=$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 ; exec bash --login' || exit # +o monitor LC_ALL=C PATH=$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 ; CHROOT_DIR="'"$mnt"'" mnt=/ exec "'"$self"'" bash' || exit # +o monitor
fi fi
mkdir -p $mnt/var/lib/systemd/timesync && touch $mnt/var/lib/systemd/timesync/clock || true # save current time mkdir -p $mnt/var/lib/systemd/timesync && touch $mnt/var/lib/systemd/timesync/clock || true # save current time

View File

@ -161,7 +161,7 @@ function run-qemu {
done ; fi done ; fi
if [[ ${args[install]:-} == always ]] ; then if [[ ${args[install]:-} == always ]] ; then
local verbosity=--quiet ; if [[ ${args[trace]:-} ]] ; then verbosity=--trace ; fi ; if [[ ${args[debug]:-} ]] ; then verbosity=--debug ; fi local verbosity=--quiet ; if [[ ${args[trace]:-} ]] ; then verbosity=--trace ; fi ; if [[ ${args[debug]:-} ]] ; then verbosity=--debug ; fi
hostPath=${hostPath:-} ${args[dry-run]:+echo} $0 install-system "$diskImages" $verbosity --no-inspect || return hostPath=${hostPath:-} ${args[dry-run]:+echo} "$self" install-system "$diskImages" $verbosity --no-inspect || return
fi fi
qemu+=( "${argv[@]}" ) qemu+=( "${argv[@]}" )
@ -198,6 +198,18 @@ function mount-keystore-luks {
@{native.util-linux}/bin/mount -o nodev,umask=0077,fmask=0077,dmask=0077,ro /dev/mapper/$keystore /run/$keystore && prepend_trap "@{native.util-linux}/bin/umount /run/$keystore" EXIT || return @{native.util-linux}/bin/mount -o nodev,umask=0077,fmask=0077,dmask=0077,ro /dev/mapper/$keystore /run/$keystore && prepend_trap "@{native.util-linux}/bin/umount /run/$keystore" EXIT || return
} }
## Opens the keystore with the primary unlock method, which may not be convenient to use, but should always be defined.
function mount-keystore-luks-primary {
local usage=luks/keystore-@{config.networking.hostName!hashString.sha256:0:8}/0
local method=@{config.setup.keystore.keys[$usage]%%=*}
local options=@{config.setup.keystore.keys[$usage]:$(( ${#method} + 1 ))}
local attempt ; for attempt in 2 3 x ; do
if mount-keystore-luks --key-file=<( gen-key-"$method" "$usage" "$options" ) ; then break ; fi
if [[ $attempt == x ]] ; then \return 1 ; fi ; echo "Retrying ($attempt/3):"
done
}
declare-command open-system diskImages << 'EOD' declare-command open-system diskImages << 'EOD'
Performs any steps necessary to mount the target system at »/tmp/nixos-install-@{config.networking.hostName}« on the current host. Performs any steps necessary to mount the target system at »/tmp/nixos-install-@{config.networking.hostName}« on the current host.
For any steps taken, it also adds the steps to undo them on exit from the calling shell (so this is not useful as a standalone »COMMAND«, use the »bash« or »--command« options, and don't call this from a sub-shell that exits too early). For any steps taken, it also adds the steps to undo them on exit from the calling shell (so this is not useful as a standalone »COMMAND«, use the »bash« or »--command« options, and don't call this from a sub-shell that exits too early).
@ -220,27 +232,19 @@ function open-system {
@{native.systemd}/bin/udevadm settle -t 15 || true # sometimes partitions aren't quite made available yet @{native.systemd}/bin/udevadm settle -t 15 || true # sometimes partitions aren't quite made available yet
if [[ @{config.setup.keystore.enable} && ! -e /dev/mapper/keystore-@{config.networking.hostName!hashString.sha256:0:8} ]] ; then # Try a bunch of approaches for opening the keystore: if [[ @{config.setup.keystore.enable} && ! -e /dev/mapper/keystore-@{config.networking.hostName!hashString.sha256:0:8} ]] ; then # Try a bunch of approaches for opening the keystore:
mount-keystore-luks --key-file=<( printf %s "@{config.networking.hostName}" ) || return mount-keystore-luks --key-file=<( printf %s "@{config.networking.hostName}" ) || # costs nothing to try
mount-keystore-luks --key-file=/dev/disk/by-partlabel/bootkey-@{config.networking.hostName!hashString.sha256:0:8} || return mount-keystore-luks --key-file=/dev/disk/by-partlabel/bootkey-@{config.networking.hostName!hashString.sha256:0:8} || # costs nothing to try
mount-keystore-luks --key-file=<( read -s -p PIN: pin && echo ' touch!' >&2 && @{native.yubikey-personalization}/bin/ykchalresp -2 "$pin" ) || return mount-keystore-luks-primary || # should always be applicable
# TODO: try static yubikey challenge mount-keystore-luks --key-file=<( read -s -p PIN: pin && echo ' touch!' >&2 && @{native.yubikey-personalization}/bin/ykchalresp -2 "$pin" ) ||
mount-keystore-luks || return mount-keystore-luks || # (getting desperate)
return # oh well
fi fi
mnt=/tmp/nixos-install-@{config.networking.hostName} # allow this to leak into the calling scope export mnt=/tmp/nixos-install-@{config.networking.hostName} # allow this to leak into the calling scope
if [[ ! -e $mnt ]] ; then mkdir -p "$mnt" && prepend_trap "rmdir '$mnt'" EXIT || return ; fi if [[ ! -e $mnt ]] ; then mkdir -p "$mnt" && prepend_trap "rmdir '$mnt'" EXIT || return ; fi
open-luks-layers || return # Load crypt layers and zfs pools: open-luks-layers || return # Load crypt layers and zfs pools:
if [[ $( LC_ALL=C type -t ensure-datasets ) == 'function' ]] ; then if [[ $( LC_ALL=C type -t import-zpools ) == 'function' ]] ; then import-zpools "$mnt" skipImported ; fi
local poolName ; for poolName in "@{!config.setup.zfs.pools[@]}" ; do
if [[ ! @{config.setup.zfs.pools!catAttrSets.createDuringInstallation[$poolName]} ]] ; then continue ; fi
if ! @{native.zfs}/bin/zfs get -o value -H name "$poolName" &>/dev/null ; then
@{native.zfs}/bin/zpool import -f -N -R "$mnt" "$poolName" && prepend_trap "@{native.zfs}/bin/zpool export '$poolName'" EXIT || return
fi
: | @{native.zfs}/bin/zfs load-key -r "$poolName" || true
ensure-datasets "$mnt" '^'"$poolName"'($|[/])' || return
done
fi
prepend_trap "unmount-system '$mnt'" EXIT && mount-system "$mnt" '' 1 || return prepend_trap "unmount-system '$mnt'" EXIT && mount-system "$mnt" '' 1 || return
df -h | grep $mnt | cat df -h | grep $mnt | cat

View File

@ -79,7 +79,7 @@ function copy-function { # 1: existingName, 2: newName
function mkdir-sticky { # 1: path, 2?: fallbackOwner, 3?: fallbackGroup, 4?: fallbackMode function mkdir-sticky { # 1: path, 2?: fallbackOwner, 3?: fallbackGroup, 4?: fallbackMode
local path ; path=$1 ; shift local path ; path=$1 ; shift
if [[ -d $path ]] ; then return ; fi # existing (symlink to existing) dir if [[ -d $path ]] ; then return ; fi # existing (symlink to existing) dir
if [[ -L $path || -e $path ]] ; then echo "Can't create (child of) existing file (or broken symlink) '$path'" 1>&2 ; return 1 ; fi if [[ -L $path || -e $path ]] ; then echo "Can't create (child of) existing file (or broken symlink) '$path'" 1>&2 ; \return 1 ; fi
local parent ; parent=$( dirname "$path" ) || return local parent ; parent=$( dirname "$path" ) || return
mkdir-sticky "$parent" "$@" || return mkdir-sticky "$parent" "$@" || return
parent=$( realpath "$parent" ) || return parent=$( realpath "$parent" ) || return

View File

@ -7,11 +7,23 @@ function create-zpools { # 1: mnt
done done
} }
## Imports all of the system's ZFS pools that are »createDuringInstallation« and not imported yet.
function import-zpools { # 1: mnt, 2: skipImported
local mnt=$1 ; local skipImported=${2:-}
local poolName ; for poolName in "@{!config.setup.zfs.pools[@]}" ; do
if [[ ! @{config.setup.zfs.pools!catAttrSets.createDuringInstallation[$poolName]} ]] ; then continue ; fi
if @{native.zfs}/bin/zfs get -o value -H name "$poolName" &>/dev/null && [[ $skipImported ]] ; then continue ; fi
@{native.zfs}/bin/zpool import -f -N -R "$mnt" "$poolName" && prepend_trap "@{native.zfs}/bin/zpool export '$poolName'" EXIT || return
: | @{native.zfs}/bin/zfs load-key -r "$poolName" || true
ensure-datasets "$mnt" '^'"$poolName"'($|[/])' || return
done
}
declare-command create-zpool mnt poolName << 'EOD' declare-command create-zpool mnt poolName << 'EOD'
Creates a single of the system's ZFS pools, and its datasets. Can be called manually to create pools that were added to the configuration, or to create those declared with »createDuringInstallation = false«. Expects the backing device(-partition)s to exist as declared for the pool. Creates a single of the system's ZFS pools, and its datasets. Can be called manually to create pools that were added to the configuration, or to create those declared with »createDuringInstallation = false«. Expects the backing device(-partition)s to exist as declared for the pool.
EOD EOD
declare-flag install-system zpool-force "" "(create-zpool) When creating ZFS storage pools, pass the »-f« (force) option. This may be required when installing to disks that are currently part of a pool, or ZFS refuses do reuse them." declare-flag install-system,create-zpool zpool-force "" "When creating ZFS storage pools, pass the »-f« (force) option. This may be required when installing to disks that are currently part of a pool, or ZFS refuses do reuse them."
function create-zpool { function create-zpool {
local mnt=$1 ; local poolName=$2 local mnt=$1 ; local poolName=$2
eval 'local -A pool='"@{config.setup.zfs.pools[$poolName]}" eval 'local -A pool='"@{config.setup.zfs.pools[$poolName]}"
@ -67,7 +79,8 @@ function ensure-datasets {
if $zfs get -o value -H name "${dataset[name]}" &>/dev/null ; then # dataset exists: check its properties if $zfs get -o value -H name "${dataset[name]}" &>/dev/null ; then # dataset exists: check its properties
if [[ ${props[mountpoint]:-} ]] ; then # don't set the current mount point again (no-op), cuz that fails if the dataset is mounted if [[ ${props[mountpoint]:-} ]] ; then # don't set the current mount point again (no-op), cuz that fails if the dataset is mounted
local current=$($zfs get -o value -H mountpoint "${dataset[name]}") ; current=${current/$mnt/} # (The behavior inside a (nixos-enter) chroot is quite odd: ZFS ignores the chroot when printing the mountpoint, but heeds it when setting it. So this test fails (if the CHROOT_DIR is not set), but the set operation still sets the correct value.)
local current=$($zfs get -o value -H mountpoint "${dataset[name]}") ; current=${current/${CHROOT_DIR:-}$mnt/}
if [[ ${props[mountpoint]} == "${current:-/}" ]] ; then unset props[mountpoint] ; fi if [[ ${props[mountpoint]} == "${current:-/}" ]] ; then unset props[mountpoint] ; fi
fi fi
if [[ ${props[keyformat]:-} == ephemeral ]] ; then if [[ ${props[keyformat]:-} == ephemeral ]] ; then

View File

@ -71,7 +71,7 @@ in {
system.boot.loader.id = "extlinux"; system.boot.loader.id = "extlinux";
system.build.installBootLoader = "${pkgs.writeShellScript "install-extlinux.sh" '' system.build.installBootLoader = "${pkgs.writeShellScript "install-extlinux.sh" ''
if [[ ! ''${1:-} || $1 != /nix/store/* ]] ; then echo "Usage: $0 TOPLEVEL_PATH" 1>&2 ; exit 1 ; fi if [[ ! ''${1:-} || $1 != /nix/store/* ]] ; then echo "Usage: ${builtins.placeholder "out"} TOPLEVEL_PATH" 1>&2 ; exit 1 ; fi
export PATH=$PATH:${pkgs.stdenv}/bin export PATH=$PATH:${pkgs.stdenv}/bin
${extlinux-conf-builder} "$1" -d ${esc cfg.targetDir} ${extlinux-conf-builder} "$1" -d ${esc cfg.targetDir}

View File

@ -12,9 +12,9 @@ The default functions in [`lib/setup-scripts`](../../lib/setup-scripts/README.md
What keys are used for is derived from the attribute name in the `.keys` specification, which (plus a `.key` suffix) also becomes their storage path in the keystore: What keys are used for is derived from the attribute name in the `.keys` specification, which (plus a `.key` suffix) also becomes their storage path in the keystore:
* Keys in `luks/` are used for LUKS devices, where the second path label is both the target device name and source device GPT partition label, and the third and final label is the LUKS key slot (`0` is required to be specified, `1` to `7` are optional). * Keys in `luks/` are used for LUKS devices, where the second path label is both the target device name and source device GPT partition label, and the third and final label is the LUKS key slot (`0` is required to be specified, `1` to `7` are optional).
* Keys in `zfs/` are used for ZFS datasets, where the further path is that of the dataset. Datasets implicitly inherit their parent's encryption by default. An empty key (created by method `unencrypted`) explicitly disables encryption on a dataset. Other keys are by default used with `keyformat=hex` and must thus be exactly 64 (lowercase) hex digits. * Keys in `zfs/` are used for ZFS datasets, where the further path is that of the dataset. Datasets implicitly inherit their parent's encryption by default. An empty key (created by method `unencrypted`) explicitly disables encryption on a dataset. Other keys are by default used with `keyformat=hex` and must thus be exactly 64 (lowercase) hex digits.
* Keys in `home/` are meant to be used as composites for home directory encryption, where the second and only other path label us the user name. * Keys in `home/` are meant to be used as composites for home directory encryption, where the second and only other path label is the user name.
The attribute value in the `.keys` keys specification dictates how the key is acquired, primarily initially during installation, but (depending on the keys usage) also during boot unlocking, etc. The attribute value in the `.keys` keys specification dictates how the key is acquired, primarily initially during installation, but (depending on the key's usage) also during boot unlocking, etc.
The format of the key specification is `method[=args]`, where `method` is the suffix of a bash function call `gen-key-<method>` (the default functions are in [`add-key.sh`](../../lib/setup-scripts/add-key.sh), but others could be added to the installer), and `args` is the second argument to the respective function (often a `:` separated list of arguments, but some methods don't need any arguments at all). The format of the key specification is `method[=args]`, where `method` is the suffix of a bash function call `gen-key-<method>` (the default functions are in [`add-key.sh`](../../lib/setup-scripts/add-key.sh), but others could be added to the installer), and `args` is the second argument to the respective function (often a `:` separated list of arguments, but some methods don't need any arguments at all).
Most key generation methods only make sense in some key usage contexts. A `random` key is impossible to provide to unlock the keystore (which it is stored in), but is well suited to unlock other devices (if the keystore has backups); conversely a USB-partition can be used to headlessly unlock the keystore, but would be redundant for any further devices, as it would also be copied into the keystore. Most key generation methods only make sense in some key usage contexts. A `random` key is impossible to provide to unlock the keystore (which it is stored in), but is well suited to unlock other devices (if the keystore has backups); conversely a USB-partition can be used to headlessly unlock the keystore, but would be redundant for any further devices, as it would also be copied into the keystore.

View File

@ -10,7 +10,7 @@ Such state generally falls into one of four categories:
3) It may or may not be secret and can be re-generated quickly. 3) It may or may not be secret and can be re-generated quickly.
Category 1 data can not be included in the nix store and thus can not (directly) be derived from the system configuration. Category 1 data can not be included in the nix store and thus can not (directly) be derived from the system configuration.
It needs to be stored in a way that the host (and only the host) can access it an that it can't be lost. It needs to be stored in a way that the host (and only the host) can access it and that it can't be lost.
It is therefore referred to as `remote` data (even though it would usually also be stored locally). It is therefore referred to as `remote` data (even though it would usually also be stored locally).
Category 2 data is required for the system to boot (in reasonable time), and should thus, as `local` data, be stored persistently on the host. Category 2 data is required for the system to boot (in reasonable time), and should thus, as `local` data, be stored persistently on the host.
@ -204,7 +204,15 @@ in {
}) (lib.mkIf cfg.persistenceFixes { # Cope with the consequences of having »/« (including »/{etc,var,root,...}«) cleared on every reboot. }) (lib.mkIf cfg.persistenceFixes { # Cope with the consequences of having »/« (including »/{etc,var,root,...}«) cleared on every reboot.
environment.etc.nixos.source = "/local/etc/nixos"; environment.etc = {
nixos.source = "/local/etc/nixos";
# 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";
};
systemd.tmpfiles.rules = [ # keep in mind: this does not get applied super early ... systemd.tmpfiles.rules = [ # keep in mind: this does not get applied super early ...
# »/root/.nix-channels« is already being restored. # »/root/.nix-channels« is already being restored.
@ -216,12 +224,7 @@ in {
"f /${keep}/root/.local/share/nix/repl-history 0600 root root -" "f /${keep}/root/.local/share/nix/repl-history 0600 root root -"
"L+ /root/.local/share/nix/repl-history - - - - ../../../../${keep}/root/.local/share/nix/repl-history" "L+ /root/.local/share/nix/repl-history - - - - ../../../../${keep}/root/.local/share/nix/repl-history"
# SSHd host keys:
"d /${keep}/etc/ssh/ 0755 root root - -" "d /${keep}/etc/ssh/ 0755 root root - -"
"L /etc/ssh/ssh_host_ed25519_key - - - - /${keep}/etc/ssh/ssh_host_ed25519_key"
"L /etc/ssh/ssh_host_ed25519_key.pub - - - - /${keep}/etc/ssh/ssh_host_ed25519_key.pub"
"L /etc/ssh/ssh_host_rsa_key - - - - /${keep}/etc/ssh/ssh_host_rsa_key"
"L /etc/ssh/ssh_host_rsa_key.pub - - - - /${keep}/etc/ssh/ssh_host_rsa_key.pub"
]; ];
fileSystems = { # this does get applied early fileSystems = { # this does get applied early

View File

@ -3,7 +3,7 @@
# ZFS Pools and Datasets # ZFS Pools and Datasets
This module primarily allows the specification of ZFS pools and datasets. The declared pools and datasets are complemented with some default and are then used by [`lib/setup-scripts/zfs.sh`](../../lib/setup-scripts/zfs.sh) to create them during system installation, and can optionally later be kept up to date (with the config) at config activation time or during reboot. This module primarily allows the specification of ZFS pools and datasets. The declared pools and datasets are complemented with some default and are then used by [`lib/setup-scripts/zfs.sh`](../../lib/setup-scripts/zfs.sh) to create them during system installation, and can optionally later be kept up to date (with the config) at config activation time or during reboot.
Additionally, this module sets some defaults for ZFS (but only in a "always better than nothing" style, so `lib.mkForce null` should never be necessary). Additionally, this module sets some defaults for ZFS (but only in an "always better than nothing" style, so `lib.mkForce null` should never be necessary).
## Implementation ## Implementation